vue后台管理系統項目


項目介紹

 

1.項目根目錄文件

 

2.源碼子目錄結構

 

3.api目錄

 

4.assets目錄

 

5.components目錄

 

6.mixins目錄

 

7.permission目錄

 

8.router目錄

 

9.store目錄

 

10.styles目錄

 

11.utils目錄

 

 

 

 

 

 

項目文件介紹

 

1.安裝element-ui組件實現按需加載

// 1.1.npm i element-ui -S

// 1.2.在babel.config.js中配置

module.exports = {

    plugins: [

        [
              'component',
              {
                libraryName: 'element-ui',
                styleLibraryName: 'theme-chalk'
              }
        ],
    ]
     
}

// utils下新建文件Element.js按需加載組件

import Vue from 'vue'

// 按需引入

import {

  Button,

} from 'element-ui'

// 注冊組件,之后就可以在所有組件中使用

Vue.use(Button)

/*
    注意:列如消息類組件,如:Message,Notification,MessageBox等
    
    不能像上面一樣注冊使用,否則還沒有使用該組件該組件就會被初始化
*/


// 解決:綁定到vue原型上

Vue.prototype.$message = Message
Vue.prototype.$notify = Notification
Vue.prototype.$confirm = MessageBox

// 再組件使用時直接this調用即可

this.$message.success('注冊成功')

// 如果想使用MessageBox中的確認彈框

this.$confirm.confirm()

 

2.封裝二次請求使用起來更方便,在utils中新建request.js中寫入

// 安裝第三方請求工具axios

npm install axios

// request.js

import axios from 'axios'

// 處理javascript大數字的問題

// 安裝 npm i json-bigint

import jsonBig from 'json-bigint'


import store from '@/store/index'


// element-ui的加載組件和消息提示組件
import { message, Loading } from 'element-ui'

// 是我們項目的配置文件,項目的接口和其他的配置放在utils目錄中的config.js中

import { config, loaddingConfig } from '@/utils/config'

// 創建一個新的axios對象供我們使用
const request = axios.create({

  baseURL: `${config.baseURL}${config.prot}`,
  timeout: config.timeout
})


// 請求攔截器

// 注意在單獨的js中使用Loading和在組件中使用有一點不同

/*

    1.在組件中使用

    const loading = this.$loading({
          lock: true,
          text: 'Loading',
          spinner: 'el-icon-loading',
          background: 'rgba(0, 0, 0, 0.7)'
     });


    關閉加載

    loading.close()


    2.在單獨的js文件中使用

    Loading.service({

         lock: true,
         text: 'Loading',
         spinner: 'el-icon-loading',
         background: 'rgba(0, 0, 0, 0.7)'
    })

    關閉加載狀態
    
    Loading.service().close()

*/ 

request.interceptors.request.use(config => {
  
  // 開始加載
  Loading.service(loaddingConfig)

  if (store.getters.getuserInfo.token) {

    // 每次登陸的時候將token插入到請求頭中
    config.headers.authorization = store.getters.getuserInfo.token
  }

  return config

}, error => {

  // 取消失敗終止
  Loading.service().close()
  return Promise.reject(error)
})



// 處理大數字的問題
request.defaults.transformResponse = [function (data) {
  try {
    return jsonBig.parse(data)
  } catch (err) {
    return data
  }
}]



// 響應攔截器
request.interceptors.response.use(response => {

  // 請求完畢取消加載
  Loading.service().close()
  return response
}, error => {

  Loading.service().close()

    // 處理請求錯誤的函數
  errorMsg(error.response)

  return Promise.reject(error)
})


// 異常處理
function errorMsg (error) {

  if (error) {

    return
  }

  switch (error.status) {
    case 400:
      message.error('親,您查看的資料出現了錯誤')
      break
    case 401:
      message.error('請檢查token,可能已經過期,需要重新登陸')
      tokenOverdue(error)
      break
    case 403:
      message.error('抱歉您的權限還需要升級')
      break
    case 404:
      message.error('資源被狗狗調走了')
      break
    case 408:
      message.error('請求超時,請重試')
      break
    case 500:
      message.error('可愛的服務器好像奔潰了')
      break
    case 502:
      message.error('請仔細檢查您的網絡')
      break
    case 504:
      message.error('您的網絡很慢,已經超時,請重試')
      break
    case 503:
      message.error('當前服務不支持')
      break
    default:
      message.error('與服務器的連接斷開了')
      break
  }
}


// token的過期處理

