尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

大型项目验证的企业级 Vue3 项目架构模板 从零到生产可用的架构骨架

大型项目验证的企业级 Vue3 项目架构模板 从零到生产可用的架构骨架
📅 发布时间:2026/7/2 7:02:17

一个真正能落地、经过大型项目验证的企业级 Vue3 项目模板。不是那种"hello world 级别"的脚手架,而是从零到生产可用的架构骨架。

一、技术栈选型(先定武器)

领域

选型

理由

框架

Vue 3.4+ (<script setup>)

性能 + 组合式 API

语言

TypeScript 5.x(严格模式)

类型即文档

构建

Vite 5.x

快,生态成熟

路由

Vue Router 4.x

标配

状态

Pinia + pinia-plugin-persistedstate

比 Vuex 更适合组合式

HTTP

Axios(封装层)或 fetch 包装

拦截器、取消请求

UI

不绑定具体库(Element Plus / Ant Design Vue 均可)

解耦 UI 框架

样式

UnoCSS + CSS Variables + SCSS(按需)

原子化 + 主题

表单

自建useFormcomposable 或vee-validate

看团队偏好

表格

自建useTablecomposable

配置驱动

校验

Zod 或@vuelidate/validators

Schema 校验

测试

Vitest + @vue/test-utils

单元测试

代码质量

ESLint + Prettier + simple-git-hooks + lint-staged

强制约束

Monorepo(可选)

pnpm workspace

超大型项目拆分


二、完整目录结构

