Vue3 项目目录结构规范:按业务域划分,新人快速上手|项目规范篇

【Vue3】+【中后台前端开发】:从【按业务域划分目录结构】到【落地实操】,彻底搞懂【可维护前端代码】的最佳写法,避开目录混乱、维护困难高频坑!

同学们好,我是 Eugene(尤金),一名多年中后台前端开发工程师。

(Eugene 发音 /juːˈdʒiːn/,大家怎么顺口怎么叫就好)

很多前端开发者都会遇到一个瓶颈:

代码能跑,但不够规范;功能能实现,但维护起来特别痛苦;

一个人写没问题,一到团队协作就各种混乱、踩坑、返工。

想写出干净、优雅、可维护的专业代码,

靠的不是天赋,而是体系化的规范 + 真实实战经验

这一系列《前端规范实战》,我会用大白话 + 真实业务场景

不讲玄学、不堆理论,只分享能直接落地的规范、标准与避坑指南。

帮你从「会写代码」真正升级为「会写优质、可维护、团队级别的代码」。

一、开篇:这篇文章能帮你解决什么问题

工作里常见几类问题:

  • 新人接手项目,不知道功能写在哪
  • 页面一多,文件到处散落,难以维护
  • 团队各写各的,命名、分层不统一
  • 改需求时,要翻很多目录才能定位到相关代码

这篇文章会围绕 Vue3 项目按业务域划分的目录结构规范,讲清楚:

  • 为什么要这样分
  • 每个目录该放什么、不该放什么
  • 怎么从零搭建一个规范的目录
  • 常见坑点如何规避

目标是:看完就能照着做,新人也能快速上手。

二、常见的"反面教材"

2.1 所有组件堆在 components

bash 复制代码
src/
├── components/
│   ├── UserCard.vue      # 用户相关
│   ├── OrderList.vue     # 订单相关
│   ├── ProductDetail.vue # 商品相关
│   ├── LoginForm.vue     # 登录相关
│   ├── Header.vue
│   ├── Footer.vue
│   └── ...(几十个组件混在一起)

问题:业务一多,components 会变成大杂烩,找文件、理清依赖都很费劲。

2.2 只按技术类型划分

bash 复制代码
src/
├── views/      # 全是页面
├── components/ # 全是组件
├── api/        # 全是接口
├── utils/      # 全是工具函数

问题:和业务脱节。改"用户中心"要同时改 viewscomponentsapi,心智负担大。

2.3 嵌套过深

bash 复制代码
src/
├── modules/
│   └── user/
│       └── profile/
│           └── settings/
│               └── security/
│                   └── SecurityForm.vue  # 层级太深

问题:导入路径过长,易出错,也不利于理解整体结构。

三、核心思路:按业务域划分

3.1 什么是"业务域"

业务域 = 产品里的一块独立功能/业务模块。例如:

  • 用户:登录、注册、个人中心、设置
  • 商品:列表、详情、搜索
  • 订单:下单、列表、详情
  • 营销:活动、优惠券

每个域包含:页面、组件、接口、类型、工具等,尽量聚合在一起。

3.2 这样划分的好处

好处 说明
易于定位 改"订单"就进 order 目录,不用到处找
易于协作 不同人负责不同域,冲突少
易于复用 模块边界清晰,抽离公共能力更自然
易于交接 新人看目录就能大致理解业务结构

4、推荐的 Vue3 项目目录结构(完整版)