function tokenOverdue (error) {
  // 如果有token的話重新存儲,刷新

  if (error.data.token) {
    // 將刷新的token重新存儲到本地即可
    const userInfo = {
      ...store.getters.getuserInfo,
      token: error.data.token
    }
    
    // 將新的token重新存儲到本地,
    store.commit('SET_USERINFO', userInfo)
    
    // 將錯誤的請求再重新發送刷新token
    return request(error.config)
  }
}


// 導出請求接口封裝函數
export default (method, url, data = null) => {
  method = method.toUpperCase()

  if (method === 'POST') {
    return request.post(url, data)
  } else if (method === 'GET') {
    return request.get(url, { params: data })
  } else if (method === 'DELETE') {
    return request.delete(url, { params: data })
  } else if (method === 'PUT') {
    return request.put(url, data)
  }
}

 

3.創建api根目錄,里面當接口文件

// user.js文件

import request from '@/utils/request'

// 用戶登陸 export const userLogin = params => request('GET', '/admin/user/login', params) // 用戶注冊 export const userRegister = params => request('GET', '/admin/user/register', params) // 獲取用戶的權限數據 export const getUserAuth = params => request('GET', '/admin/user/user_auth', params) // 獲取用戶頭像 export const getUserImg = params => request('GET', '/admin/user/user_img', params) // 獲取用戶登陸時的頭像 export const getLoginImg = params => request('GET', '/admin/user/login_img', params) // 修改用戶資料 export const editUserInfo = data => request('POST', '/admin/user/edit_user', data)

 

4.require.context(path, Boolean, file)動態加載組件

 

  1.為什么使用下一步components目錄中的組件要引入使用,那么就存在一個問題,如果我們一個頁面要引入很多組件那么就會有很多的import。

     此時可以通過使用require.context(path, Boolean, file) 之后就可以通過組件的name屬性來調用組件

 

// 在components目錄下新建 cptsRegister.js

// cptsRegister.js

import Vue from 'vue'

function capitalizeFirstLetter (str) {
  return str.charAt(0).toUpperCase() + str.slice(1)
}

// 用來匹配.vue的前綴函數
function validateFileName (str) {
  return /^\S+\.vue$/.test(str) && str.replace(/^\S+\/(\w+)\.vue$/, (res, $1) => capitalizeFirstLetter($1))
}

const requireComponent = require.context('./', true, /\.vue$/)

// 遍歷匹配到的文件夾及文件名,並且遍歷得到每一個
requireComponent.keys().forEach(filePath => {

  // 得到每一個.vue文件中的屬性方法和組件的name值
  const componentConfig = requireComponent(filePath)
  // 得到文件名的前半部分index
  const fileName = validateFileName(filePath)
  // 判斷如果是以index開頭命名的就返回組件的name值來注冊組件,否則就使用文件名來注冊

  const componentName = fileName.toLowerCase() === 'index' ? 
capitalizeFirstLetter(componentConfig.default.name) : fileName
  Vue.component(componentName, componentConfig.default || componentConfig)
})


// components目錄下的cheking目錄的vue

<template>

    <div class="container">
        我是一個組件
    </div>

</template>

<script>

export default {

    name: 'Checking',
    
}

</script>


// 其他組件中使用,直接使用,無需引入和注冊,但是必須和組件的name名字保持一致,是不是很棒

<Checking />

 

5.處理一下過濾器把

// 在utils目錄下創建filters.js文件,我們一次性注冊掉所有的過濾器

// filter.js

// 事件格式化第三方插件
import moment from 'moment'

const filters = {

  relativeTime (value) {
    return moment(value).startOf('second').fromNow()
  },

  formatTime (value, format = 'YYYY-MM-DD HH:mm:ss') {
    return moment(value).format(format)
  },

  statusFilter (status) {
    const statusText = ['審核通過', '草稿', '待審核', '審核失敗']

    if (status === undefined) return []

    if (status.length) {

      status.forEach(item => {
        
        item.statusContent = statusText[item.status]
      })
    }
    return status
  }
}

export default filters


// 之后在main.js中一次性注冊


import filterRegister from './utils/filters'

for (const key in filterRegister) {

  Vue.filter(key, filterRegister[key])
}

 

6.圖片懶加載( vue-lazyload )

// 安裝 vue-lazyload 插件

npm i vue-lazyload

// 在main.js中引入使用

import VueLazyload from 'vue-lazyload'

// 引入圖片懶加載的配置文件
import { lazyLoad } from './utils/config'


// 注冊使用
Vue.use(VueLazyload, lazyLoad)

// 在組件中img標簽上的src屬性替換成v-lazy="reqquire('../../assets/images')",
注意圖片的地址必須是網絡地址,如果是本地地址需要跳過require()導入使用