my-enterprise-app/ ├── .husky/ ├── .vscode/ │ ├── extensions.json │ └── settings.json ├── public/ ├── src/ │ ├── main.ts # 入口:只做 app 实例创建 & 插件注册 │ ├── App.vue # 根组件:只放 <RouterView /> │ ├── env.d.ts │ │ │ ├── assets/ # 纯静态资源 │ │ ├── images/ │ │ └── styles/ │ │ ├── variables.scss # 全局变量 │ │ ├── reset.scss # 重置样式 │ │ └── index.scss # 统一导出 │ │ │ ├── components/ # ✅ 通用基础组件(纯 UI,零业务) │ │ ├── base/ # 最底层:Button、Input、Modal... │ │ │ ├── BaseButton.vue │ │ │ └── BaseInput.vue │ │ ├── feedback/ # 反馈类:Toast、Loading、Empty... │ │ ├── layout/ # 布局类:Container、Header、Sider... │ │ └── index.ts # 统一 export + 自动全局注册 │ │ │ ├── layouts/ # ✅ 页面级布局壳 │ │ ├── DefaultLayout.vue # 侧边栏 + 头部 + 内容区 │ │ ├── BlankLayout.vue # 空白(登录页用) │ │ └── FullPageLayout.vue # 全屏页面 │ │ │ ├── pages/ # ✅ 页面(按业务域划分) │ │ ├── login/ │ │ │ ├── index.vue # 只做模板编排 │ │ │ ├── LoginForm.vue # 页面内子组件 │ │ │ └── composables/ │ │ │ └── useLogin.ts # 登录逻辑 │ │ ├── dashboard/ │ │ │ └── index.vue │ │ ├── system/ │ │ │ ├── users/ │ │ │ │ ├── index.vue │ │ │ │ ├── UserTable.vue │ │ │ │ ├── UserDrawer.vue │ │ │ │ ├── composables/ │ │ │ │ │ ├── useUserList.ts │ │ │ │ │ ├── useUserForm.ts │ │ │ │ │ └── useUserDelete.ts │ │ │ │ └── types.ts │ │ │ ├── roles/ │ │ │ └── permissions/ │ │ └── orders/ │ │ ├── index.vue │ │ ├── OrderTable.vue │ │ └── composables/ │ │ └── useOrderList.ts │ │ │ ├── composables/ # ✅ 跨页面复用的通用逻辑 │ │ ├── state/ │ │ │ ├── useToggle.ts │ │ │ ├── useCounter.ts │ │ │ └── useLoading.ts │ │ ├── ui/ │ │ │ ├── useDialog.ts # 命令式弹窗 │ │ │ ├── useMessage.ts # 全局提示 │ │ │ ├── usePagination.ts # 分页逻辑 │ │ │ └── useTable.ts # 表格通用逻辑 │ │ ├── dom/ │ │ │ ├── useEventListener.ts │ │ │ ├── useClickOutside.ts │ │ │ └── useDebounceFn.ts │ │ └── auth/ │ │ ├── usePermission.ts │ │ └── useAuth.ts │ │ │ ├── stores/ # ✅ Pinia 状态管理 │ │ ├── modules/ │ │ │ ├── app.store.ts # 应用级:sidebar collapsed、theme │ │ │ ├── user.store.ts # 用户信息、token │ │ │ ├── permission.store.ts # 权限路由、按钮权限 │ │ │ └── dict.store.ts # 字典数据(枚举映射) │ │ └── index.ts # 统一导出 + persist 配置 │ │ │ ├── services/ # ✅ API 请求层 │ │ ├── request/ │ │ │ ├── http.ts # axios 实例 + 拦截器 │ │ │ ├── types.ts # 请求相关类型 │ │ │ ├── errorHandler.ts # 统一错误处理 │ │ │ └── cancelRequest.ts # 请求取消管理 │ │ ├── modules/ │ │ │ ├── user.service.ts │ │ │ ├── order.service.ts │ │ │ └── auth.service.ts │ │ └── index.ts │ │ │ ├── domain/ # ✅ 领域模型 & 业务规则(核心!) │ │ ├── user/ │ │ │ ├── types.ts # User 实体定义 │ │ │ ├── rules.ts # 业务规则:canEdit、canDelete │ │ │ ├── transformers.ts # DTO ↔ Entity 转换 │ │ │ └── constants.ts # 枚举、状态码 │ │ ├── order/ │ │ │ ├── types.ts │ │ │ ├── rules.ts │ │ │ └── flow.ts # 订单流转规则 │ │ └── shared/ │ │ ├── pagination.ts # 分页通用类型 │ │ └── result.ts # ApiResult<T> 包装 │ │ │ ├── router/ # ✅ 路由 │ │ ├── index.ts # createRouter │ │ ├── routes/ │ │ │ ├── base.ts # 基础路由(登录、404) │ │ │ ├── system.ts # 系统管理模块路由 │ │ │ └── business.ts # 业务模块路由 │ │ ├── guards/ # 导航守卫 │ │ │ ├── auth.guard.ts # 登录校验 │ │ │ ├── permission.guard.ts# 权限校验 │ │ │ └── progress.guard.ts # 进度条 │ │ └── helpers/ │ │ └── routeMatch.ts # 路由匹配工具 │ │ │ ├── directives/ # ✅ 自定义指令 │ │ ├── permission.ts # v-permission │ │ ├── debounce.ts # v-debounce │ │ ├── copy.ts # v-copy │ │ └── index.ts │ │ │ ├── plugins/ # ✅ 第三方插件初始化 │ │ ├── iconify.ts # 图标注册 │ │ ├── echarts.ts # 图表注册 │ │ └── index.ts │ │ │ ├── utils/ # ✅ 纯工具函数 │ │ ├── storage.ts # localStorage/sessionStorage 包装 │ │ ├── format.ts # 日期、金额格式化 │ │ ├── validate.ts # 通用校验 │ │ ├── tree.ts # 树形数据处理 │ │ ├── download.ts # 文件下载 │ │ └── crypto.ts # 加密解密 │ │ │ ├── config/ # ✅ 应用配置(编译时确定) │ │ ├── app.ts # 应用名称、版本 │ │ ├── menu.ts # 菜单配置 │ │ └── api.ts # API 路径前缀 │ │ │ ├── constants/ # ✅ 运行时常量 │ │ ├── storageKeys.ts │ │ ├── regexps.ts │ │ └── httpStatus.ts │ │ │ └── types/ # ✅ 全局类型声明 │ ├── api.d.ts │ ├── env.d.ts │ ├── shims-vue.d.ts │ └── business.d.ts │ ├── tests/ # ✅ 测试 │ ├── unit/ │ └── e2e/ │ ├── scripts/ # ✅ 构建/开发脚本 │ ├── generateApi.js # swagger → ts types │ └── generatePage.js # 自动生成页面模板 │ ├── .env.development ├── .env.production ├── .env.staging ├── .eslintrc.cjs ├── .prettierrc.cjs ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts ├── package.json └── README.md

三、核心模块代码示例

1️⃣ 入口文件(main.ts)—— 只做装配

// src/main.ts import { createApp } from 'vue' import App from './App.vue' import { setupRouter } from './router' import { setupStores } from './stores' import { setupDirectives } from './directives' import { setupPlugins } from './plugins' import { setupGlobalComponents } from './components' import './assets/styles/index.scss' function bootstrap() { const app = createApp(App) setupStores(app) // Pinia setupRouter(app) // Vue Router setupDirectives(app) // 自定义指令 setupPlugins(app) // 第三方插件 setupGlobalComponents(app) // 全局基础组件 app.mount('#app') } bootstrap()

📌原则:入口不写业务逻辑,只做"接线"


2️⃣ HTTP 请求层(services/request/http.ts)

