我见过太多后台管理系统项目,其实死法都差不多:Demo阶段跑得飞快,一进真实业务就开始糟心。页面越加越多,路由权限乱成一锅粥;全局变量满天飞,改一个字段要全局搜索三遍;等想接个智能分析功能,发现数据层和接口层根本没留余地。我自己的Vue.js + Vite电商后台管理系统,就是按21天的节奏从零搭出来的,从工程化底座、核心业务模块到最后的AI驱动架构,整个过程完整踩了一遍。这篇文章把这21天怎么分、每天该干什么、每个阶段为什么这么设计,全部摊开讲清楚。适合有Vue基础但没完整做过中大型项目的人,也适合正准备启动后台管理项目、想少走弯路的团队参考。后台管理系统不是CRUD堆页面,真正决定成败的,是前期的工程化选择和数据流设计。
1. 先想清楚:21天到底在解决后台管理系统的什么难题
很多人觉得后台管理系统是前端里最简单的一类项目,不就是表格加表单,查数据改数据。这种判断在几十个页面的小后台里基本成立,一旦进入电商领域,复杂度会立刻暴露出来。电商后台要管的不只是商品和订单,还有库存、会员、营销活动、售后流程、数据报表,每个模块之间都有数据流转关系。商品改了价格,订单列表里的历史数据不能跟着变;用户发起退款,订单状态和库存数量要联动更新;运营配置了一个满减活动,前台计算逻辑和后台展示口径必须一致。
这些业务交织带来的复杂度,远超过页面本身的开发量。你真正要处理的是状态一致性、权限粒度、数据口径和后续可维护性。21天这个周期不是拍脑袋定的,它恰好够一个成熟开发者完成一轮"从地基到智能化"的完整闭环:前面花一周把工程底座打牢,中间一周集中攻核心业务域,再花三到四天做性能优化和工程沉淀,最后两三天引入AI驱动能力。如果是团队作战,21天还能留出代码评审和联调缓冲。
我把21天拆成四个阶段,每个阶段有明确的目标和产物:
| 阶段 | 天数 | 核心目标 | 关键产出 |
|---|---|---|---|
| 工程化地基 | 第1-7天 | Vite项目搭好、请求层鉴权、路由权限模型 | 可扩展的目录结构、动态路由、axios封装 |
| 核心业务域 | 第8-14天 | 商品、订单、看板三大模块完整落地 | SPU/SKU管理、订单状态机、指标看板 |
| 性能与沉淀 | 第15-18天 | 构建优化、长列表优化、测试规范 | 分包配置、虚拟列表、vitest用例、lint规则 |
| AI驱动升级 | 第19-21天 | 接入AI能力层,从数据展示升级为辅助决策 | 智能数据问答、内容生成、异常提醒 |
这套计划的核心逻辑是"先定边界,再写代码"。很多项目烂尾不是因为程序员水平不够,而是没想清楚哪些东西属于工程化,哪些属于业务,哪些属于未来扩展。工程化解决的是能不能长期迭代的问题,业务域解决的是能不能用的问题,AI驱动解决的是值不值得用的问题。顺序不能反,地基没打好就冲业务,后面每加一个功能都要还债。
2. 第1-7天:用Vite把工程底座打牢,而不是急着写页面
2.1 为什么是Vite而不是Webpack:启动速度只是表面原因
选Vite最直接的理由确实是快。后台管理系统组件多、路由多、依赖多,用Webpack开发时冷启动经常要等十几秒,改一行代码热更新也要两三秒,一天下来浪费的时间非常可观。Vite基于原生ESM,冷启动基本在一秒级别,热更新能做到毫秒级响应,开发体验完全不在一个量级。
但Vite的价值不止于此。它的依赖预构建机制会把第三方包提前打包成ESM格式,开发环境不用像Webpack那样逐个解析依赖,这才是它快的根本原因。生产构建方面,Vite默认使用Rollup做打包,Tree Shaking效果和代码分割能力都很成熟,配合手动chunk配置,能把后台系统的首屏加载控制得很好。需要注意一个坑:有些老牌库只提供CommonJS格式导出,Vite虽然能兼容,但偶尔会有"named export not found"之类的报错,遇到这种情况,在build.commonjsOptions里配置一下include范围就能解决。
// vite.config.js 片段 import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], build: { rollupOptions: { output: { manualChunks: { 'vendor-vue': ['vue', 'vue-router', 'pinia'], 'vendor-ui': ['element-plus'], 'vendor-echarts': ['echarts'] } } } } })手动分包的目的是把很少变化的第三方库单独打成几个大包,这样业务代码更新时,用户只需要重新下载增量部分,缓存命中率会大幅提升。这个问题我在第四阶段还会细说。
2.2 目录结构:按业务域划分,而不是按技术类型划分
很多Vue项目的目录长这样:views下面是商品列表、订单列表、用户列表,components下面是所有项目共享的按钮、弹窗、表格。这种按技术类型划分的方式,在项目刚开始时很清爽,一旦业务模块多起来,就会变成灾难。你想改商品模块的一个功能,要在router、store、views、api、components里来回跳,每个目录下面都有商品相关的文件,改完这处忘了那处。
我这次按业务域组织目录,每个业务模块自带一套完整的分层:
src/ api/ # 全局请求封装,axios实例 assets/ components/ # 真正跨模块复用的基础组件 features/ product/ # 商品模块:列表页、编辑页、API、Store、组件都在这 api.ts store.ts views/ components/ order/ # 订单模块 dashboard/ # 数据看板模块 member/ # 会员模块 layouts/ # 后台整体布局:侧边栏、顶栏、面包屑 router/ # 路由配置与守卫 stores/ # 全局级状态(用户信息、权限码、全局配置) utils/按业务域划分有个附带好处:每个人负责的模块边界清晰,代码评审和测试范围都能收敛到单个目录里。后台管理系统通常以业务模块为单位做迭代,目录结构和迭代节奏对齐,长期维护会轻松很多。初次涉足的人可能会担心公共组件怎么放,我的原则是:跨三个以上模块复用的东西才提升到全局components,否则就留在业务模块内部,这个判断标准能避免过早抽象。
2.3 请求层与鉴权:token刷新、401拦截、错误提示一个都不能少
后台管理系统几乎没有不带登录鉴权的。开发前7天里,我花了不少时间在请求层,而不是写登录页面本身。登录页很简单,真正难的是登录之后的所有请求都要走一套统一的鉴权逻辑:请求发出前带上token,响应返回后发现token过期要静默刷新,刷新也失败了要强制跳回登录页。
axios实例的封装看起来不难,但细节决定了后面会不会返工。我在项目里的做法是:实例化时设置baseURL和超时时间,请求拦截器里从Pinia读取token并写入headers,响应拦截器里统一处理HTTP状态码和业务错误码。需要特别处理的是token刷新,这里不能每个请求各刷各的,否则并发请求会同时触发多个刷新接口。我用一个队列方案:第一次遇到401时开始刷新token,刷新期间其他请求进入等待队列,刷新完成后再批量重放。
// request.ts 简化版核心逻辑 let isRefreshing = false let pendingQueue: Array<(token: string) => void> = [] async function handleRefreshToken() { if (isRefreshing) { return new Promise((resolve) => { pendingQueue.push(resolve) }) } isRefreshing = true try { const { token } = await refreshTokenAPI() updateToken(token) pendingQueue.forEach((resolve) => resolve(token)) pendingQueue = [] return token } finally { isRefreshing = false } } service.interceptors.response.use( (response) => { // 业务码判断,正常放行 return response.data }, async (error) => { const { response, config } = error if (response?.status === 401 && !config._retry) { config._retry = true const newToken = await handleRefreshToken() config.headers.Authorization = `Bearer ${newToken}` return service(config) } // 统一错误提示,避免每个模块重复写 showErrorMessage(response?.data?.message || '请求失败') return Promise.reject(error) } )这个封装在工程化阶段一次性做完,后面所有业务模块请求都不用再关心鉴权逻辑,只管传参数和拿数据。后端返回结构最好也约定统一格式,我们用的是{ code, data, message },前端只看code判断成功失败,data里放业务数据。这套约定越早定义越好,等到后端十几个接口都写完了再改,成本就大了。
2.4 路由与权限模型:动态路由、菜单权限、按钮权限三层区分
电商后台的角色通常很复杂:超级管理员、运营、客服、仓库管理员、财务,每个角色看到的菜单不一样,能点的按钮也不一样。权限设计如果不做在架构层,后面会到处写v-if,权限一变就要改一堆页面。
我在项目里用的是动态路由方案。路由表分成两部分:基础路由是登录页、404页这种所有角色都能访问的;业务路由是带权限标记的,登录成功后,前端拿到当前用户的权限码列表,把有权限的路由动态注册到Vue Router里,同时根据路由表生成侧边菜单。这样做的好处是,没有权限的模块不仅看不到入口,连路由都进不去,安全性更好。
按钮级权限我用了一个自定义指令v-permission,指令内部检查用户权限码集合里有没有对应权限,没有就直接移除DOM。指令用起来很直观,页面里写一句就行:
// permission.ts app.directive('permission', { mounted(el, binding) { const required = binding.value const hasPermission = useUserStore().permissions.includes(required) if (!hasPermission) { el.parentNode?.removeChild(el) } } })这里有个容易忽略的点:前端权限只是交互层控制,真正防止越权还要靠后端每个接口的权限校验。前端做权限的目的是让界面干净、用户操作链路顺畅,这一点需要和后端明确对齐,否则就会出现"前端菜单能进但接口返回403"的尴尬情况。
3. 第8-14天:死磕商品、订单、看板三个核心业务域
3.1 商品模块:SPU与SKU的数据结构决定整个系统的复杂度
电商后台最典型的一个设计,是商品拆分SPU(标准产品单元)和SKU(库存量单位)。简单说,一个SPU是用户在商品详情页看到的那件商品,比如"某品牌白色T恤",而SKU是具体到规格的售卖单元,比如"白色-M码""白色-L码"。后台商品管理如果只做成一张简单的商品表,后面库存、价格、订单明细全都会乱。
我做的商品模块表单里,编辑区上方是SPU基本信息(名称、类目、品牌、描述),下方是SKU规格区。运营选择颜色、尺码两个规格维度后,前端要自动生成规格组合的二维表格,每个组合对应一个SKU,可以单独设置价格、库存、SKU编码和图片。规格组合的生成逻辑不难,本质是笛卡尔积,但要注意组合顺序要稳定,否则前端展示和后端存储对不上。
// 规格组合生成 function generateSkuCombinations(specs: Record<string, string[]>): Array<Record<string, string>> { const keys = Object.keys(specs) const result: Array<Record<string, string>> = [] function backtrack(index: number, current: Record<string, string>) { if (index === keys.length) { result.push({ ...current }) return } const key = keys[index] for (const value of specs[key]) { current[key] = value backtrack(index + 1, current) } } backtrack(0, {}) return result }做完这个模块我对"数据结构先行"有了更深的体会。SKU的表结构定了,订单明细里存SKU编码,库存模块按SKU做增减,数据看板里按SPU聚合分析,所有下游模块都有了稳定抓手。反过来,如果商品模块一开始图省事只存一个规格文本字段,后续做订单和报表会痛苦万分。
3.2 订单模块:状态机是避免业务混乱的最佳实践
订单模块是整个后台状态最复杂的部分。一个订单从用户下单开始,经历待付款、待发货、已发货、已完成,还可能走到已取消、退款中、已退款。每个状态下,界面上允许的操作完全不同:待付款能取消,已发货能修改物流,已完成只能申请售后。如果不把状态流转抽出来,页面里会充斥大量if else判断,加一个新状态就要翻遍所有相关组件。
我的做法是用一个状态配置表驱动UI。每个状态定义它允许的操作列表,每个操作定义它对应的目标状态。这样页面上渲染按钮时,只需要查表:
const orderStatusMap = { PENDING_PAYMENT: { label: '待付款', operations: [ { key: 'cancel', label: '取消订单', toStatus: 'CANCELLED' }, { key: 'remind', label: '催付款', toStatus: null } ] }, PENDING_SHIPMENT: { label: '待发货', operations: [ { key: 'ship', label: '发货', toStatus: 'SHIPPED' }, { key: 'remark', label: '备注', toStatus: null } ] } }订单列表页根据当前状态动态渲染操作按钮,操作成功后刷新列表。状态配置单独维护一份,后端返回到前端的状态码只在这个映射表里出现一次,语义清晰,测试也好写。别小看这个设计,订单状态一旦在多个页面里硬编码,售后流程、对账单、库存回滚逻辑都会跟着出错。
3.3 数据看板:图表不是重点,指标口径才是
做数据看板最大的误区是一上来就选图表库。ECharts封装不难,真正难的是回答一个问题:你展示的这个数字,是怎么算出来的。GMV是包含运费还是不含运费?订单数是统计支付成功还是包含待付款?退款订单在成交额里要不要剔除?这些口径不先定义清楚,图表画得再好看,业务方也会说数据不对。
我的做法是先和维护看板的后端同学一起定指标字典,前端把指标编码、名称、口径说明维护成一份配置,需要新增指标时只改配置不动页面。图表组件方面,我封装了一个基于ECharts的通用报表组件,接收type、data、config三个prop,内部统一处理init、setOption、resize、销毁这些生命周期逻辑,业务方只需要传数据。
<script setup> import * as echarts from 'echarts' import { onMounted, onUnmounted, watch, ref } from 'vue' const props = defineProps({ type: { type: String, default: 'line' }, data: { type: Object, required: true }, config: { type: Object, default: () => ({}) } }) const chartEl = ref(null) let chartInstance function renderChart() { if (!chartInstance) { chartInstance = echarts.init(chartEl.value) } // 根据 type 和 data 组装 option const option = buildChartOption(props.type, props.data, props.config) chartInstance.setOption(option) } onMounted(() => { renderChart() window.addEventListener('resize', resizeChart) }) onUnmounted(() => { window.removeEventListener('resize', resizeChart) chartInstance?.dispose() }) </script> <template> <div ref="chartEl" class="chart-container" /> </template>封装ECharts组件时最容易漏的是resize监听和dispose。后台看板页面通常有侧边栏,侧边栏收起展开会导致容器宽度变化,不监听resize图表就不刷新。组件销毁时不调用dispose,切路由会出现内存泄漏或者图表实例叠加的怪问题。
3.4 组件复用的沉淀时机:出现第二次再抽象
后台管理系统里,搜索表单、分页表格、详情抽屉这些组件几乎每个页面都有。但我不建议第一天就写一套通用组件,因为你还不知道真实的业务差异在哪。过早抽象出来的组件往往参数满天飞,每个页面传几十个配置项,比直接写页面还难维护。
我的原则是:同一个结构出现第二次时再抽象。第一次使用先按页面需求写,等发现第二个页面有类似结构,对比差异点,抽取出公共部分。比如订单列表和商品列表都有搜索区加表格加分页的结构,但搜索字段、表格列、按钮操作完全不同,这时候抽出通用逻辑层比抽出通用UI组件更有价值。
到第14天结束时,商品、订单、看板三个模块的代码已经各成体系,但模块之间也沉淀出了几个真正跨模块复用的东西:请求层、权限指令、分页表格组件、指标字典。后面再加会员模块、营销模块,主体工作就是复制这套模式,开发速度会明显加快。
4. 第15-18天:从"能跑"到"能扛"——性能优化与工程化沉淀
4.1 首屏性能:路由懒加载、依赖预构建与持久化缓存
后台管理系统的首屏体验容易被忽略,因为使用者是内部员工,加载慢一点好像也能忍。但实际运营中,客服和运营人员每天要打开后台几十次,每慢一秒累积起来都是实打实的效率损失。我在这几天做了两件事:路由懒加载和依赖分包。
路由懒加载是最直接的优化,把每个页面组件变成异步加载,首屏只加载layout和当前页面。Vue Router里用动态导入即可,写法上有个细节:业务组件统一用注释命名chunk,方便看构建产物时定位大块文件:
const routes = [ { path: '/product', component: () => import('@/features/product/views/ProductList.vue') } ]依赖分包我在前面已经配置过manualChunks,这里再补充一个经验:chunk分割要适度。把每个组件都单独打包反而会增加HTTP请求数量,最佳实践是挑选更新频率低的第三方库作为单独chunk,比如vue全家桶、UI组件库、ECharts。我自己第一次配置时把chunk切得很碎,结果本地加载反而变慢,调整成三大块之后才理想。
4.2 大数据量渲染:虚拟列表和分页是两条互补路线
后台系统最常见的性能问题是长列表。订单列表一个月几万条,商品列表几千条,把所有数据一次性渲染成DOM,浏览器立刻卡死。传统做法是分页,每页20条,性能没问题但交互体验一般。现在很多后台把分页改成了滚动加载,这时候就要考虑虚拟列表。
我的方案是:默认用分页表格,因为后台用户习惯按页码跳转和精确查找;在操作日志、消息通知这种无限滚动场景用虚拟列表。虚拟列表实现的核心是只渲染可视区域的DOM,滚动时计算起始索引和结束索引,常见库有无限滚动组件和虚拟表格组件。如果不想引入第三方,自己实现一个简化版也只需要监听scroll事件、计算渲染范围、控制高度占位这三步。
需要注意一个容易被忽略的点:虚拟列表里的每一行必须有稳定唯一的key,因为滚动时元素会频繁重建,key不稳定会导致DOM错乱、事件绑定失效。排查了半天发现是key用了数组索引,而不是业务数据的唯一ID,这个坑非常经典。
4.3 状态管理:Pinia不是必须的,该用才用
Vue后台管理系统的团队很容易陷入一个误区:所有数据都要放进store,页面组件里能不用ref就不用ref。结果store越来越大,数据血缘关系越来越难追踪,改一个字段要翻三个store文件。我把状态分为两类:服务端状态和客户端状态。
服务端状态是接口返回的数据,比如商品列表、订单详情,这类状态最好不要放进全局store。它们天然属于某个页面的临时数据,组件内请求、组件内维护,离开页面就释放,简单直接。客户端状态才是真正需要全局共享的,比如用户信息、权限码、侧边栏折叠状态、全局配置。这类状态跨页面、跨组件被反复使用,放进Pinia能保证单一数据源。
// 辨别是否需要全局状态 const needGlobalStore = [ '用户信息需要被所有模块读取', '权限码影响路由和按钮显示', '侧边栏折叠状态需要路由切换后保持', '语言/主题配置全局生效' ] const needLocalState = [ '当前列表页的筛选条件', '当前编辑弹窗的表单数据', '当前看板的图表参数' ]遵循这个边界后,我的Pinia store只有三个:userStore、permissionStore、appStore,而业务模块内部的状态全部用组件内ref或模块内store。代码清晰了很多,跨模块数据流也完全可控。
4.4 代码质量:lint、测试、提交规范一个都不能省
第18天的工作容易让人产生"这不重要"的错觉。但我自己的体会是,工程化沉淀才是支撑"21天后还能继续迭代"的关键。我做了三件事:ESLint规则、vitest单测、husky提交检查。
ESLint按推荐配置基础上,额外开启了vue/multi-word-component-names、@typescript-eslint/no-unused-vars等规则,团队提交代码前自动走一遍检查,明显减少无意识错误。一些可读性规则当时觉得严格,后来看历史代码才发现确实有用。
单测我用vitest覆盖了核心数据和工具函数:SKU规格组合、订单状态流转、权限判断、token刷新逻辑。这些纯函数逻辑复杂且容易回归,有单测兜底,后面改代码时才会安心。第14天做订单状态机时我先写了状态流转测试,再实现配置表,测试驱动开发在业务状态密集的场景非常适用。
husky配合commitlint,要求每次提交消息遵循规范格式。这一步一开始大家都不习惯,觉得被约束了。用了一周后反而离不开,因为提交历史一眼看清每个commit的意图,CI失败时定位问题快得多。
5. 第19-21天:AI驱动架构——从数据展示到辅助决策的最后一跃
5.1 先想清楚AI能在后台的哪个环节产生真实价值
AI驱动架构听起来高大上,但直接在大模型API上接一个聊天框,只会得到一个"看起来很酷但没人用"的后台。真正要做的是找到业务场景里AI能替代重复判断、提升决策效率的环节。我梳理了电商后台的几个切入点:
- 数据问答:运营用自然语言查数据,不用翻报表,比如"上周销量前10的商品是什么"
- 商品内容生成:根据商品参数自动生成标题、卖点描述,运营微调即可上架
- 异常提醒:用模型识别订单增长异常、库存预警、退款率突增,推送解释性结论
- 智能补货建议:综合销量趋势、库存、在途数据给出补货数量建议
选切入点时我定了一个标准:AI必须产出业务可直接使用的结果,而不是让用户再加工几轮。数据问答和数据异常解读最符合这个标准,所以我优先做了这两个场景。生成商品描述这类功能虽然好演示,但电商运营对文案有自己的品牌调性要求,AI产出往往只能当草稿,价值不够直接。
5.2 架构设计:在业务页面和大模型API之间加一条AI服务层
直接把大模型API调用散落在各个业务组件里是灾难性的。一个数据问答功能会出现在看板、报表、运营中心好几个地方,如果每个页面各自实现prompt组装、请求逻辑、流式解析,后续换模型、改prompt、加成本控制都会牵一发动全身。
我在features目录下单独建了一个ai模块,封装AI能力层,对外暴露语义化的方法,比如queryData(prompt)、analyzeTrend(params)、generateContent(template)。业务页面不关心底层用的是哪家大模型服务,也不关心提示词怎么拼。这一层内部统一处理:请求签名、输入校验、流式接收、错误降级、结果缓存。
// features/ai/service.ts 简化版 export class AIService { async queryData(question: string) { const metricContext = getMetricDictionary() const userPermission = checkDataPermission() const messages = [ { role: 'system', content: buildSystemPrompt(metricContext, userPermission) }, { role: 'user', content: question } ] // 统一解析SSE流,逐段返回给前端 const stream = await this.requestStream(messages) return stream } }页面拿到的是一个流式响应,我会用打字机的效果逐字展示,因为数据问答如果让用户等完整响应再一次性弹出,体验会很差。流式接收的用户感知延迟能做到800毫秒内出第一行字,几乎接近即时反馈。
5.3 提示词与业务数据组合:要让模型先理解"口径"再回答问题
数据问答功能看似简单,实际上最容易翻车。我问模型"这个月卖了多少单",模型如果不懂我们的订单统计口径,会把待付款订单也数进去,或者把退款单也算进销量。所以提示词里必须注入业务上下文和指标字典。
我在AI服务层里维护了一份指标字典配置,和看板模块共用同一份数据源,保证AI的回答和看板数字口径一致。系统提示词的写法要明确:哪些指标可用、定义是什么、过滤条件怎么处理、权限范围是什么。
const buildSystemPrompt = (metricDict, permission) => ` 你是一个电商后台数据分析助手。 可查询指标:${metricDict.map((m) => `${m.name}(${m.code},口径:${m.definition})`).join(';')} 当前用户权限范围:${permission.join('、')}。 回答要求: 1. 只能使用上述指标,如果无法回答请明确说"暂不支持该查询"。 2. 涉及金额统一使用订单实付金额口径,包含已取消订单请单独说明。 3. 如果问题需要趋势对比,默认返回最近7天按天粒度数据。 `把口径定义清楚写在提示词里,比让模型自由发挥靠谱得多。另外我也会对用户输入的查询做前置校验,识别出问别的模块数据或者权限外的指标,直接返回拒绝,而不是把未授权的上下文传给模型。
5.4 上线前必须处理的三个问题:成本、安全、降级
AI功能接入后台系统,工程上要提前处理三类问题,否则不敢推到生产环境。
成本控制方面,token用量直接决定账单。我在AI服务层做了三件事:对相同问题做结果缓存,键是归一化后的问句加指标配置版本号;对单用户单日调用次数做限额,超限后提示"今日额度已用完";对大模型响应结果做长度上限截断,防止模型跑题时输出一大篇无关内容。
安全方面要做三层防护:输入层校验用户参数,防止把恶意指令混进prompt;权限层做数据范围隔离,客服角色只能查到自己的订单,运营角色才能查全局数据;输出层做内容审核,模型生成的结果经过脱敏和合规检查再展示。运营后台的数据往往涉及用户隐私,比如手机号、详细地址,AI生成或者查询结果里不能直接输出这些内容。
降级方案同样重要。大模型服务偶尔会超时或者返回异常,不能因为AI挂了就影响整个后台使用。我的做法是给AI入口加feature flag,服务不稳定时前端直接隐藏AI入口,核心业务模块完全不受影响。同时AI服务层有超时兜底,请求超过10秒就返回"网络繁忙"并向运维发告警。
6. 项目跑起来之后,我复盘出的几条实操建议
21天结束后,系统的功能基本完备,AI能力也在真实环境跑了起来。复盘整个过程,有几点体会比代码本身更值得分享。
第一,接口约定一定要在写页面之前做。我前期和后端同学花了两天对齐所有接口的返回结构、错误码、分页格式,后面开发几乎没有因为联调返工过。最怕的是后端返回结构不统一,有的接口data直接是数组,有的包了一层list字段,前端每个页面都要写兼容逻辑。
第二,权限模型越早设计越好,哪怕前期只用了两个角色。我见过太多项目是菜单权限写死在路由表里,后面加了新角色只能到处打补丁。动态路由的方案第一天就做好,后面加角色加菜单都只是加数据的事。
第三,AI驱动不是简单的功能叠加,而是数据层的重新审视。做AI数据问答之前,我强迫自己把整个系统的指标口径整理了一遍,这个过程本身就很有价值,因为它让业务方和技术侧第一次严格对齐了"每个数字是怎么算的"。如果从一开始就在数据层用统一的指标字典,AI接进来会顺畅非常多。
第四,如果重来一遍,我会在第一周就建立一份数据字典文档。商品的SKU结构、订单状态枚举、指标口径、权限码规范,全部集中记录。这份文档不需要写得多复杂,但对于后续加入项目的同事来说,快速理解系统结构会非常宝贵。
从Vite工程化到AI驱动架构,这套后台管理系统最大的收获不是代码量,而是一条完整的方法路径。当你面对下一个后台类项目时,你会清楚地知道:边界在哪里、性能优化从哪下手、AI能力该沉淀在哪一层。把这些想明白了,21天只是一个起点,后面想往哪个方向发展,都由你决定。