項目介紹
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" ] }