0%

vue项目结构设计

5e2160d3a0a60530

书接上回,这次我们来聊一下,我之前项目中关于 vue 的架构实践,也欢迎大佬们指出不足。

我们先看一下整体的目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
.
├── dev-tools // 开发工具,比如自定义的 stylelint 规则之类的
│   └── ...  
├── dist
│   └── ...  
├── public
│   └── index.html
├── src
│   ├── api // 抽取出API请求,所有 API
│   │   └── ...
│   ├── assets // 静态文件目录(图片、字体)
│   │   └── ...
│   ├── components // 公共组件
│   │   └── ...
│   ├── constants // 项目中的常量
│   │   └── ...
│   ├── lang // 多语言文件
│   │   ├── en-US.json
│   │   └── zh-CN.json
│   ├── lib // 第三方 js 代码
│   │   └── ...
│   ├── layouts // 布局层相关的 vue 组件
│   │   ├── BasicLayout.vue // 基础 layout vue 组件
│   │   ├── BlankLayout.vue // 空白 layout vue 组件
│   │   └── index.js
│   ├── router
│   │   └── index.js
│   ├── store
│   │   ├── actions.js // 根级别的 action
│   │   ├── getters.js // 根级别的 getter
│   │   ├── index.js // 组装模块并导出 store 的地方
│   │   ├── mutations.js // 根级别的 mutation
│   │   ├── state.js // 根级别的 state
│   │   └── modules
│   │      └── A // 模块级别的 store
│   │      │ ├── actions.js
│   │      │ ├── getters.js
│   │      │ ├── index.js
│   │      │ ├── mutations.js
│   │      │ └── state.js
│   │   └── ...
│   ├── styles
│   │   ├── app.less // 通用的less
│   │   ├── mixin.less // 通用的mixin
│   │   ├── variables.less // 通用的变量
│   │   └── ...
│   ├── utils
│   │   ├── http // 封装 axios
│   │   │   ├── axios.js
│   │   │   └── http.js
│   │   └── ...
│   ├── views // 页面组件
│   │   └── ...
│ ├── App.vue
│   ├── i18n.js
│   ├── initData.js
│   └── main.js
├── tests // 测试
│   ├── e2e   // e2e 测试
│   │ └── ...  
│   └── unit // 单元测试
│   └── ...  
├── .browserslistrc
├── .commitlintrc.js // commit 规范校验
├── .editorconfig // 编辑器配置文件
├── .env.development // 开发环境的环境变量
├── .env.production // 生产环境的环境变量
├── .eslintignore // eslint 的忽略规则
├── .eslintrc.js // eslint 的配置
├── .gitignore // git 的忽略规则
├── .prettierrc // prettier 的配置
├── .stylelintrc.json // stylelint 的配置
├── babel.config.js // 开发环境的环境变量
├── Dockerfile // 构建 Docker 镜像的文本文件
├── docker-compose.yml // Docker compose 配置
├── README.md
├── build.sh // Docker 镜像中执行的构件脚本
├── default.conf // ngnix 配置
├── jest.config.js // jest 配置
├── jsconfig.json // VSCode js 配置
├── package-lock.json
├── package.json
├── start-nginx.sh // docker 镜像中运行 nginx 的脚本
└── vue.config.js // vue 配置文件

首先对于视图层分成了三块:componentslayoutsviews

components 为公共组件,主要包括原子组件(比如 Button、Modal等)和业务公用组件,从深度上,此处的目录层级结构应该尽量扁平,不应该有很深的层级;

layouts 主要用来负责基本的布局,每个页面都会是 layout 组件的子集,BasicLayout是页面基本布局,会是用得最多的布局,BlankLayout是空白页面,方便处理一些特殊页面;

views 主要是路由页面组件,。

router中是页面路由,最上层的路由的 Component 会是layout 中的 Component,其 children 则是 views 中的 Component :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[
{
path: '/',
component: BasicLayout,
redirect: '/homepage',
children: [
{
path: 'homepage',
name: 'homepage',
component: () => import('../views/homepage/Homepage')
},
]
},
{
path: '/404',
name: '404',
component: () => import('../views/exception/404')
},
{
path: '/500',
name: '500',
component: () => import('../views/exception/500')
},
{
path: '*',
redirect: '/404',
hidden: true
}
]

styles 下会是所有全局的样式,比如全局的变量、mixin 以及修改的ant-design的样式等。

所有的接口都会放在Api目录下,做统一管理。utils下面的http 目录是对 axios 的二次封装,集成了拦截器、统一错误处理、 token 处理等功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
const handleError = (message) => {
Vue.prototype.$warning({
title: 'Warning',
content: message
})
store.commit('setGlobalLoading', {
loading: false
})
}

const makeRequest = axios.create({
baseURL: host.BACK_END_URL,
timeout: 60000
})

const requestRetryInfo = {}
const MaxRetry = 5

const handleRefreshToken = config => {
const url = config.url
if (!requestRetryInfo[url]) {
requestRetryInfo[url] = 0
}
if (requestRetryInfo[url] > MaxRetry) {
requestRetryInfo[url] = 0
return
}
requestRetryInfo[url]++

return getNewToken()
.then(res => {
const { token } = res
setToken(res.token)
config.headers.Authorization = `Bearer ${token}`
config.baseURL = host.BACK_END_URL
return makeRequest(config)
})
.catch(() => {
clearToken()
})
}

const error = error => {
let parsedError = { ...error }
const response = _.get(parsedError, 'response')
const url = _.get(parsedError, 'response.config.url') || _.get(parsedError, 'config.url')
if (_.isEmpty(response)) {
parsedError = {
...error,
response: {
data: { message: i18n.t('timeout') },
status: 500
}
}
}
const errorCode = _.get(parsedError, 'response.status')
const message = _.get(parsedError, 'response.data.message')
const config = error.config
if (errorCode === 401) {
return handleRefreshToken(config)
}
if (!NOT_SHOW_ERROR_URL.some(value => url.includes(value))) {
switch (errorCode) {
case 403:
...
handleError(message)
break
case 406:
handleError(message)
clearToken()
break
default:
handleError(message)
}
}
return Promise.reject(parsedError)
}

// request interceptor
makeRequest.interceptors.request.use(
config => {
const token = getToken()
if (token && !config.url.includes('token')) {
config.headers.Authorization = `Bearer ${localStorage.getItem('TOKEN')}`
}
return config
},
error =>
Promise.reject(error)
)

// response interceptor
makeRequest.interceptors.response.use(response => {
const token = _.get(response, 'headers.authorization')
if (token) {
setToken(token)
}
return response.data
}, error)

store的管理按照modules进行拆分,根级别的只放类似globalLoading这种状态管理,其他的状态管理按照业务拆分成 modules。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// root store
export default new Vuex.Store({
state,
getters,
mutations,
actions,
modules: {
homepage
}
})

// homepage
export default {
namespaced: true,
state,
getters,
actions,
mutations
}

constants主要存放常量,用于 store 的常量放在单独文件内,其他常量的管理也按业务进行拆分。

assets主要存放代码以外的静态资源,比如图片、视频等,资源需要按业务进行分类,方便管理这些静态资源。

欢迎关注我的其它发布渠道