出处:掘金
原作者:金泽宸
模块化 + 领域分层 + 插件化目录,是大型项目的“骨架系统”
写在前面
项目越大,目录越乱,是所有前端架构师绕不过去的坑。我们常常见到这样的代码结构:
pages/
├── page1.vue
├── page2.vue
├── page3.vue
components/
├── common/
├── header.vue
├── dialog.vue
utils/
api/
很快你就发现:
- 哪些是用户相关页面?哪些是订单?
- 哪些组件是通用的?哪些是业务定制的?
- utils 的函数是哪个模块的?是否重复?
今天这一篇,我们通过实战项目演示:如何设计清晰、高可维护、方便多人协作的大型项目结构
目录结构设计常见 3 种方式
模式 | 特点 | 适合场景 |
---|---|---|
按功能划分(Flat) | 易于上手、轻量 | 小项目、单人开发 |
按页面划分(Views) | 页面结构清晰 | 中型项目、界面主导 |
按领域划分(Domain Oriented)✅ | 高内聚、低耦合 | 大型项目、多人协作、可平台化发展 |
推荐目录结构(面向领域 + 插件化)
src/
├── modules/ # 各个业务模块(重点)
│ ├── user/
│ │ ├── views/ # 页面(profile、login等)
│ │ ├── components/ # 业务组件
│ │ ├── api.ts # 模块内接口
│ │ ├── store.ts # 模块状态
│ │ └── index.ts # 模块入口
│ ├── order/
│ └── dashboard/
├── shared/ # 通用能力封装
│ ├── components/ # 通用组件(弹窗、表单)
│ ├── hooks/ # 通用 hooks(useDialog、useTable)
│ ├── utils/ # 工具函数
│ └── constants/ # 枚举/状态码
├── router/ # 动态路由加载逻辑
├── store/ # 全局状态,如用户/主题
├── assets/ # 静态资源
├── services/ # 和后端联动的接口封装
├── App.vue
└── main.ts
实战演示:一个“用户模块”的结构
modules/user/
├── views/
│ ├── Login.vue
│ ├── Profile.vue
├── components/
│ ├── UserAvatar.vue
│ ├── UserInfoCard.vue
├── api.ts
├── store.ts
└── index.ts
模块 API:
// modules/user/api.ts
import request from '@/shared/utils/request'
export const getUserInfo = () => request.get('/user/info')
export const login = (data) => request.post('/user/login', data)
好处:
- 用户相关代码全部集中,清晰明确
- 删除该模块直接整个目录删掉,低耦合
- 可以被独立打包发布(用于微前端、插件平台等)
模块化加载 + 路由动态挂载
步骤1:在模块中暴露路由配置:
// modules/user/index.ts
export default {
routes: [
{
path: '/user/login',
component: () => import('./views/Login.vue'),
},
{
path: '/user/profile',
component: () => import('./views/Profile.vue'),
},
],
}
步骤2:在全局 router 中自动加载所有模块路由:
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
const modules = import.meta.glob('../modules/**/index.ts', { eager: true })
const routes = Object.values(modules)
.map((m: any) => m.default.routes)
.flat()
export const router = createRouter({
history: createWebHistory(),
routes,
})
优点:
- 每个模块自管理路由,无需集中维护 router 文件
- 支持按需加载,减少首次包体积
- 模块即插件,可插拔
组件设计的目录策略
目录位置 | 用途 | 示例组件 |
---|---|---|
shared/components/ | 通用组件 | Dialog、Pagination、DatePicker |
modules/user/components/ | 业务组件 | UserAvatar、UserStatsCard |
pages/组件目录 | 页面私有组件 | TableColumnRenderer.vue |
建议:
- 通用组件要求文档/测试/可扩展性高
- 业务组件建议与模块绑定
- 页面组件不做复用,随页面走
复用策略:避免重复造轮子
- 通用模块统一收敛(Dialog、Form、Upload)
- 模块复用提炼成插件库
- 通过 props + slot 提高复用性
<!-- 通用组件 Dialog -->
<MyDialog :title="title" @confirm="handleConfirm">
<template #default>
<p>{{ description }}</p>
</template>
</MyDialog>
<!-- 业务中使用 -->
<MyDialog title="删除用户" @confirm="deleteUser">
<template #default>
<p>是否确认删除该用户?</p>
</template>
</MyDialog>