python 复制代码
src/
├── api/                    # 全局 API(跨域、通用接口)
│   └── request.ts          # 封装 axios / fetch
│
├── assets/                 # 静态资源
│   ├── images/
│   ├── styles/
│   │   ├── variables.scss  # 变量
│   │   └── global.scss     # 全局样式
│   └── fonts/
│
├── components/             # 全局公共组件(多域复用)
│   ├── common/             # 通用 UI 组件
│   │   ├── AppButton.vue
│   │   ├── AppModal.vue
│   │   └── AppTable.vue
│   └── layout/             # 布局组件
│       ├── AppHeader.vue
│       ├── AppSidebar.vue
│       └── AppFooter.vue
│
├── composables/            # 全局可复用组合式函数
│   ├── useAuth.ts          # 鉴权
│   ├── usePagination.ts    # 分页
│   └── useTable.ts         # 表格
│
├── router/
│   └── index.ts            # 路由配置
│
├── store/                  # Pinia 状态
│   └── index.ts            # 根 store 或全局模块
│
├── types/                  # 全局类型
│   └── common.ts
│
├── utils/                  # 全局工具
│   ├── format.ts
│   ├── storage.ts
│   └── validate.ts
│
├── views/                  # 路由页面(按业务域划分子目录)
│   ├── user/               # 用户域
│   │   ├── Login.vue
│   │   ├── Register.vue
│   │   ├── Profile.vue
│   │   └── components/     # 仅本域使用的组件
│   │       └── ProfileForm.vue
│   ├── product/            # 商品域
│   │   ├── List.vue
│   │   ├── Detail.vue
│   │   └── components/
│   │       └── ProductCard.vue
│   └── order/              # 订单域
│       ├── List.vue
│       ├── Detail.vue
│       └── components/
│           └── OrderStatus.vue
│
├── modules/                # 业务域模块(页面外的逻辑、API、类型)
│   ├── user/
│   │   ├── api.ts          # 用户相关接口
│   │   ├── types.ts        # 用户相关类型
│   │   └── store.ts        # 用户相关 store(可选)
│   ├── product/
│   │   ├── api.ts
│   │   └── types.ts
│   └── order/
│       ├── api.ts
│       └── types.ts
│
├── App.vue
└── main.ts

4.1 各目录职责说明

目录 职责 放什么 不放什么
api/ 请求封装、全局接口 axios 实例、拦截器、跨域请求 具体业务接口
assets/ 静态资源 图片、样式、字体 逻辑代码
components/ 全局组件 多域复用 UI、布局 只在某一域用的组件
composables/ 全局组合式函数 鉴权、分页、表格等 单页面内用的逻辑
router/ 路由 路由配置 业务组件
store/ 全局状态 跨域共享的状态 单域状态建议放 modules/xxx/store.ts
types/ 全局类型 通用 TS 类型 业务域专用类型
utils/ 全局工具 格式化、校验、存储 业务逻辑
views/ 路由页面 按业务域分组 非页面组件
modules/ 业务域逻辑 接口、类型、单域 store 视图组件

五、实战:按业务域搭建一个简单项目

5.1 目录规划

bash 复制代码
views/user/Login.vue          # 登录页
modules/user/api.ts           # 登录接口
modules/user/types.ts         # 用户类型
components/common/AppInput.vue # 通用输入框(若复用)

5.2 modules/user/types.ts

ts 复制代码
// 用户相关类型定义
export interface LoginParams {
  username: string
  password: string
}

export interface UserInfo {
  id: number
  username: string
  avatar: string
  token: string
}

5.3 modules/user/api.ts

ts 复制代码
// 用户相关接口 - 集中管理,方便维护
import request from '@/api/request'
import type { LoginParams, UserInfo } from './types'

export const loginApi = (data: LoginParams) => {
  return request.post<UserInfo>('/api/user/login', data)
}

export const getUserInfoApi = (id: number) => {
  return request.get<UserInfo>(`/api/user/${id}`)
}

5.4 views/user/Login.vue

html 复制代码
<template>
  <div class="login-page">
    <h1>用户登录</h1>
    <form @submit.prevent="handleLogin">
      <input
        v-model="form.username"
        type="text"
        placeholder="用户名"
      />
      <input
        v-model="form.password"
        type="password"
        placeholder="密码"
      />
      <button type="submit" :disabled="loading">登录</button>
    </form>
    <p v-if="errorMsg" class="error">{{ errorMsg }}</p>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { loginApi } from '@/modules/user/api'
import type { LoginParams } from '@/modules/user/types'