// config中的配置

// 圖片懶加載的配置
export const lazyLoad = {

  preLoad: 1.3,

  // 加載中顯示的圖片
  loading: 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=2843662159,2317606805&fm=16&gp=0.jpg',

  // 加載失敗顯示的圖片
  error: 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=104394466,2625116464&fm=11&gp=0.jpg',

  // 嘗試加載一次
  attempt: 1
}

 

7.路由切換進度條(nprogress

// 安裝進度條插件

npm i nprogress

// 在全局路由導航中使用,在目錄permission下的index.js中使用

import router from '@/router'

// 導入進度條
import nprogress from 'nprogress'

router.beforeEach((to, from, next) => {

   // 開啟進度條
  nprogress.start()
})


router.afterEach((to, from, next) => {
  // 關閉進度條
  nprogress.done()
})


// main.js中引入樣式文件

// 引入滾動條的樣式文件
import 'nprogress/nprogress.css'


// 引入全局路由導航攔截
import './permission/index'


// 自定義滾動條的顏色

#nprogress .bar {
  background: #c3f909 !important;
}

 

8.看一下路由的設置

import Vue from 'vue'
import VueRouter from 'vue-router'

// 公共路由
export const publicRoutesMap = [
  {
    path: '/login',
    name: 'login',
    component: () => import(/* webpackChunkName: "user" */ '@/views/Login')
  },
  {
    path: '/register',
    name: 'register',
    component: () => import(/* webpackChunkName: "user" */ '@/views/Register')
  },
]


// 權限路由

// 需要的權限路由,動態掛載的路由
export const asyncRoutesMap = [
  {
    path: '*',
    redirect: '/404',
    hidden: true,
    meta: {
      roles: ['user']
    }
  },
  {
    path: '/404',
    name: 'four',
    component: () => import('../views/Error/foruAndFour.vue'),
    meta: {
      roles: ['user']
    }
  }
]

 

9.在store目錄下的modules目錄新建permission.js文件

// 把公共路由和權限的路由導出來
import router, { publicRoutesMap, asyncRoutesMap } from '@/router'

// 定義一個函數用來篩選后端返回來的權限數據,如果篩選成功的話返回true,否則false
function hasPerMission (roles, route) {
  if (route && route.meta.roles) {
    return roles.some(role => route.meta.roles.indexOf(role) >= 0)
  } else {
    return true
  }
}

const permission = {

  state: {
    routers: publicRoutesMap,
    addRouters: []
  },
  mutations: {
    SET_ROUTERS (state, routers) {
      state.addRouters = routers
      state.routers = publicRoutesMap.concat(routers)
    }
  },
  actions: {
    generateRoutes ({ state, commit }, data) {
      // 返回一個promise回調

      return new Promise((resolve, reject) => {
        // 遍歷權限數組
        const accessedRoutes = asyncRoutesMap.filter(v => {
          // 如果包含admin,說明就是管理員直接進入即可
          if (data.indexOf('admin') >= 0) return true

          // 之后就是調用hasPerMission函數對象權限動態路由和后台返回的用戶權限進行嚴格匹配
          if (hasPerMission(data, v)) {
            // 判斷是否有權限路由是否有子路由,有子路由繼續遍歷
            if (v.children && v.children.length > 0) {
              v.children = v.children.filter(child => {
                // 對權限子路由和后台返回的用戶權限數據,在進行匹配,匹配成功返回
                if (hasPerMission(data, child)) {
                  return child
                }
                // 失敗返回false
                return false
              })

              // 並且要把權限的父路由返回來,不光要把權限子路由返回,
              // 無論權限子路有還是沒有,都應該把權限父路由返回來
              return v
            } else {
              // 否則說明沒有子路由,直接把父路由返回
              return v
            }
          }

          // 如果權限驗證沒有匹配項,直接返回false
          return false
        })

        // 將返回的動態路由存儲到公共路由中
        commit('SET_ROUTERS', accessedRoutes)
        resolve()
      })
    }
  },
  getters: {
    // 只要權限路由數組發生變化就重新計算
    addRouters (state) {
      return state.routers
    }
  }
}

export default permission

 

10.在全局路由導航中進行攔截當天添加

import router from '@/router'
import store from '@/store'

// 導入進度條
import nprogress from 'nprogress'

const whiteList = ['/login', '/register'] // 不重定向白名單

