清晰的項目結構能讓別人開發進來更容易理解,固然,每一個人都有必定的代碼風格習慣。但基於vue開發框架的項目,vue-cli腳手架搭建的項目組織結構大同小異。同時,預想到後面的需求變動及功能增長進展得更有效率,下面截圖是我以爲比較好的項目組織結構:css
這個截圖只是針對我的以爲比較通用的vue工程結構,不過這個結構要根據具體的項目狀況調整,沒必要爲了模塊化而模塊化。模塊化的優點就是體如今項目業務比較複雜的狀況,若是項目業務邏輯並不複雜,能夠適當的刪減部分模塊或文件。前端
相關說明:vue
assets: 存放圖片、UI設計的圖標文件node
componets:自研的業務型及通用型組件webpack
router:項目的路由管理模塊ios
store:基於vuex的狀態管理容器,api存放各模塊的數據請求,modules存放將store分割成模塊(module),按官網的說法,每一個模塊應該擁有本身的 state、mutation、action、getter,主要是解決應用的全部狀態若是所有集中到一個比較大的store對象,當應用變得很是複雜時,store 對象就有可能變得至關臃腫而難以維護。es6
例子:web
其中的一個模塊configManage.jsajax
import { configManageService } from "../api/index" // state const state = { accountMenuList:[] } // getters const getters = { // 菜單 menuTree: state => { return state.accountMenuList; }, } // actions const actions = { async GET_ACCOUT_MENU({ state, commit }, model) { // 參數 state爲當前局部狀態,commit響應式改變當前綁定的菜單數據 const res = await configManageService.getACountMenu(model); commit("CHANGE_MENU", res.data); } } // mutations const mutations = { CHANGE_MENU: (data) => { state.accountMenuList = data; } } export default { state, getters, actions, mutations }
index.js,統一出口,導出所有的store模塊vue-router
import Vue from 'vue' import Vuex from 'vuex' import index from './modules/index' import report from './modules/report' import createLogger from 'vuex/dist/logger' // 控制檯輸出當前變化的某個狀態 Vue.use(Vuex) const debug = process.env.NODE_ENV !== 'production' // 生產或開發環境打包 export const indexStore = new Vuex.Store({ modules: { report, index }, strict: debug, // 按照官網建議,改變state的狀態只能經過getter plugins: debug ? [createLogger()] : [] })
style:
存放重寫UI庫的樣式和不一樣組件公共樣式文件
util:
存放用es6封裝的工具類,http請求類,配置類、校驗類、事件類等
views:
存放各路由模塊頁面
static:
存放全局配置文件,環境域名等
iconfont:
存放字體圖標文件
基於vue的項目,與後臺請求數據咱們一般使用的是axios,它是基於promise的http庫,其提供的優秀的特性被普遍運用在項目當中,官方已推薦使用axios,放棄原有的vue-resource。
一、axios的封裝,在不少業務場景下用來進行請求的攔截、響應的攔截及請求超時等;
// axios請求類,一些基礎化配置 class AjaxRequestModel { constructor(model) { this.url = model.url || ""; this.data = model.data || {}; this.method = model.method || "POST"; // this.success = model.success || function () {}; // this.fail = model.fail || function () {}; // this.slientSuccess = model.slientSuccess || true; this.failMsg = model.failMsg || true; this.baseUrl = model.baseUrl || window.sysConfig.baseUrl; this.loading = model.loading || true; // this.setData(); this.setUrl(); } setData() { // let options = { // sessionid: "" // }; this.data = Object.assign({}, this.data); } setUrl() { this.url = this.baseUrl + this.url; } } // 實例化axios,配置請求超時時間 const axiosInstance = axios.create({ timeout: 1000 * 20 }); // 封裝ajaxService函數,以更少的代碼處理get、post、delete、put請求方式,同時支持async、await異步處理方案,返回promise const ajaxService = param => { let model = new AjaxRequestModel(param); let o = { url: model.url, data: model.data, method: model.method }; // if (model.loading) { // ak.Msg.showLoading(); // } if (model.method === "GET") { o = { url: model.url, params: model.data, method: model.method }; } return new Promise((resolve, reject) => { axiosInstance .request(o) .then(res => { if (res.data.code === 200 || res.data.code === 0) { resolve(res.data); } else { ak.Msg.toast(res.data.message, "error"); reject(res.data); } }) .catch(err => { httpResponseHandle.call(err); reject(err); }); }); };
二、在請求的攔截中,能夠攜帶用於接口身份驗證的token,配置headers請求頭、提交參數的序列化等
// 請求頭相關配置 axiosInstance.interceptors.request.use( function (config) { const info = ak.Utils.getSessionStorage("USER_INFO"); config.headers.common['token'] = info ? info[0].token : ""; // config.headers.common['Content-Type'] = "application/json"; return config; }, function (error) { return Promise.reject(error); } );
三、在響應的攔截中,能夠進行根據各類狀態碼來進行錯誤的統一處理等
const httpResponseHandle = err => { const opt = err.response; // 請求超時 if (err.code === "ECONNABORTED") { ak.Msg.toast("請求超時,請稍後再試", "error"); } if (opt.status === 401) { ak.Msg.confirm("用戶登陸超時,請從新登陸", () => { sessionStorage.removeItem("USER_INFO"); window.utryVue.$router.replace("/login"); location.reload(); }); } else { ak.Msg.toast(opt.data.message, "error"); } };
四、api接口模塊化管理,業務邏輯和數據請求分層,這樣能夠很方便統一管理咱們的接口
如圖,把不一樣的功能拆分,實現代碼模塊化管理,所有的接口均放在api文件夾下面。index.js是一個api接口的導出的出口,這樣就能夠把api接口根據功能劃分爲多個模塊,利於多人協做開發,好比一我的只負責一個模塊的開發等,還能方便每一個模塊中接口的命名
index.js:
import report from './report'; // 報表模塊 import accountService from './accountService'; // 登錄、用戶信息相關 // 導出接口 export { accountService, report }
API請求service層:
// 報表管理請求模塊,與後臺請求的參數、請求方式、url均看做一個model import http from "@/util/http.js"; const API_CONTEXT = "sys/"; // 請求的上下文 const report = { async getMenuList() { let model = {}; model.url = API_CONTEXT + "category/getCategoryTree"; model.method = "GET"; let res = await http.ajaxService(model); return res; }, async removeMenu(model) { model.data = { ...model }; model.url = API_CONTEXT + "category/removeCategory"; let res = await http.ajaxService(model); return res; } } export default report;
組件的業務邏輯層調用方式:
// 說明:async、await的寫法省去了很多的回調,在有些必須請求兩個接口或者兩個接口以上場景下,async、await優點就顯示出來了 import { reportService } from "../../store/api/index"; async getMenuList() { const param = { role: "" }; const res = await reportService.getMenuList(param); // 下面代碼返回成功時才執行,錯誤由上面所講的axios封裝ajaxService統一處理 this.menuList = res.data; }
五、若是後期維護須要修改的接口,咱們就直接在api.js中找到對應的修改就行了,而不用去每個頁面查找咱們的接口而後再修改會很麻煩,若是修改的量比較大,不免會自測不充分產生bug,直接gg。還有就是若是直接在咱們的業務代碼修改接口,一不當心還容易動到咱們的業務代碼形成沒必要要的麻煩
六、處理接口域名、端口有多個狀況
// 無需前端打包,運維環境快速修改配置,eg: window.sysConfig = { // 運維平臺 baseUrl: 'http://10.0.33.97:7083/', // 租戶平臺 tenantUrl: 'http://10.0.33.96:7082/' } // 區分不一樣平臺的url地址在http.js文件下的AjaxRequestModel類實例化會統一處理 this.baseUrl = model.baseUrl ? window.sysConfig.baseUrl : window.sysConfig.tenantUrl
按需加載是針對某些第三方庫體積比較大的狀況下,優化webpack打包後的js體積,減小頁面的加載時間
以echart爲例子:
優化前:
// 全導入 import * as echarts from "echarts";
webpack打包後:
優化後(主js體積減小了400kb,同時build編譯打包速度也獲得了減小)
import echarts from "echarts/lib/echarts"; // 依賴注入,目前項目只用到折線圖、餅圖和柱形圖,故只需引入對應的模塊便可,tooltip是提示類,title是鼠標懸停顯示的對應的圖表名稱 import 'echarts/lib/chart/bar'; import 'echarts/lib/chart/line'; import 'echarts/lib/chart/pie'; import 'echarts/lib/component/tooltip'; import 'echarts/lib/component/title';
vue頁面組件的樣式基本是寫在<style scoped lang="less"></style>中,增長scoped屬性的目的讓其樣式只在當前頁面有效。按照這些寫的方式,編譯後當前標籤會加上相似於[data-v-]這樣的屬性,可是第三方的UI組件庫並無編譯爲帶[data-v-]這樣的屬性,因此就遇到了當前頁面覆蓋的樣式沒生效的狀況,有沒有方法處理這種問題呢。有些小夥伴可能會想到我在公共樣式裏面寫,額外添加類名來覆蓋當前組件的樣式,其實,這也不失爲一種方案,可是會引來樣式全局污染和命名可能重名的狀況。下面列舉更簡單粗暴的方式,同時避免了樣式污染和命名衝突的問題:
.menu-tree { /deep/ .el-tree-node__content { height: 32px; } /deep/ .is-current .el-tree-node__content { background-color: #f2f2fa; } }
編譯後,默認給menu-tree加上了[data-v-3c93a211]
/deep/深度選擇器支持less或者sass,若是你用的是原生的css,能夠用<<<符號
在項目用jenkins自動化打包前端項目的時候,經常會遇到打包速度慢而體驗不好,在優化減負依賴包的狀況下,同時沒有一個測試環境或生產環境當前打包進度捉雞。這裏推薦一個第三方的插件包
progress-bar-webpack-plugin。
// 需安裝依賴 npm install progress-bar-webpack-plugin --save-dev const ProgressBarPlugin = require('progress-bar-webpack-plugin') // 在生產環境webpack配置文件的plugin是加上 new ProgressBarPlugin(), // 打包進度
這裏純屬我的觀點,可能有些小夥伴用vue開發不是聽從這個。爲何要規定組件的寫法順序呢,或者說它是官方要求的規範,不如說是能讓的代碼更加優雅,更易於維護,由於你寫的代碼不只是你一我的維護。要是一個團隊都按這個規範來,你們在維護代碼的時候認知同樣,那效率就提升了。
組件依賴:
components(自研的子組件或第三方組件)
service(api請求類,其餘服務類)
utils(工具類等)
事件傳遞(vue eventBus)
mixins(複用的屬性或方法)
組合:
mixins
組件的屬性、接口:
components
props
本地響應式屬性、狀態:
data
computed
事件註冊:
watch
組件生命週期:
created
mounted
destroyed等
組件的方法:
methods
例子:
// 例子 import utryTree from "@/components/utry-tree/utry-tree.vue"; import { reportService } from "@/store/api/index"; import Validation from "../../util/Validation"; import eventBus from "@/util/eventBus";
import reportMixins from "@/mixins/reportMixins"; export default { mixins: [], components: { }, props: { menuList: { type: Array, default() { return []; } } }, data(){}, computed:{}, watch:{}, mounted(){}, methods:{}, }
有時候,針對有些複雜組件,初始化頁面其實並不須要把所有組件資源加載進來,把業務複雜的組件抽離出來,從而能減小初始化頁面的加載時間
優化前:
import reportManage from '@/views/reportManage/index'; import reportPreview from '@/views/reportManage/reportPreview'; export default [ { path: 'reportManage/index', name: 'reportManage', component: reportManage }, { path: 'reportManage/reportPreview', name: 'reportPreview', component: reportPreview } ];
初始化頁面的加載耗時:
優化後:
import reportManage from '@/views/reportManage/index'; export default [ { path: 'reportManage/index', name: 'reportManage', component: reportManage }, { path: 'reportManage/reportPreview', name: 'reportPreview', component: () => import('@/views/reportManage/reportPreview'), meta: { keepAlive: false } } ];
初始化頁面加載耗時:
時間的差異主要是在js的解析上,主要是是由於初始化頁面沒有加載當前模塊的二次組件的js,等到跳轉到二次頁面再去解析靜態資源,整體優化後初始化頁面的加載時間快了100多毫秒。
這裏的路由拆分,是指按模塊拆分紅不一樣的路由文件,針對單頁面應用這樣更方便團隊的多人協調同步開發,本身寫的功能模塊互不影響。若是當業務需求多起來的時候,它的優點就越能體現出來。咱們並不想就在一個router.js寫整個工程的路由,這樣會是單文件代碼量龐大而變得很槽糕,同時也會帶來其餘同事誤改的問題。
咱們在router文件夾下面建立router.js做爲路由的入口文件,其餘以router.js後綴的文件存放着各個模塊的路由。
router.js:
import Vue from "vue"; import Router from "vue-router"; import NProgress from "nprogress"; // 引入nprogress,每次路由變化網頁頂端有個加載條效果 import ak from "@/util/ak.js"; // 業務路由 import login from "@/views/index/login"; // 租戶平臺 import oamLogin from "@/views/index/oamLogin"; // 運維平臺 import indexRouter from "./index.router"; // 首頁相關 import reportManage from "./reportManage.router"; // 報表管理 Vue.use(Router); // 默認登陸 let routes = [ { path: "/", redirect: "login" }, { path: "/login", name: "login", component: login }, { path: "/oamLogin", name: "oamLogin", component: oamLogin } ]; routes = routes.concat( indexRouter, reportManage ); // router register const router = new Router({ routes }); // 路由相關的攔截操做,在這裏處理,以前有的router相關操做寫在main.js,並非很友好 router.beforeEach((to, from, next) => { // 每次切換頁面時,調用進度條 NProgress.start(); // cache機制 const info = ak.Utils.getSessionStorage("USER_INFO"); const token = info ? info[0].token : ""; if (token) { next(); } else { if (to.path === "/oamLogin") { next(); } else if (to.path === "/login") { next(); } else { next("/login"); } } }); router.afterEach(() => { // 在即將進入新的頁面組件前,關閉掉進度條 NProgress.done(); });
index.router.js:
import home from '@/views/index/home'; export default [ { path: '/index/home', name: 'home', component: home } ];
這裏把首頁的路由放在一個數組裏,而後導出去,有router.js統一引入,並實例化當前路由
未完待續......