import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios' import { useUserStore } from '@/stores/modules/user.store' import { showMessage } from '@/composables/ui/useMessage' import { handleBusinessError, handleHttpError } from './errorHandler' const http: AxiosInstance = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000, headers: { 'Content-Type': 'application/json' } }) // 请求拦截 http.interceptors.request.use((config) => { const userStore = useUserStore() if (userStore.token) { config.headers!.Authorization = `Bearer ${userStore.token}` } return config }) // 响应拦截 http.interceptors.response.use( (response) => { const { code, data, message } = response.data // 业务码处理 if (code === 200) return data if (code === 401) { // token 过期 → 跳转登录 window.location.href = '/login' return Promise.reject(new Error('未登录')) } handleBusinessError(code, message) return Promise.reject(new Error(message)) }, (error) => { handleHttpError(error) return Promise.reject(error) } ) export default http

3️⃣ Service 层(services/modules/user.service.ts)

import http from '../request/http' import type { User, CreateUserDto, UpdateUserDto } from '@/domain/user/types' import type { PaginatedResult } from '@/domain/shared/result' export const userApi = { getList(params: { page: number; pageSize: number; keyword?: string }) { return http.get<PaginatedResult<User>>('/users', { params }) }, getById(id: string) { return http.get<User>(`/users/${id}`) }, create(data: CreateUserDto) { return http.post<User>('/users', data) }, update(id: string, data: UpdateUserDto) { return http.put<User>(`/users/${id}`, data) }, delete(id: string) { return http.delete(`/users/${id}`) }, batchDelete(ids: string[]) { return http.post('/users/batch-delete', { ids }) } }

📌Service 只管"怎么调接口",不管"什么时候调、调完干什么"


4️⃣ Composable —— 业务逻辑核心(useUserList.ts)

// src/pages/system/users/composables/useUserList.ts import { ref, onMounted } from 'vue' import { userApi } from '@/services/modules/user.service' import { usePagination } from '@/composables/ui/usePagination' import { useLoading } from '@/composables/state/useLoading' import { useMessage } from '@/composables/ui/useMessage' import type { User } from '@/domain/user/types' export function useUserList() { const { page, pageSize, total, resetPage } = usePagination() const { loading, withLoading } = useLoading() const { success, error } = useMessage() const list = ref<User[]>([]) const keyword = ref('') const fetchList = async () => { try { const res = await withLoading( userApi.getList({ page: page.value, pageSize: pageSize.value, keyword: keyword.value }) ) list.value = res.items total.value = res.total } catch (e) { error('获取用户列表失败') } } const handleSearch = () => { resetPage() fetchList() } const handleReset = () => { keyword.value = '' resetPage() fetchList() } onMounted(fetchList) return { list, loading, page, pageSize, total, keyword, fetchList, handleSearch, handleReset } }

5️⃣ 页面组件(pages/system/users/index.vue)—— 只做编排

<script setup lang="ts"> import UserTable from './UserTable.vue' import UserDrawer from './UserDrawer.vue' import { useUserList } from './composables/useUserList' import { useUserDelete } from './composables/useUserDelete' const { list, loading, page, pageSize, total, keyword, handleSearch, handleReset } = useUserList() const { deleting, handleDelete, handleBatchDelete } = useUserDelete(() => fetchList()) // 抽屉状态 const drawerVisible = ref(false) const currentUserId = ref<string>() </script> <template> <div class="user-page"> <!-- 搜索栏 --> <a-card :bordered="false" class="mb-4"> <a-form layout="inline"> <a-form-item label="关键词"> <a-input v-model:value="keyword" placeholder="姓名/手机号" allow-clear /> </a-form-item> <a-form-item> <a-button type="primary" @click="handleSearch">搜索</a-button> <a-button class="ml-2" @click="handleReset">重置</a-button> </a-form-item> </a-form> </a-card> <!-- 操作栏 + 表格 --> <a-card :bordered="false"> <template #extra> <a-button type="primary" @click="drawerVisible = true">新增用户</a-button> <a-button danger class="ml-2" :disabled="!selectedIds.length">批量删除</a-button> </template> <UserTable :data="list" :loading="loading" :pagination="{ page, pageSize, total }" @delete="handleDelete" @edit="(id) => { currentUserId = id; drawerVisible = true }" /> </a-card> <!-- 新增/编辑抽屉 --> <UserDrawer v-model:visible="drawerVisible" :user-id="currentUserId" @success="fetchList" /> </div> </template>

📌页面组件里没有一行数据请求代码、没有业务逻辑判断


6️⃣ 领域规则(domain/user/rules.ts)

import type { User } from './types' export function canEditUser(user: User, currentUserId: string): boolean { if (user.id === currentUserId) return true if (user.role === 'super_admin') return false return true } export function canDeleteUser(user: User): boolean { return user.status !== 'active' && user.role !== 'super_admin' } export function getUserDisplayName(user: User): string { return user.nickname || user.username }