router.beforeEach((to, from, next) => {
  // 開啟進度條
  nprogress.start()

  // 檢查token
  if (store.getters.getuserInfo.token) {
    // 有token的話去登錄頁,直接攔截到首頁,因為我們需要用戶點擊按鈕退出,不希望通過瀏覽器的back按鈕
    if (to.path === '/login') {
      next()
    } else {
      // 如果沒有用戶的權限數據,去拉取一下用戶的權限數據
      if (store.getters.roles.length === 0) {

        store.dispatch('getUserAuth', store.getters.getuserInfo.token).then(res => {
          
          // 調用該方法對用戶的權限進行一次篩選
          store.dispatch('generateRoutes', res).then(() => {

            router.addRoutes(store.getters.addRouters)

            if (from.path !== '/login') {
              next({ ...to, replace: true })
            } else {
              next()
            }
          })
        }).catch(error => {
          // 驗證失敗重新登陸
          next({ path: '/login' })
        })
      } else {
        next()
      }
    }
  } else {

    // 需要重定向的白名單
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      next('/login')
    }
  }
})

router.afterEach((to, from, next) => {
  // 關閉進度條
  nprogress.done()
})

 

 

11.看一下mixins目錄中的文件

// 寫法和vue一模一樣,在需要使用的vue組件中混入使用即可

// mixins目錄下的index.js

export const publicLogic = {

    data () {

        return {
            
            msg: "我是mixins的數據"
        }
    },

    methods: {},

    created () {},

    watch : {

    },

    mounted () {}
}


// 在其他組件中混入

<template>

</template>

<script>

import { publicLogic } from '@/mixins/index'

export default {
    
    // 通過mixins書信混入
    mixins: [publicLogic],

    data () {

        return {

        }
    },

    created () {
        
        console.log(this.msg)    // 即可拿到混入的數據
    }
}

</script>

 

12.pc端的滾動條插件(vue-happy-scroll)

// 安裝使用

npm i vue-happy-scroll

import { HappyScroll } from 'vue-happy-scroll'

// 在組件中注冊
components: {

    HappyScroll
}


<happy-scroll :min-length-v="0.2" color="rgba(3,253,244,1)" size="10" hide-horizontal>

    // ...需要滾動的y元素

</happy-scroll>


// 先取消瀏覽器默認的滾動

body,html {
  overflow-y: hidden;
  overflow-x: hidden;
}

 

13.store目錄具體結構

// store下的index.js

import Vue from 'vue'
import Vuex from 'vuex'

// 把用戶權限驗證模塊引入
import permission from './modules/permission'

// 用戶存儲
import user from './modules/user'

// 公共計算屬性
import getters from './getters'
Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    permission,
    user
  },
  getters
})


// store目錄下的getters.js

const getters = {
  getuserInfo: state => state.user.userInfo,
  roles: state => state.user.roles,
  navBars: state => state.user.navBars,
  cache: state => state.user.cache
}

export default getters


// store目錄下的modules目錄下的js文件,都是一個一個的模塊

// user.js 用來存儲放置用戶的邏輯操作



// 引入api接口
import { userLogin, getUserAuth } from '@/api/user'

const user = {

  state: {

    // 需要往本地存儲的用戶信息包含token
    userInfo: JSON.parse(window.sessionStorage.getItem('userInfo')) || {},
    // 用戶的權限信息
    roles: [],
    // 用戶的導航欄數據
    navBars: JSON.parse(window.sessionStorage.getItem('nav_bar')) || {
      navBarArr: [],
      index: 0
    },
    // 緩存組件
    cache: []
  },

  mutations: {

    SET_USERINFO (state, userInfo) {
      state.userInfo = userInfo
      window.sessionStorage.setItem('userInfo', JSON.stringify(state.userInfo))
    },

    // 存儲用戶的navBar導航欄數據
    SET_USERNAVBAR ({ navBars }, navBar) {

      if (navBars.navBarArr.length === 0) {
  
        navBars.navBarArr.push(navBar)
      } else {
  
        const navItem = navBars.navBarArr.find((item, index) => {
          if (item.path === navBar.path) {
            navBars.index = index
            return true
          }
        })
  
        if (!navItem) {
          navBars.navBarArr.push(navBar)
          navBars.index = navBars.navBarArr.length - 1
        }
      }
  
      window.sessionStorage.setItem('nav_bar', JSON.stringify(navBars))
    },

    // 動態添加緩存組件
    ADD_CACHE ({ cache }, name) {
      if (cache.includes(name)) return

      cache.push(name)
    },

    // 動態刪除緩存組件
    REMOVE_CACHE ({ cache }, name) {
      const index = cache.indexOf(name)

      if (index === -1) return

      cache.splice(index, 1)
    }
  },

  actions: {

    // 獲取用戶信息
    userLogin ({ commit }, userInfo) {
      return new Promise((resolve, reject) => {
        userLogin(userInfo).then(res => {
         
          if (res.data.code === 200) {
            
            commit('SET_USERINFO', res.data.userInfo)
          }
          resolve(res.data)
        }).catch(error => {

          reject(error)
        })
      })
    },

     // 獲取用戶權限
     getUserAuth ({ state }, token) {
      return new Promise((resolve, reject) => {
        getUserAuth({ token }).then(res => {
          state.roles.push(...res.data)
          resolve(res.data)
        }).catch(error => {
          reject(error)
        })
      })
     }
  }
}