const router = useRouter()
const loading = ref(false)
const errorMsg = ref('')

const form = reactive<LoginParams>({
  username: '',
  password: ''
})

const handleLogin = async () => {
  if (!form.username || !form.password) {
    errorMsg.value = '请填写完整信息'
    return
  }
  loading.value = true
  errorMsg.value = ''
  try {
    const res = await loginApi(form)
    // 存储 token 等逻辑...
    router.push('/profile')
  } catch (e: any) {
    errorMsg.value = e.message || '登录失败'
  } finally {
    loading.value = false
  }
}
</script>

<style scoped>
.login-page {
  max-width: 400px;
  margin: 50px auto;
  padding: 20px;
}
.error {
  color: red;
  font-size: 14px;
}
</style>

说明要点:

  • 页面只负责「展示 + 调用接口」
  • 接口在 modules/user/api.ts,类型在 modules/user/types.ts
  • 修改接口或类型时,只需改对应模块,页面改动小

六、常见坑与避坑建议

6.1 组件该放 components/ 还是 views/xxx/components/

  • 只有当前业务域会用 → 放 views/xxx/components/
  • 至少两个域会用到 → 放 components/common/components/layout/

6.2 API 放哪?

  • 只服务于某一域modules/xxx/api.ts
  • 跨域或基础能力api/ 或单独抽一层(如 api/base.ts

6.3 类型放哪?

  • 仅该域使用modules/xxx/types.ts
  • 多处复用types/common.ts 或共享的 types/ 模块

6.4 路径别名

vite.config.tsvue.config.js 里配置:

ts 复制代码
// vite.config.ts 示例
resolve: {
  alias: {
    '@': path.resolve(__dirname, 'src')
  }
}

这样可以用 @/modules/user/api 代替 ../../../modules/user/api

6.5 循环依赖

modules/user/api.ts 引用 storestore 又引用 api,容易形成循环。建议:

  • API 层不依赖 store
  • store 调用 api,或通过 composable 间接调用

七、小结:如何落地这套规范

  1. 新项目:先按业务域规划 views/modules/,再写代码。
  2. 老项目:可先从新功能开始,按域拆分,逐步迁移旧代码。
  3. 团队:把目录规范写到文档或 .cursor/rules 里,新人 onboarding 时统一讲解。

核心原则:高内聚、低耦合 ------同一业务域的代码尽量在一起,跨域共用部分放到 componentsutilscomposables 等全局目录。


技术成长,从来不是比谁写得快,而是比谁写得稳、规范、可维护

哪怕每次只吃透一条规范,长期下来,差距会非常明显。

后续我会持续更新前端规范、工程化、可维护代码相关实战干货,帮你告别面条代码、维护噩梦,在开发与面试中更有底气。

觉得有用欢迎 点赞 + 收藏 + 关注,不错过每一篇实战内容。

我是 Eugene,与你一起写规范、写优质代码,我们下篇干货见~

相关推荐
程序员小李白2 小时前
vue2基本语法详细解析(2.7条件渲染)
开发语言·前端·javascript
卤蛋fg62 小时前
vue表单vxe-from配置渲染日期范围选择器的用法
vue.js
悟空瞎说2 小时前
# 10年前端血坑:Canvas drawImage画不出图?90%的人栽在这几步
前端
qibmz2 小时前
新电脑安装 nvm 卡住?无需修改配置文件,一行命令完美解决!
前端
遗憾随她而去.2 小时前
高德地图自定义点标记: SVG vs HTML+CSS两种方案
前端·css
陕西小伙伴网络科技有限公司2 小时前
kettle单转换实现分页查询
开发语言·前端·javascript
踩着两条虫2 小时前
低代码 + AI,到底是生产力革命,还是下一代“技术债务”?
前端·人工智能·低代码
南知意-2 小时前
cloud-app-admin:一款现代化、开箱即用的 Vue 3 后台管理模板
前端·javascript·vue.js·开源·开源项目
前端小王呀2 小时前
Vue 中高级开发面试题及答案
前端·javascript·vue.js