Vue+webpack项目配置便于维护的目录结构教程详解
新建项目的时候创建合理的目录结构便于后期的维护是很重要
环境:vue、webpack
目录结构:
项目子目录结构
子目录结构都差不多,主要目录是在src下面操作
src目录结构
src/common 目录
主要用来存放公共的文件
src/components
主要用来存放公共的组件
src/config
用来存放配置文件,文件目录如下
src/config/index.js 配置目录入口文件
import api from './website' // 当前平台 export const host_platform = 'web' // 当前环境 export const node_env = process.env.node_env || 'prod' // 是否开启监控 export const monitor_enable = true // 路由默认配置 export const router_default_config = { // mode: 'history', waitfordata: true, transitiononload: true } // axios 默认配置 export const axios_default_config = { timeout: 20000, maxcontentlength: 2000, headers: {} } // vuex 默认配置 export const vuex_default_config = { strict: process.env.node_env !== 'production' } // api 默认配置 export const api_default_config = { baseurl: api, // 图标地址 imgurl: `${api}/api/system/icon.do?name=`, // 菜单图标地址 menuimgurl: `${api}/`, dicomurl: `${api}/testdicom/`, // 请求参数格式 json/form-data isjson: true, // 请求加载效果, 支持element-ui所有参数配置 loading: { text: '加载中' }, // 是否开启mock mock: false, // 是否开启debug debug: false, // 定义全局变量 ippid: 'test' } export const console_request_enable = true // 开启请求参数打印 export const console_response_enable = false // 开启响应参数打印 export const console_router_enable = false // 打印路由信息 export const console_monitor_enable = true // 监控记录打印
src/config/website.js 动态配置ip文件
/** * 动态匹配api接口地址 */ const website = [ { web: 'localhost:9000', api: '//192.168.0.170:8080/xhhms', env: 'dev' }, { web: '127.0.0.1:8000', api: '//192.168.0.149:8080/xhhms', env: 'dev' } ] let matchapi = website.filter(item => new regexp(item.web).test(location.href)) if (matchapi.length > 1) { console.error(`${location.href}: 该站点映射了多个api地址${matchapi.map(item => item.api).join(',')},默认选取第一个匹配项`) } export default matchapi[0].api
src/config/interceptors目录
拦截器配置
src/config/interceptors/axios.js
import router from 'plugins/router' import { console_request_enable, console_response_enable } from '../index.js' import { toast, indicator } from 'mint-ui' import store from 'store' import qs from 'qs' /** * 请求拦截器(成功) * @param {object} request 请求对象 * @return {object} request 处理后的请求对象 */ export function requestsuccessfunc(request) { console_request_enable && console.info('requestinterceptorfunc', `url: ${request.url}`, request) // 自定义请求拦截逻辑,可以处理权限,请求发送监控等 // console.log(request.url) // if (localstorage.getitem('token') === null && request.url.indexof('login') === -1) { // console.log('[*] 当前用户没有登录!!') // router.push('/login') // return false // } // 登录token携带 request.headers['x-auth-token'] = localstorage.getitem('token') // 兼容性写法,如果request里边没得site_code 就用全局site_code let publicparams = { orgcode: sessionstorage.getitem('orgcode'), menuid: sessionstorage.getitem('currentmenuid') } /** * @author wucheshi * @time 2018-08-13 * @description 需求变动,网站code从本地sitecodelist 这个字段来 */ let sitecodelist = sessionstorage.getitem('sitecodelist') // !request.data.site_code && (publicparams = object.assign({ site_code: store.state.currentsite.code }, publicparams)) !request.data.site_code && !request.nositecode && (publicparams = object.assign({ site_code: sitecodelist }, publicparams)) /** * @author wucheshi * @time 2018-08-13 * @description 单表操作接口不需要传递sitecode */ // 兼容单表操作传递site_code // if (request.data.condition && !request.nositecode) { // console.log(sitecodelist, 11111) // if (request.data.condition.findindex(item => item.name === 'site_code') === -1) { // request.data.condition.push({ name: 'site_code', value: sitecodelist }) // } else { // request.data.condition.find(item => item.name === 'site_code').value = sitecodelist // } // } let newdata // 判断是否是formdata类型 if (object.prototype.tostring.call(request.data) === '[object formdata]') { // 合并formdata格式公共参数 object.keys(publicparams).foreach(key => { request.data.append(key, publicparams[key]) }) newdata = request.data } else { // 合并公共参数 newdata = object.assign(request.data, publicparams) // 判断是否采用json格式提交参数 !request.isjson && (newdata = qs.stringify(newdata)) } // 不同提交参数方式给不同的字段赋值 if (request.method.touppercase() === 'post') { request.data = newdata } else if (request.method.touppercase() === 'get') { request.params = newdata } // 加载效果 request.loading && indicator.open(request.loading) // 输出请求数据 console_request_enable && console.info(`%c 请求接口地址:${request.url} 请求接口名称:${request.desc} 请求参数json: ${json.stringify(request.data, '', 2)} `, 'color: #f60') return request } /** * 请求拦截器(失败) * @param {object} requesterror 请求报错对象 * @return {object} 返回promise对象 */ export function requestfailfunc(requesterror) { // 自定义发送请求失败逻辑,断网,请求发送监控等 return promise.reject(requesterror) } // 你就是个sx /** * 响应拦截器(成功) * @param {object} responseobj 响应对象 */ export function responsesuccessfunc(responseobj) { // 自定义响应成功逻辑,全局拦截接口,根据不同业务做不同处理,响应成功监控等 // console.log(typeof (responseobj.data)) // // 判断string是否包含 java字段 说明error // if (typeof (responseobj.data) === 'string' || responseobj.data.indexof('java') !== -1) { // console.log('[*] token错误') // this.$router.push('/login') // } // 加载效果 indicator.close() // 响应对象 let resdata = typeof responseobj.data === 'object' ? responseobj.data : json.parse(responseobj.data) let { status, message } = resdata // 输出响应体 console_response_enable && console.info(responseobj) // 输出返回json数据 console_response_enable && console.info(`%c 响应接口地址: ${responseobj.config.url} 响应接口描述: ${responseobj.config.desc} 响应数据json: ${json.stringify(resdata, '', 2)} `, 'color: blue') // 自定义处理业务逻辑 if (responseobj.config.customerrorhandle) { return resdata } // 统一逻辑处理 switch (+status) { case 0: // 常规错误 toast(message) break case 1: // 如果业务成功,直接进成功回调 return resdata case 401: // 登录失效 store.commit('delete_user_info') router.push({ path: '/login', redirect: router.app._route.fullpath }) toast(message) break default: // 业务中还会有一些特殊 code 逻辑,我们可以在这里做统一处理,也可以下方它们到业务层 // !responseobj.config.noshowdefaulterror && global.vbus.$emit('global.$dialog.show', resdata.msg); return promise.reject(resdata) } } /** * 响应拦截器(失败) * @param {object} responseerror 响应报错对象 * @return {object} 返回promise对象 */ export function responsefailfunc(responseerror) { // 响应失败,可根据 responseerror.message 和 responseerror.response.status 来做监控处理 // ... // 加载效果 indicator.close() // 错误码处理 // console.log(responseerror.response) if (typeof (responseerror.response) === 'undefined') { return false } switch (responseerror.response.status) { case 401: console.error('401错误') store.commit('delete_user_info') router.push({ path: '/login', redirect: router.app._route.fullpath }) store.state.user.username && toast('登录超时') break case 403: console.error('403错误') router.push({ path: '/403' }) break case 500: console.error('500错误') router.push({ path: '/500' }) break } return promise.reject(responseerror) }
src/config/interceptors/index.js
import {requestsuccessfunc, requestfailfunc, responsesuccessfunc, responsefailfunc} from './axios' import {routerbeforeeachfunc} from './router' export default { requestsuccessfunc, requestfailfunc, responsesuccessfunc, responsefailfunc, routerbeforeeachfunc }
src/config/interceptors/router.js
/** * 路由beforeach拦截器 */ import {console_router_enable} from '../index' export function routerbeforeeachfunc (to, from, next) { // 打印路由数据 console_router_enable && console.info(`%c 路由to: fullpath: ${to.fullpath}, query: ${json.stringify(to.query, '', 2)}, meta: ${json.stringify(to.meta, '', 2)} 路由from: fullpath: ${from.fullpath} `, 'color: green;font-weight: bold;') // 登录状态验证 if (to.meta.requirelogin) { (localstorage.getitem('token')) ? next() : next({path: '/login', query: { redirect: to.fullpath }}) return } // 路由重定向 // if (to.query.route) { // let newquery = object.assign({}, to.query) // delete newquery.route // next({ // path: `${to.query.route.indexof('/') === 0 ? '' : '/'}${to.query.route}`, // query: newquery // }) // return // } // console.log(to, from) // 防止死循环 if (to.fullpath === from.fullpath) return // 404错误 if (!to.name) { next('/404') return } next() }
src/locale目录
国际化配置,这个百度一下就行
src/mixin目录
引入配置文件,定义部分全局变量,名字自己定义
src/mixin/index.js
import vue from 'vue' import { api_default_config } from 'config' vue.mixin({ computed: { // 图片根地址 imgurl () { return api_default_config.imgurl }, baseurl () { return api_default_config.baseurl }, ippid () { return api_default_config.ippid }, dicomurl () { return api_default_config.dicomurl } } })
src/pages目录
主要的页面文件,目录结构主要按照层次结构来分。
ex:该页面主要跟医生相关,主要包含云搜索(cloud)、个人中心(mycenter)、工作中心(workcenter)、搜索(serach)、同理子层级也同样区分、目录结构如下
至于公共页面可以放在common文件目录下,也可以摆在文件夹外面。
src/plugins目录
也是配置文件目录
src/plugins/api.js
import axios from './axios' import _pick from 'lodash/pick' import _assign from 'lodash/assign' import _isempty from 'lodash/isempty' import { assert } from 'utils/tools' import { api_default_config } from 'config' import api_config from 'service/api' class makeapi { constructor (options) { this.api = {} this.options = object.assign({}, options) this.apibuilder(options) } apibuilder ({ config = {} }) { object.keys(config).map(namespace => { this._apisinglebuilder({ namespace, config: config[namespace] }) }) } _apisinglebuilder ({ namespace, config = {} }) { config.foreach(api => { const { methodsname, desc, params, method, path, mockpath } = api let { mock, mockbaseurl, baseurl, debug, isjson, loading } = this.options let url = mock ? (mockbaseurl + mockpath) : (baseurl + path) debug && assert(methodsname, `${url} :接口methodsname属性不能为空`) debug && assert(url.indexof('/') === 0, `${url} :接口路径path,首字符应为/`) object.defineproperty(this.api, methodsname, { value (outerparams, outeroptions) { let allowtparam = (outeroptions && outeroptions.allowparams) || {} let _data = (outeroptions && outeroptions.isformdata) ? outerparams : _isempty(outerparams) ? params : _pick(_assign({}, params, outerparams), object.keys(object.assign(params, allowtparam))) return axios(_assign({ url, desc, method, isjson, loading }, outeroptions, { data: _data })) } }) }) } } export default new makeapi({ config: api_config, ...api_default_config })['api']
src/plugins/axios.js
import axios from 'axios' import {axios_default_config} from 'config/index' import {requestsuccessfunc, requestfailfunc, responsesuccessfunc, responsefailfunc} from 'config/interceptors/axios' let axiosinstance = {} axiosinstance = axios.create(axios_default_config) // 注入请求拦截 axiosinstance .interceptors.request.use(requestsuccessfunc, requestfailfunc) // 注入失败拦截 axiosinstance .interceptors.response.use(responsesuccessfunc, responsefailfunc) export default axiosinstance
src/plugins/inject.js
import axios from './axios' import api from './api' // global.ajax = axios export default { install: (vue, options) => { vue.prototype.$api = api vue.prototype.$ajax = axios // 需要挂载的都放在这里 } }
src/plugins/router.js
import vue from 'vue' import router from 'vue-router' import routes from 'routes' import {router_default_config} from 'config/index' import {routerbeforeeachfunc} from 'config/interceptors/router' vue.use(router) // 注入默认配置和路由表 let routerinstance = new router({ ...router_default_config, routes: routes }) // 注入拦截器 routerinstance.beforeeach(routerbeforeeachfunc) export default routerinstance
src/router目录
路由配置文件目录,同理按照页面的层次结构来,结构如下
我们来看src/router/index.js 和 src/common/index.js 即可
src/common/index.js
const routes = [ { path: '/login', name: 'login', component: () => import('pages/login'), meta: { require: true, title: '登录' } }, { path: '/register', name: 'register', component: () => import('pages/register'), meta: { require: true, title: '注册' } }, { path: '/404', name: '404', component: () => import('pages/error/404.vue'), meta: { require: true, title: '404' } }, { path: '/500', name: '500', component: () => import('pages/error/500.vue'), meta: { require: true, title: '500' } }, { path: '/403', name: '403', component: () => import('pages/error/403.vue'), meta: { require: true, title: '403' } } ] export default routes
src/router/index.js
import common from './common' import doctor from './doctor' import patient from './patient' import test from './test' const route = [ { path: '/', redirect: '/login' }, { path: '/checkrecord', name: 'checkrecord', component: () => import('pages/checkrecord.vue'), meta: { require: true, title: '检查记录' } }, { path: '/report', name: 'report', component: () => import('pages/report.vue'), meta: { require: true, title: '心电图报告' } }, { path: '/opinion', name: 'opinion', component: () => import('pages/opinion.vue'), meta: { require: true, title: '意见' } }, { path: '/bind', name: 'bind', component: () => import('pages/bind.vue'), meta: { require: true, title: '绑定' } }, ...common, ...doctor, ...patient, ...test ] export default route
把所有的路由文件挂载进去。
src/service 目录
接口配置文件目录,根据页面来定义文件
同理我们只看src/service/api/index.js 和src/service/api/login.js、src/pages/login/index.vue以及页面如何调用接口即可。
src/service/api/login.js
先定义好login接口
const login = [ { methodsname: 'loginbyphone', // 方法名 method: 'post', desc: '登录', path: '/rest/app/login', // 接口路径 mockpath: '/rest/app/login', params: { // 参数配置 这里需要注意,只有配置的这些参数才能通过接口,所以需要传递的参数都要在这里配置 phone: 1, password: 2, code: 3, codeid: '', clientid: '' } }, { methodsname: 'login', method: 'post', desc: '登录', path: '/rest/interfaceslogincontroller/login', mockpath: '/rest/interfaceslogincontroller/login', params: { username: 1, password: 2, code: 3, codeid: '', clientid: '' } }, { methodsname: 'checkcode', method: 'post', desc: '验证提取码', path: '/rest/app/medical/checksharecode', mockpath: '/rest/app/medical/checksharecode', params: { sharecode: '', id: '' } }, { methodsname: 'getcode', method: 'post', desc: '获取验证码', path: '/rest/interrandomcodecontroller/gereraterandomcode', mockpath: '', params: { } }, { methodsname: 'getpublickey', method: 'post', desc: '获取公钥', path: '/rest/interrandomcodecontroller/clientidandpublickey', mockpath: '', params: { } } ] export default login
src/service/api/index.js
挂载所有定义的接口文件
import login from './login' import workcenter from './workcenter' import detail from './detail' import register from './register' import doctorpc from './doctorpc' import patientpc from './patientpc' import checklist from './checklist' export default { login, workcenter, detail, register, doctorpc, patientpc, checklist }
src/pages/login/index.vue
this.$api.login( params).then(data => { }) // 这样调用登陆接口 this.$api.方法名(参数).then(res=>{}) // 方法名定义不能重名
其它目录
这些目录还是包含很多东西,用户的信息保存,主体,工具函数这些,就不多说了。
对于项目的维护还是需要看重,后期维护方便也便于管理。
总结
以上所述是小编给大家带来的vue+webpack项目配置便于维护的目录结构的相关知识,希望对大家有所帮助
上一篇: 如何来布局网站内部长尾关键词