曠志光,紀婷婷,吳小麗
(上海計算機軟件技術開發(fā)中心,上海201112)
基于Vue.js的后臺單頁應用管理系統(tǒng)的研究與實現(xiàn)
曠志光,紀婷婷,吳小麗
(上海計算機軟件技術開發(fā)中心,上海201112)
通過研究Vue.js、路由、全局狀態(tài)、axios等技術,實現(xiàn)后臺單頁應用管理系統(tǒng)。對actions進行封裝,并在配置文件中定義好相關請求信息,即可實現(xiàn)帶后臺API請求的action操作,極大程度減少系統(tǒng)代碼量,并保證系統(tǒng)的統(tǒng)一性、健壯性。前端頁面根據(jù)菜單策略控制頁面文件的入口,根據(jù)頁面權限控制策略進行按鈕級別權限校驗。
Vue.js;MVVM;單頁應用;權限管理
為了滿足日益復雜、多樣的Web App需求,越來越多的原本后端處理的業(yè)務邏輯開始轉移到前端來處理。Vue.JS就是這樣一套構建用戶界面的漸進式框架,方便大部分后端邏輯移植到前端去實現(xiàn)。Vue.JS采用自底向上增量開發(fā)的設計,其核心庫只關注視圖層,它不僅易于上手,還便于與第三方庫或既有項目整合。另一方面,當Vue.JS與單文件組件及其Vue.JS生態(tài)系統(tǒng)支持的庫結合使用時,也完全能夠為復雜的單頁應用程序提供驅動。由此,采用Vue.JS進行后臺管理系統(tǒng)的前端設計工作,一定程度減輕前后端開發(fā)人員開發(fā)難度。
本系統(tǒng)前端工程基于流程簡化、低成本、快速開發(fā)、高性能等需求,主要選用到 Vue.JS、vue-router、vuex、axios等關鍵組件。
Vue.JS是整個前端工程的基礎視圖層框架,主要解決前端數(shù)據(jù)綁定的問題。傳統(tǒng)的前端開發(fā),主要基于jQuery通過各種復雜的選擇器來操作DOM。同時,通過AJAX跟服務器請求數(shù)據(jù),前端代碼一層層解析JSON,將JSON某個層級的數(shù)據(jù)賦給相應的DOM操作,還要進行請求的異常處理,數(shù)據(jù)不但操作繁瑣復雜且易出現(xiàn)未知錯誤。而通過Vue.JS的響應式雙向綁定數(shù)據(jù),實時反映數(shù)據(jù)的真實變化并映射到目標虛擬DOM上,避免前端頁面開發(fā)中DOM選擇器繁雜的操作,簡化Web前端開發(fā)流程和降低開放難度,提升前端開發(fā)效率,降低開發(fā)成本和周期。
vue-router是Vue.JS官方發(fā)布的一款路由插件,和Vue.JS是深度集成的,適合用于構建單頁面應用。傳統(tǒng)的頁面應用,是用一些超鏈接來實現(xiàn)頁面切換和跳轉的。而在vue-router單頁面應用中,則是路徑之間的切換,也就是組件的切換。通過router-view來動態(tài)掛載頁面組件,并最終渲染成頁面。另外,HTML5里引入了新的 API,history.pushState和 history.replaceState,可以通過這個新的接口做到無刷新訪問頁面的同時改變頁面URL[1],這讓Vue.JS能夠動態(tài)調(diào)整頁面路徑,方便頁面切換,提高了用戶體驗。
vuex是Vue.JS官方依照Flux實現(xiàn)的一套全局狀態(tài)管理方案,并被集成到vue-devtools,無需配置即可在瀏覽器中實現(xiàn)時光旅行式調(diào)試。當開發(fā)單頁應用時,我們通常會把狀態(tài)儲存在組件的內(nèi)部,每一個組件都擁有其自身的狀態(tài)管理,但是在整個應用層面上看,很多公共的狀態(tài)是卻是分散在各個頁面中。同時,我們經(jīng)常會需要把狀態(tài)的一部分共享給多個組件。一個常見的解決方案是使用事件系統(tǒng)來讓一個組件把一些狀態(tài)“告知”到其他組件中,來讓相應的組件去響應變化。但是這種模式的問題在于,大型組件樹中的事件流會很快變得非常繁雜,并且調(diào)用時很難去找出究竟哪里出錯,事件的冒泡也很有可能導致整個應用的資源消耗非常大。應用越來越大,頁面文件數(shù)也越來越多,多個狀態(tài)分散的跨越在許多組件和交互中,其復雜度也經(jīng)常逐漸增長。使用vuex將狀態(tài)放入一個全局的實例中,做到各個組件同步響應,減少系統(tǒng)狀態(tài)復雜度。但是在使用全局狀態(tài)管理時,我們還需要對組件的組件本地狀態(tài)(componentlocalstate)和應用層級狀態(tài)(application levelstate)進行區(qū)分,避免出現(xiàn)組件本地狀態(tài)放到應用級狀態(tài)去管理。應用級的狀態(tài)不屬于任何特定的組件,但每一個組件仍然可以監(jiān)視(Observe)其變化從而響應式地更新DOM。
通過vue-router我們解決了頁面切換問題,通過vuex我們解決了全局狀態(tài)共享管理的問題,但是所有應用的基礎在于數(shù)據(jù)。傳統(tǒng)數(shù)據(jù)請求,主要利用jQuery封裝的AJAX請求來實現(xiàn)。在處理異步問題時,一般采用的是callback回調(diào)的方式。callback回調(diào)存在一個很嚴重的金字塔問題——大量的回調(diào)函數(shù)慢慢向右側屏幕延伸的一種狀態(tài)[2]。通過采用含Promise特性的組件來進AJAX請求,使得我們可以用同步的代碼形式實現(xiàn)異步的請求操作。axios就是這樣一個基于Promise用于瀏覽器和nodejs的HTTP客戶端,其可以從瀏覽器中創(chuàng)建XMLHttpRequest,支持Promise API,同時方便實現(xiàn)請求過程中的中間件操作,例如權限校驗。
整個前端工程總結下來,主要采用vue作為基礎視圖層框架,利用vue-router完成前端頁面路由的跳轉及各類訪問攔截功能,采用axios作為HTTP請求庫,同時利用vuex負責前端的全局狀態(tài)管理,采用iView作為界面基礎組件庫。
整個后臺單頁應用管理系統(tǒng)分為兩個工程:前端工程、后端工程,以API接口形式聯(lián)合前后端。其中前端工程的結構目錄如圖1所示:
圖1 前端工程結構
前端工程中,以components作為獨立頁面組件的存放目錄,使用pages作為系統(tǒng)頁面的存放目錄,所有涉及全局狀態(tài)管理的代碼存放在store目錄,涉及路由配置的代碼存放在router目錄下,通用的配置文件存放在common目錄。
通常,在進行store設計時,會將所有的action集中在action.js文件中,其中包含通過axios進行后臺api請求的代碼。在進行后臺單頁應用系統(tǒng)開發(fā)的時候,會頻繁跟后臺進行數(shù)據(jù)溝通,故考慮將action分成兩種類型,一種是不帶后臺API請求的,只是單純進行前端操作的,另外一直則是包含后臺API請求,跟后臺進行數(shù)據(jù)溝通。根據(jù)以往經(jīng)驗,大概2/3的action會涉及后臺API請求,故需要通過某種形式來減少重復的請求代碼。經(jīng)過對上述問題的思考,可以通過以下方式來減少系統(tǒng)的代碼量,同時保證請求統(tǒng)一性,尤其是請求異常的統(tǒng)一性處理:
在common/mutation-types.js中聲明所有的action的 name,如:export const BACKEND_LOGIN='BACKEND_LOGIN',其中定義了一個BACKEND_LOGIN的action。
在api/config.js中聲明所有涉及后臺API請求的action,并配置其請求地址等內(nèi)容,其格式如下:api[BACKEND_LOGIN]={url:'some_url',method:'get',pathinfo:true|false,noMutation:true|false}。其中,對象的鍵是mutation-type的常量,url表示請求的路徑,method表示請求的方式,pathinfo表示請求的url參數(shù)是否以pathinfo加入到請求的路徑上,noMutation表示是否忽略commit操作,該設計考慮是,某些ajax請求到的數(shù)據(jù),并非全局的狀態(tài),而是某些頁面級別的狀態(tài),故無需將請求到的相關數(shù)據(jù)進行全局狀態(tài)管理。
在api/helper.js中,聲明createAPIRequest方法,其接受三個參數(shù):commit,type,param,其中 commit是一個方法,進行mutation的commit操作,type是一個具體的 mutation-type(即 action的 name),param 是需要傳入的請求參數(shù)。通過傳入的type,可以從api/config.js中獲取該type的api請求配置。根據(jù)其配置,進行相應的后臺api請求,并根據(jù)配置項中的noMutation來鑒別是否需要對mutation進行commit操作,最終返回一個Promise實例,使得我們可以使用同步代碼的形式實現(xiàn)各類異步操作。核心代碼如下所示:
//api/config.js
importapifrom'./config'
export const createAPIRequest=function(commit,type,params={}){
letapiItem=api[type]
if(apiItem){
//requestconfig
letconfig={}
config.url=API_ROOT+apiItem.url
config.method=apiItem.method||'get'
config.headers={}
//以下省略處理params,根據(jù)請求method決定
......
//do request
return axios(config)
.then(function(response){
return response.data
},function({response}){
//以下省略網(wǎng)絡異常處理
......
})
.then(function(data){
//根據(jù)noMutation決定是否要進行commit
if(apiItem.noMutation===undefined||apiItem.no-Mutation===false){
commit(type,data)
}
return data
})
}else{
//none apidefined
return new Promise(function(resolve,reject){
reject(ERROR_NO_API_CONFIG)
})
}
}
最終,我們在store/actions.js中引入上述3個文件,并通過遍歷config,動態(tài)生成帶后臺api請求的action。相關代碼參考如下:
import{createAPIRequest}from'../api/helper'
importconfig from'../api/config'
//create apiactions,use mutions type as action name
letactions={}
Object.keys(config).forEach(function(type){actions[type]=function({commit},params={}){
return createAPIRequest(commit,type,params)
}
})
其中actions包含所有帶API請求的action,通過跟不帶API請求的action進行合并,得到這個store管理需要使用的action。
另外,在進行前端系統(tǒng)設計的時候,還需考慮整個系統(tǒng)的權限問題。在后臺應用中,主要是在中間件中根據(jù)RBAC來鑒別用戶權限[3]。在前端頁面中,主要分以下三個層面進行用戶權限的控制:
第一層,主要是對用戶入口的控制,表現(xiàn)為:根據(jù)用戶角色來獲取用戶所能訪問的菜單列表。
第二層,主要是對用戶訪問頁面的時候,進行權限鑒別。在router的全局鉤子中,根據(jù)用戶角色的權限,來鑒別用戶是否擁有訪問該頁面的權限,如果有權限,則next(),否則,直接重定向用戶到401頁面。
第三層,當用戶只有訪問頁面權限,但沒有進行操作權限時,通過封裝的hasPermission方法動態(tài)展示或隱藏相關操作入口,也可以根據(jù)實際請求展示或隱藏相關數(shù)據(jù)列。
通過前后端分離,來保證前后端發(fā)布的獨立性。整個應用的請求反饋邏輯如圖2所示:
圖2 部署結構
針對前端工程,利用webpack打包來生成生成環(huán)境下的前端代碼。針對后端工程,則需根據(jù)特定的開發(fā)環(huán)境,打包相應的生產(chǎn)環(huán)境應用。當進行前后端分離發(fā)布,也經(jīng)常會遇到跨域的問題,為減少代碼層面的修改動作,可利用Nginx進行反向代理,將兩個工程的放到一個域名下去部署,從而解決跨域問題。以下是以PHP應用為樣例的Nginx配置文件,其主要思想是將對前端頁面的請求反向代理到前端靜態(tài)頁面,由前端vue-router進行頁面路由控制;將所有對QPI的訪問反向代理到后臺PHP應用。關鍵配置如下:
server{
listen 91;
set$root/usr/share/nginx/admin/public;
location~.*.(gif|jpg|jpeg|bmp|png|ico|txt|js|css)$
{
root$root;
}
location/{
root$root;
index index.html;
if( -f$request_filename){
break;
}
if( !-e$request_filename){rewrite^(.*)$/index.php/$1 last;break;
}
}
location/api{
root$root;
index index.htmlindex.php;
if( -f$request_filename){
break;
}
if( !-e$request_filename){rewrite^/api/(.*)$/api/index.php/$1 last;break;
}
}
###vuejs前端URL前綴
location/system{
index index.html;
try_files$uri$uri/@backendApp;
}
###將所有對vuejs URL的訪問重定向到index.html文件
location@backendApp{
rewrite^.*$/index.htmllast;
}
location ~.+.php($|/){
include fastcgi_params;
fastcgi_pass unix:/run/php-fpm/php7.0-fpm.sock;
fastcgi_split_path_info^((?U).+.php)(/?.+)$;
fastcgi_param PATH_INFO$fastcgi_path_info;
fastcgi_param PATH_TRANSLATED$root$fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME$root$fastcgi_script_name;
}
}
本文主要從組件的選型、系統(tǒng)結構設計以及部署實現(xiàn)三個方面,詳細描述基于Vue.JS進行后臺單頁應用系統(tǒng)的設計工作。對相關組件的封裝,并以配置的形式主導功能的開發(fā),在一定程度上減少后臺系統(tǒng)的前端開發(fā)工作量。利用Vue.JS的雙向綁定特性,高效地將數(shù)據(jù)反映到頁面模型上,同時Vue.JS更高效地處理頁面DOM操作,提升后臺應用的性能。
[1]岳曉瑞,陳繼華.HTML5環(huán)境下PJAX快速瀏覽技術實踐[J].廣東通信技術,2013.
[2]鄧森泉,楊海波.Promise方式實現(xiàn)Node.js應用的實踐[J].計算機系統(tǒng)應用,2017.
[3]喬穎,須德.一種基于角色訪問控制(RBAC)的新模型及其實現(xiàn)機制[J].計算機研究與發(fā)展,2000.
曠志光(1991-),男,江西吉安人,初級工程師,本科,研究方向為軟件工程、云計算、數(shù)據(jù)挖掘
紀婷婷(1989-),女,江蘇徐州人,中級工程師,碩士研究生,研究方向為大數(shù)據(jù)、數(shù)據(jù)挖掘、可視化
吳小麗(1988-),女,江蘇南通人,初級工程師,本科,研究方向為數(shù)據(jù)庫、軟件工程
Research and Practice of Single Page Application For Management System Based on Vue.js
KUANG Zhi-guang,JITing-ting,WU Xiao-li
(ShanghaiDevelopment Center ofComputer Software Technology,Shanghai 201112)
By studying Vue.js,router,global state,axios and other components,achieves a single-page application for managementsystem.Realizes the actions which contain APIrequest,justencapsulates the actions and defines the relevantrequestinformation in the configuration file.Also,it will minimize the amount of system code,ensure the unity and robustness of the system.The front page controls the entry of the page files according to the menu policy,and the button levelpermission check according to the page permission controlpolicy.
Vue.js;MVVM;Single Page Application;RBAC
上海市軟件技術創(chuàng)新服務平臺(No.17DZ2292100)
1007-1423(2017)30-0051-05
10.3969/j.issn.1007-1423.2017.30.011
2017-08-22
2017-10-15