export default user


// permission.js 用來存儲放置用戶的權限方法

 

 

14.vue.config.js配置文件

const CompressionPlugin = require('compression-webpack-plugin')

const path = require('path')

const BundleAnalyzer = require('webpack-bundle-analyzer').BundleAnalyzerPlugin


module.exports = {

  // 是否觸發eslint檢查
  lintOnSave: false,

  // 是否使用包含運行時編譯器的 Vue 構建版本, 但是這會讓你的應用額外增加 10kb 左右
  runtimeCompiler: false,

  publicPath: '/',

  // 打包文件的出口
  outputDir: 'dist',

  // 放置生成的css和js和img和fonts的目錄
  assetsDir: 'static',

  // 數組存放html的路徑
  indexPath: 'index.html',

  productionSourceMap: false,

  /*
  
    默認情況下,生成的靜態資源在它們的文件名中包含了 hash 以便更好的控制緩存,

    前提是保證index.html是vue cli自動生成的,如果無法保證的話就設置為false
  */
  filenameHashing: true,

  chainWebpack: config => {
    // ============壓縮圖片 start============
    config.module.rule('images')
        .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
        .use('url-loader')
        .loader('url-loader')
        .options({
          limit: 10240,
          outputPath: 'static/images'
        })
        .end()
    // ============壓縮圖片 end============
  },

  configureWebpack: config => {

    return {

      plugins: [

        // 壓縮js和css和html
        new CompressionPlugin({
          test: /\.js$|\.html$|\.css/,
          threshold: 10240,
          deleteOriginalAssets: false
        }),

        // 圖形化展示打包后的詳情
        new BundleAnalyzer()
      ],

      performance: {
        // 關閉webpack的性能提示
        hints:'warning',
        //入口起點的最大體積
            maxEntrypointSize: 50000000,
            //生成文件的最大體積
        maxAssetSize: 30000000,
        //只給出 js 文件的性能提示
        assetFilter: function(assetFilename) {
                return assetFilename.endsWith('.js');
            }
      },

      // 指定不打包的第三方包,那么這些包需要在html頁面通過cdn引入
      externals: {
        'vue': 'Vue',
        'vuex': 'Vuex',
        'vue-router': 'VueRouter',
        'axios': 'axios',
        "moment": "moment",
        'echarts': 'echarts'
      }
    }
  },

}


// index.html

<script
   src="https://cdn.bootcss.com/vue/2.6.11/vue.runtime.min.js"
   crossorigin="anonymous"
>
</script>
<script
   src="https://cdn.bootcss.com/vue-router/3.1.3/vue-router.min.js"
   crossorigin="anonymous"
>
</script>
<script
   src="https://cdn.bootcss.com/vuex/3.1.2/vuex.min.js"
   crossorigin="anonymous"
>
</script>
<script
   src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"
   crossorigin="anonymous"
>
</script>
<script
   src="https://cdn.bootcss.com/moment.js/2.24.0/moment.min.js"
   crossorigin="anonymous"
>
</script>
<script
   src="https://cdn.bootcss.com/echarts/3.7.1/echarts.min.js"
   crossorigin="anonymous"
>
</script>

 

15.babel.config.js

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  plugins: [
    [
      'component',
      {
        libraryName: 'element-ui',
        styleLibraryName: 'theme-chalk'
      }
    ],

    /*
    
      把es6轉換成es5的時候,babel會需要一些輔助函數,如果多個源碼文件都依賴這些輔助函數

      那么這些輔助函數會出現很多次,造成代碼的沉余,為了不讓這些輔助函數的代碼重復注銷

      是 babel-plugin-transform-runtime插件可以做到讓他們只出現一次,將這些輔助函數幫到

      一個單獨的模塊 babel-runtime 中,這樣做能減小項目文件的大小。
    
    */
    "@babel/plugin-transform-runtime"
  ]
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM