☰
基于 Vue 的前端架构,我做了这 15 点:从 vue-router 到 Ant Design Vue 的工程化落地
2026/9/27 12:26:46 网站建设 项目流程

1. 从零搭建 Vue 中大型项目,我踩过的那些坑

Vue 前端架构这件事,说简单也简单,vue create一把梭就能跑起来;说复杂也复杂,尤其是当项目从 3 个页面膨胀到 80 个页面、从 1 个人写到 6 个人协作的时候,路由乱成一锅粥、vuex 状态到处飞、Ant Design Vue 组件样式互相打架,这些问题会一个接一个冒出来。这篇内容聚焦的就是 Vue 中大型项目从零搭建到可维护的工程化路径,覆盖 vue-router 路由分层、vuex 状态管理、Ant Design Vue 组件规范与目录结构设计,最终交付一套可复制的 router 配置骨架、vuex module 拆分模板与目录约定,并给出本地启动与构建验证动作。

适合谁看?如果你正在负责一个 GIS、BIM、后台管理这类中大型 Vue 系统,或者团队要从零搭一个能撑住两三年迭代的前端底座,那这篇内容里的 15 个点基本能覆盖你 80% 的决策场景。我试过把这套结构用在几个实际项目里,后面招进来的新人基本两天就能上手改业务代码,不用再问“这个接口封装在哪”“这个状态该放哪个 module”。

下面按“问题场景 → 前置准备 → 可复制配置 → 验证 → 排障 → 工具入口”的顺序展开,你可以直接照着搭。

2. 先解决一个前置问题:模型与 API 的统一入口

在真正写 router 和 vuex 之前,有个容易被忽略的前置环节:项目里如果涉及 AI 能力(比如智能表单填充、代码辅助、文档摘要),你需要一个稳定的模型调用入口。很多团队的做法是每个人各自申请 key、各自写一套请求封装,结果就是密钥散落、额度无法统一管理、换模型要改一堆代码。

TaoToken 在这里扮演的角色就是统一入口:它提供兼容 OpenAI 风格的 API,你可以在一个控制台里管理密钥、查看用量、切换模型。对 Vue 项目来说,你只需要在@/libs/request.js里配一个 baseURL,剩下的和普通接口没区别。

具体入口我列一下,方便你按需取用:

  • 官网了解整体能力:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
  • API 接入地址(写进环境变量):https://taotoken.net/api
  • 模型对话体验(验证模型是否可用):https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
  • 长期编码 / Agent 场景(Coding Plan):https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
  • 控制台(管理密钥与用量):https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite
  • 创建 API Keys:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
  • 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
  • ClaudeCode / Anthropic 兼容说明:https://taotoken.net/claudecode-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite

注意:API 地址不要加 UTM 参数,直接写https://taotoken.net/api即可,否则部分 SDK 会把它当成非法路径。

拿到 key 之后,在项目根目录建.env.development和.env.production,分别写入:

# .env.development VUE_APP_BASE_URL=/api VUE_APP_AI_BASE_URL=https://taotoken.net/api VUE_APP_AI_KEY=sk-你的开发key VUE_APP_STATIC_URL=http://localhost:8081/static # .env.production VUE_APP_BASE_URL=https://your-domain.com/api VUE_APP_AI_BASE_URL=https://taotoken.net/api VUE_APP_AI_KEY=sk-你的生产key VUE_APP_STATIC_URL=https://your-oss.aliyuncs.com/static

这样做的价值在于:AI 请求和业务请求走同一套 axios 封装,拦截器、错误处理、loading 全部复用,不用为 AI 单独维护一套请求逻辑。

3. 可复制配置:router 分层 + vuex module 拆分 + 目录约定

3.1 vue-router 路由分层骨架

中大型项目最怕的就是router/index.js一个文件写 800 行。我的做法是按“布局层 → 业务模块层 → 页面层”三层拆:

src/router/ ├── index.js # 只做 createRouter 和全局守卫挂载 ├── routes/ │ ├── frameIn.js # 需要登录/权限的路由 │ ├── frameOut.js # 登录页、注册页等 │ └── errorPage.js # 404 / 403 └── guard/ ├── auth.js # 登录态校验 └── permission.js # 按钮级权限

