vue项目一个页面接口很多怎么写_vue项目的一些最佳实践提炼和经验总结

项目组织结构

ajax数据请求的封装和api接口的模块化管理

第三方库按需加载

利用less的深度选择器优雅覆盖当前页面UI库组件的样式

webpack实时打包进度

vue组件中选项的顺序

路由的懒加载

路由模块拆分化管理

项目组织结构

清晰的项目结构能让别人开发进来更容易理解,当然,每个人都有一定的代码风格习惯。但基于vue开发框架的项目,vue-cli脚手架搭建的项目组织结构大同小异。同时,预想到后面的需求变更及功能增加进展得更有效率,下面截图是我觉得比较好的项目组织结构:

这个截图只是针对个人觉得比较通用的vue工程结构,不过这个结构要根据具体的项目情况调整,不必为了模块化而模块化。模块化的优势就是体现在项目业务比较复杂的情况,如果项目业务逻辑并不复杂,可以适当的删减部分模块或文件。

相关说明:

assets: 存放图片、UI设计的图标文件

componets:自研的业务型及通用型组件

router:项目的路由管理模块

store:基于vuex的状态管理容器,api存放各模块的数据请求,modules存放将store分割成模块(module),按官网的说法,每个模块应该拥有自己的 state、mutation、action、getter,主要是解决应用的所有状态如果全部集中到一个比较大的store对象,当应用变得非常复杂时,store 对象就有可能变得相当臃肿而难以维护。

例子:

其中的一个模块configManage.js

import {

configManageService

} from"../api/index"

//state

const state ={

accountMenuList:[]

}//getters

const getters ={//菜单

menuTree: state =>{returnstate.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;

}

}

exportdefault{

state,

getters,

actions,

mutations

}

index.js,统一出口,导出全部的store模块

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= newVuex.Store({

modules: {

report,

index

},

strict: debug,//按照官网建议,改变state的状态只能通过getter

plugins: debug ?[createLogger()] : []

})

style:

存放重写UI库的样式和不同组件公共样式文件

util:

存放用es6封装的工具类,http请求类,配置类、校验类、事件类等

views:

存放各路由模块页面

static:

存放全局配置文件,环境域名等

iconfont:

存放字体图标文件

ajax数据请求的封装和api接口的模块化管理

基于vue的项目,与后台请求数据我们通常使用的是axios,它是基于promise的http库,其提供的优秀的特性被广泛运用在项目当中,官方已推荐使用axios,放弃原有的vue-resource。

1、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= newAjaxRequestModel(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);

});

});

};

2、在请求的拦截中,可以携带用于接口身份验证的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";

returnconfig;

},function(error) {returnPromise.reject(error);

}

);

3、在响应的拦截中,可以进行根据各种状态码来进行错误的统一处理等

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");

}

};

4、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);returnres;

},

async removeMenu(model) {

model.data={ ...model };

model.url= API_CONTEXT + "category/removeCategory";

let res=await http.ajaxService(model);returnres;

}

}

exportdefault 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;

}

5、如果后期维护需要修改的接口,我们就直接在api.js中找到对应的修改就好了,而不用去每一个页面查找我们的接口然后再修改会很麻烦,如果修改的量比较大,难免会自测不充分产生bug,直接gg。还有就是如果直接在我们的业务代码修改接口,一不小心还容易动到我们的业务代码造成不必要的麻烦

6、处理接口域名、端口有多个情况

//无需前端打包,运维环境快速修改配置,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';

利用less的深度选择器优雅覆盖当前页面UI库组件的样式

vue页面组件的样式基本是写在中,增加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,可以用<<

webpack实时打包进度

在项目用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组件中写选项的顺序

这里纯属个人观点,可能有些小伙伴用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";

exportdefault{

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';

exportdefault[

{ path:'reportManage/index', name: 'reportManage', component: reportManage },

{ path:'reportManage/reportPreview', name: 'reportPreview', component: reportPreview }

];

初始化页面的加载耗时:

优化后:

import reportManage from '@/views/reportManage/index';

exportdefault[

{ 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 = newRouter({

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';

exportdefault[

{ path:'/index/home', name: 'home', component: home }

];

这里把首页的路由放在一个数组里,然后导出去,有router.js统一引入,并实例化当前路由

未完待续......

你可能感兴趣的:(vue项目一个页面接口很多怎么写_vue项目的一些最佳实践提炼和经验总结)