7️⃣ usePagination —— 通用复用逻辑

// src/composables/ui/usePagination.ts import { ref } from 'vue' export function usePagination(defaultPageSize = 20) { const page = ref(1) const pageSize = ref(defaultPageSize) const total = ref(0) const resetPage = () => { page.value = 1 } const onChange = (p: number, ps: number) => { page.value = p pageSize.value = ps } return { page, pageSize, total, resetPage, onChange } }

8️⃣ 权限指令(directives/permission.ts)

import type { Directive } from 'vue' import { usePermission } from '@/composables/auth/usePermission' export const permissionDirective: Directive = { mounted(el, binding) { const { hasPermission } = usePermission() const required = binding.value // ['user:create', 'user:update'] if (!hasPermission(required)) { el.parentNode?.removeChild(el) } } }

模板中使用:

<a-button v-permission="['user:create']">新增用户</a-button>

四、数据流全景图

┌─────────────────────────────────────────────┐ │ Template │ │ (只消费数据 + 触发事件) │ └──────────────────┬──────────────────────────┘ │ ┌──────────────────▼──────────────────────────┐ │ Page Component │ │ (编排 composables) │ └──────┬───────────┬────────────┬────────────┘ │ │ │ ┌──────▼───┐ ┌────▼────┐ ┌───▼──────────┐ │Composable│ │Composable│ │ Domain Rules │ │(业务逻辑) │ │(UI 逻辑) │ │ (业务规则) │ └──────┬───┘ └────┬────┘ └───┬──────────┘ │ │ │ ┌──────▼───────────▼────────────▼──────────┐ │ Pinia Store │ │ (跨页面共享状态) │ └──────────────────┬───────────────────────┘ │ ┌──────────────────▼───────────────────────┐ │ Services Layer │ │ (HTTP 请求封装) │ └──────────────────┬───────────────────────┘ │ ┌──────────────────▼───────────────────────┐ │ HTTP Client (Axios) │ └──────────────────┬───────────────────────┘ │ Backend API

五、关键设计决策说明

决策

为什么这么做

Service 不直接返回 response

统一在拦截器拆包,调用方拿到纯数据

Composable 返回函数引用而非执行结果

页面控制时机

页面组件不await异步

用withLoading包裹,页面只关心 loading 状态

Domain 层不依赖 Vue

规则函数可单独测试、可复用到 Node.js

指令做权限而非 v-if

声明式、无侵入、可统一处理

每个页面域有自己的 composables/

避免跨页面耦合,同时也允许提取到全局 composables/


六、团队协作规范(必加)

组件分类约定

层级

位置

能否调 API

能否有业务

Base 组件

components/base/

❌

❌

业务组件

pages/xxx/SubComponent.vue

通过 props/emit

✅

页面

pages/xxx/index.vue

通过 composable

编排

Git Commit 规范

feat(user): 新增用户批量导出功能 fix(order): 修复订单状态流转异常 refactor(domain): 重构用户权限判断逻辑 style(table): 调整表格列宽

七、后续演进路线

阶段

动作

初期

按上面结构搭建,先跑通

中期

把composables/ui/抽成内部 npm 包

后期

如果多项目共用,拆成 pnpm monorepo

成熟期

Domain 层独立成包,Vue 只是渲染层


这就是一个能支撑 50+ 页面、10+ 开发者协作、持续迭代 3 年以上不崩盘的 Vue 3 企业级项目模板。

如果这篇文章对你有帮助,欢迎点赞收藏,关注我,我会持续分享前端项目框架和gis领域的实战经验~

相关新闻

  • MeEdu开源网校系统:如何构建高可用、低成本的视频点播平台架构
  • 关于人工智能辅助驾驶
  • 国产鼎讯 DXA-601:以 “芯” 动力,护航能源光缆抢修

最新新闻

  • 数据产业服务分类(08)——经济学术语——概述
  • 嵌入式状态机怎么写?用“洗衣机“讲清楚(附代码模板)
  • EastWave应用:光场与石墨烯和特异介质相互作用的研究
  • Windows 11终极瘦身指南:Win11Debloat让系统重获新生
  • 如何为Windows掌机添加完美运动控制:HandheldCompanion终极指南
  • APDTFlow+NSGM+MLflow时序AI工程实践指南

日新闻

  • Python Playwright录制功能:从零到一构建自动化测试脚本
  • 如何用开源工具永久保存你心爱的小说:novel-downloader全攻略
  • In-Context Learning不是教知识,而是模式对齐:从5个示例到100个工业级样本的真相

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号