index.js保持极简:

import { createRouter, createWebHistory } from 'vue-router' import frameIn from './routes/frameIn' import frameOut from './routes/frameOut' import errorPage from './routes/errorPage' import setupGuard from './guard' const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes: [...frameOut, ...frameIn, ...errorPage], scrollBehavior: () => ({ top: 0 }) }) setupGuard(router) export default router

frameIn.js里每个路由都带meta.auth和meta.permissions:

export default [ { path: '/', component: () => import('@/layouts/BasicLayout.vue'), redirect: '/home', children: [ { path: 'home', name: 'Home', component: () => import(/* webpackChunkName: "home" */ '@/views/home/index.vue'), meta: { title: '首页', auth: true } }, { path: 'project', name: 'Project', component: () => import(/* webpackChunkName: "project" */ '@/views/project/index.vue'), meta: { title: '项目管理', auth: true, permissions: { add: true, delete: false } } } ] } ]

webpackChunkName这条注释一定要加,否则打包后你根本不知道哪个 chunk 对应哪个页面,排查体积问题时会很痛苦。

3.2 vuex module 拆分模板

vuex 的拆分原则是“按页面模块划分,默认 namespaced”。目录结构:

src/store/ ├── index.js ├── modules/ │ ├── system/ │ │ ├── user.js # 用户信息、token │ │ ├── menu.js # 菜单与路由 │ │ └── index.js │ └── project/ │ ├── list.js │ └── index.js └── plugins/ └── persist.js # 持久化插件

单个 module 模板(以user.js为例):

import { login, getInfo, logout } from '@/api/user' import storage from 'store' import { ACCESS_TOKEN } from '@/store/mutation-types' const state = { token: storage.get(ACCESS_TOKEN) || '', info: {}, roles: [] } const getters = { isLogin: state => !!state.token, userName: state => state.info.name || '' } const mutations = { SET_TOKEN(state, token) { state.token = token storage.set(ACCESS_TOKEN, token, 7 * 24 * 60 * 60 * 1000) }, SET_INFO(state, info) { state.info = info }, CLEAR_ALL(state) { state.token = '' state.info = {} state.roles = [] storage.remove(ACCESS_TOKEN) } } const actions = { async Login({ commit }, payload) { const res = await login(payload) commit('SET_TOKEN', res.token) return res }, async GetInfo({ commit }) { const res = await getInfo() commit('SET_INFO', res) return res }, async Logout({ commit }) { await logout() commit('CLEAR_ALL') } } export default { namespaced: true, state, getters, mutations, actions }

index.js里统一注册:

import { createStore } from 'vuex' import user from './modules/system/user' import menu from './modules/system/menu' import projectList from './modules/project/list' export default createStore({ modules: { 'system/user': user, 'system/menu': menu, 'project/list': projectList } })

命名用“大模块/子模块”的路径式 key,调用时store.dispatch('system/user/GetInfo')一眼就知道数据归属,比扁平命名清晰得多。

3.3 Ant Design Vue 组件规范与目录约定

Ant Design Vue 的样式覆盖是个老话题。我的建议是:能用 JS 对象改内置变量就别用 less 覆盖 class,因为后者会把全量样式打进来,CSS 体积轻松破 500KB。

// src/styles/antdTheme.js module.exports = { 'primary-color': '#1890ff', 'border-radius-base': '4px', 'font-size-base': '14px' }
// vue.config.js const modifyVars = require('./src/styles/antdTheme') module.exports = { css: { loaderOptions: { less: { lessOptions: { javascriptEnabled: true, modifyVars } } } } }

配合babel-plugin-import做按需加载:

// babel.config.js module.exports = { presets: ['@vue/cli-plugin-babel/preset'], plugins: [ ['import', { libraryName: 'ant-design-vue', libraryDirectory: 'es', style: true }] ] }

目录约定我固定成这样,团队里谁进来都按这个放:

src/ ├── api/ # 接口定义,按模块分文件 ├── assets/ # 静态资源(icons、images) ├── components/ # 全局通用组件 ├── layouts/ # 布局组件 ├── libs/ # 工具库(request、utils、storage) ├── mock/ # mock 数据 ├── router/ # 路由 ├── store/ # vuex ├── styles/ # 全局样式 └── views/ # 页面,按路由层级建文件夹

views下的页面组件,复杂页面在文件夹内再建components子目录,页面入口统一叫index.vue。

4. 验证请求:本地启动与构建检查

配置写完之后,必须做两件事验证:本地能跑、构建能过。

本地启动:

npm run serve

打开http://localhost:8080,检查三件事:

  1. 路由跳转是否正常,/home能加载,未登录访问/project是否被重定向到登录页;
  2. 打开控制台 Network,看 AI 请求是否打到https://taotoken.net/api,返回 200;
  3. vuex 里system/user/info在登录后是否有值。

构建验证:

npm run build

构建完成后看dist目录,重点检查:

  • CSS 文件是否只有一个主 chunk 加若干异步 chunk,主 CSS 是否控制在 200KB 以内;
  • 每个页面是否生成了独立的 JS chunk,chunk 名是否和webpackChunkName对应;
  • 有没有出现moment的多语言包被打进来(如果打进来了,说明 ContextReplacementPlugin 没生效)。

如果构建体积异常,装个分析工具:

npm install webpack-bundle-analyzer --save-dev
// vue.config.js const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin module.exports = { chainWebpack: config => { if (process.env.use_analyzer) { config.plugin('webpack-bundle-analyzer').use(BundleAnalyzerPlugin) } } }

然后use_analyzer=true npm run build,浏览器会自动打开体积分布图。

5. 本篇常见错排查

5.1 路由守卫死循环

现象:登录后一直跳登录页,或者页面无限刷新。

原因通常是beforeEach里对登录页本身也做了 auth 校验。正确写法是先判断to.path === '/user/login'直接放行:

router.beforeEach(async (to, from, next) => { NProgress.start() if (to.path === '/user/login') { next() return } if (to.matched.some(r => r.meta.auth)) { const token = storage.get(ACCESS_TOKEN) if (token) { if (Object.keys(store.state['system/user'].info).length === 0) { await store.dispatch('system/user/GetInfo') } next() } else { next({ name: 'Login', query: { redirect: to.fullPath } }) } } else { next() } })

5.2 vuex module 命名空间找不到

报错[vuex] unknown action type: system/user/GetInfo。

检查两点:module 导出时有没有写namespaced: true;注册时 key 是不是和调用时一致。我见过有人注册写user,调用写system/user/GetInfo,自然找不到。

5.3 Ant Design Vue 样式不生效

按需加载开启后,modifyVars有时不生效。原因是babel-plugin-import的style: true会引入 less 源文件,而modifyVars需要在 less-loader 里配置。两者要同时存在,缺一不可。

5.4 AI 请求 401

如果 AI 接口返回 401,先检查.env里的 key 有没有被VUE_APP_前缀正确暴露。Vue CLI 只注入以VUE_APP_开头的变量,写成AI_KEY是读不到的。另外确认请求头是Authorization: Bearer sk-xxx,不是token。

5.5 构建后静态资源 404

检查VUE_APP_STATIC_URL在 production 环境是否指向了正确的 OSS 地址,以及publicPath是否配置正确。如果是部署在子路径下,vue.config.js里要设publicPath: '/your-sub-path/'。

6. 工具入口与后续动作

搭完这套骨架之后,如果你还要继续做 AI 相关的功能接入,建议按场景分流:

  • 只是验证模型能不能用、效果如何,直接去模型对话页试:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
  • 要在项目里正式接入 API,先去创建密钥:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
  • 接入过程中遇到参数、鉴权、返回格式问题,查接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
  • 如果是长期做编码辅助、Agent 类功能,看 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
  • 需要管理多个项目的密钥和用量,进控制台:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite

最后补一个实际经验:这套架构里最容易被忽视的是libs/request.js的错误处理。我见过太多项目把 401、403、500 的处理散落在各个页面里,结果 token 过期时用户看到的是白屏而不是登录页。把错误处理统一收口到拦截器,配合 vuex 的CLEAR_ALL,才是真正省心的做法。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询