最近刚把一个基于 Node.js + Vue 的在线商场后台管理系统完整做完,从前端页面到后端接口再到部署上线全部自己走了一遍,中间踩的坑不比写的代码少。这套系统覆盖了商品管理、订单管理、用户管理、角色权限和数据统计等模块,前端用 Vue 3 + Element Plus,后端用 Express + MySQL,属于典型的前后端分离项目。之所以要把这个过程整理出来,是因为我搜过很多资料,要么是纯前端教程,要么是纯后端示例,真正把一条链路串起来、告诉你每个设计点为什么这样做、上线会遇到什么问题的内容不多。如果你正打算做毕设、练手项目,或者公司要快速搭一个内部商城后台,这篇文章基本可以当一份落地参考。
1. 项目整体设计与技术选型背后的考量
1.1 为什么选 Node.js + Vue,而不是常见的大厂组合
很多人一听"后台管理系统",第一反应是 Spring Boot + Vue。这个组合确实成熟,后台管理系统如果涉及复杂业务、高并发、大规模团队协作,Java 生态的稳定性和可扩展性更强。但我这次项目情况不一样:团队都是前端背景,交付周期短,服务器配置也一般,客户端是内部运营人员,并发峰值能到几十就已经不错。这种场景下,Node.js 是最合适的,理由很直接:
- 前后端都是 JavaScript/TypeScript,数据结构可以共用,减少了联调时字段对不上导致的沟通成本;
- Express 或者 NestJS 把中间件模式玩得很透,写 REST API 的效率比想象中高;
- npm 生态里现成的包太多了,鉴权、校验、上传、导出基本都能找到轮子;
- 部署运维简单,一台小服务器跑 Node 服务加 Nginx 就足够。
等系统真的跑起来之后,我更确定这个选择是合理的。MySQL 里一个中等规模的商城后台,几百张表以内的项目,Node 异步 I/O 的能力完全够用。反过来,如果团队是 Java 背景,强行用 Node 反而不划算,因为熟练度才是效率第一要素。选型这件事,先想清楚人、时间、业务体量,再谈"前沿"。
1.2 商城后台的核心功能模块与页面规划
后台管理系统不同于用户端商城,它面向内部运营,核心诉求是"高效批量处理业务"。我在规划功能时没有照着某大平台后台抄,而是从"运营每天要干什么"倒推,最终收敛成六个核心模块:
| 模块 | 核心功能 | 页面数量 |
|---|---|---|
| 登录认证 | 账号密码登录、JWT 签发、登录状态保持 | 1 |
| 用户管理 | 用户列表、状态禁用、角色分配 | 2 |
| 商品管理 | 分类维护、商品列表、SPU/SKU 管理、上下架 | 4 |
| 订单管理 | 订单列表、订单详情、发货、退款处理 | 3 |
| 数据统计 | 销售趋势、订单量统计、热销商品排行 | 2 |
| 系统管理 | 菜单管理、角色权限、操作日志 | 3 |
这里有一个容易犯的错误:一开始就把模块划分得非常细,比如把优惠券、秒杀、会员积分全塞进来。结果往往是开发周期被拉长,很多页面做完根本没人用。我建议第一版先做能支撑"商品上架 -> 用户下单 -> 后台发货"这条主链路的功能,先把闭环跑通。优惠券、营销这些完全可以用插件式架构留好扩展点,后续再加。
1.3 数据库设计思路:SPU、SKU 与状态机
数据库设计是最不能省的一步。我见过太多项目代码写了一半发现缺字段、改表结构改到怀疑人生。商城后台的数据库设计,核心是把"商品"这个概念拆清楚。
商品要区分 SPU(标准化产品单元)和 SKU(库存量单位)。简单说,SPU 是"iPhone 15",SKU 是"iPhone 15 黑色 256G"。后台商品管理如果只建一张表,后续加颜色、加容量就得改表,非常痛苦。我这次用了四张核心商品表:category(分类)、spu(商品)、sku(规格库存)、sku_attr(规格属性值)。SPU 存公共信息,SKU 存价格、库存、规格组合。
订单表的状态字段是整个系统的核心之一,我用整数存状态,而不是字符串。状态值如下:
| 状态值 | 含义 | 可流转到的状态 |
|---|---|---|
| 10 | 待支付 | 20 已支付 / 90 已关闭 |
| 20 | 已支付 | 30 已发货 / 50 退款中 |
| 30 | 已发货 | 40 已完成 / 60 退货中 |
| 40 | 已完成 | 无 |
| 50 | 退款中 | 70 已退款 |
| 60 | 退货中 | 70 已退款 |
| 90 | 已关闭 | 无 |
用整数而不是字符串的好处是排序方便、逻辑比较快,而且不会因为中英文乱写导致状态判断出错。状态流转我放在后端 service 层控制,而不是随意 UPDATE,避免出现"已发货的订单还能退款"这种脏数据。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 搭建开发环境:从 Node 安装到项目跑起来,坑都在这里
2.1 Node.js 安装与环境配置,别去追最新版
新项目开始前,第一步是装 Node.js。很多新手喜欢去官网下载最新版,结果没过多久就遇到某个依赖不支持。我个人的建议是:装 LTS 版本,比如 Node 18 或 Node 20,不要装 Current 版本。为什么?LTS 版本经过长时间验证,npm 上大多数包都会优先保证兼容。Node 版本管理器 nvm 也建议用上,同一台机器多个项目可能需要不同版本,切换起来方便。
安装时有一点容易被忽略:Windows 安装包向导里第 2 步会有一个"Add to PATH"的勾选,一定要确保勾上,否则装完在终端里敲 node -v 会提示没找到命令。装完用两个命令验证:
bash复制node -v
npm -v
如果都能输出版本号,基础环境就 OK 了。接着建议做一件事:换 npm 镜像源。默认源在某些网络环境下下载容易失败,换成 npmmirror 之后快很多:
bash复制npm config set registry https://registry.npmmirror.com
npm config get registry
这里再说一个常见问题:如果项目里要用 Sass 编译样式,尽量不要装 node-sass,这个包的二进制文件下载经常失败,而且和 Node 版本强绑定。现在直接用 sass(dart-sass)就行,安装编译几乎没有兼容问题。
注意:网上很多教程让你直接改 package.json 里的 dependencies,然后 npm install,依赖冲突时可能会越改越乱。更稳妥的方法是先把项目框架搭好,再逐个安装核心依赖。
2.2 PowerShell 报错:npm.ps1 因为禁止运行脚本
我这次项目一开始就遇到一个让人崩溃的报错,也是网上被问烂的问题。在 Windows 的 PowerShell 里执行 npm install,弹出这么一串:
powershell复制npm : 无法加载文件 D:\Program Files\nodejs\npm.ps1,因为在此系统上禁止运行脚本。
这个报错和 Node 本身没关系,是 PowerShell 的执行策略默认是 Restricted,不允许运行 .ps1 脚本。npm 在 PowerShell 里的入口就是一个 .ps1 文件,所以被拦住了。
解决方式有两种。第一种是永久解除限制,用管理员身份打开 PowerShell,执行:
powershell复制Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
RemoteSigned 表示本地脚本可以运行,从网上下载的脚本需要签名,这个策略对开发来说足够安全。第二种是干脆不用 PowerShell,改用 CMD 或者 Git Bash,里面运行 npm 走的是 .cmd 文件,不会触发这个限制。
当时同事还遇到一个更隐蔽的情况:执行了 Set-ExecutionPolicy 之后依然报错,后来发现是执行策略被组策略锁定了。这种情况需要看注册表或者组策略配置,但绝大多数个人电脑不会碰到,普通情况用上面的命令就能解决。
2.3 用 Vue CLI 或 Vite 创建项目,依赖版本别乱配
当前 Vue 生态最主流的是 Vue 3,配套 UI 库是 Element Plus,状态管理是 Pinia。如果你看的旧教程还在用 Vue 2 + Element UI + Vuex,做新项目时建议切换到新版,API 更清晰,而且动态路由、组合式函数这些特性写后台很顺手。
创建项目我用的是 Vite:
bash复制npm create vue@latest mall-admin
按需选择 Router、Pinia、ESLint 等特性,Vite 会把基础工程搭好。也可以使用 vue ui 图形化界面创建,适合不熟悉命令行的同学。
项目创建完,安装基础依赖:
bash复制npm install element-plus axios pinia echarts dayjs nprogress
注意安装依赖的时候,包与包之间是有版本兼容问题的。比如 Element Plus 要求 Vue 版本必须大于某个值,如果一起安装报 peer 依赖冲突,建议先创建项目再逐个安装核心库,看到报错再针对性锁定版本。在这里栽过的人不在少数,反复卸了装、装了卸,其实往往是 package.json 里的版本写得太松或太死导致的。
3. 后端接口实现:登录鉴权、商品 CRUD、订单状态机
3.1 后端目录分层:宁可多一点,不要全堆在入口文件
后端我用 Node.js + Express,ORM 选了 Sequelize,数据库用 MySQL 5.7。为什么不直接用原生 mysql2 写 SQL?商城后台 CRUD 接口多,用 ORM 可以减少大量样板代码,模型关系和软删除也方便。不过复杂统计查询我仍然会用 sequelize.query 写原生 SQL,这一点不冲突。
目录结构我是这样分的:
text复制server/
├── app.js
├── config/
│ └── index.js
├── models/
│ ├── index.js
│ ├── product.js
│ ├── order.js
│ └── ...
├── middlewares/
│ ├── auth.js
│ ├── errorHandler.js
│ └── validator.js
├── routes/
│ ├── auth.js
│ ├── product.js
│ ├── order.js
│ └── ...
├── controllers/
│ ├── authController.js
│ ├── productController.js
│ └── ...
└── services/
├── orderService.js
└── ...
分层的好处是职责单一:routes 只做路径映射,controller 拿参数、调 service、返回响应,service 写业务逻辑,models 层只负责数据模型定义。一开始可能觉得文件多,等加一个需求时就知道好处了,改订单逻辑不用去路由文件里翻。
app.js 里的核心配置就这些:cors、express.json、路由挂载、统一错误处理中间件。顺序很重要,错误处理中间件一定放在所有路由后面,否则 Express 4 里路由抛出的异常不会被捕获。
3.2 JWT 登录认证与中间件权限校验
后台系统的第一道关卡是登录。用户提交账号密码,后端校验通过后签发 JWT,前端把 token 存在本地,每次请求带上就完成一次鉴权。JWT 无状态,适合后台这种服务端不需要保留 session 的场景。
密码存储不能明文,用 bcryptjs 哈希。登录的代码逻辑大概是:
js复制const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
// 校验密码
const isValid = bcrypt.compareSync(password, user.password_hash);
if (!isValid) {
return res.status(400).json({ code: 400, message: '账号或密码错误' });
}
// 签发 token
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '2h' }
);
JWT_SECRET 一定放在 .env 文件里,不要写死在代码里,否则代码一旦泄露,token 就可以被伪造。token 过期时间我设置了 2 小时,后台运营人员用太久反而容易出安全风险,过期后前端用响应拦截器里拿到的 401 跳回登录页。
权限校验用一个中间件即可:
js复制function auth(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) return res.status(401).json({ code: 401, message: '未登录' });
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = payload;
next();
} catch (e) {
return res.status(401).json({ code: 401, message: '登录已过期' });
}
}
需要管理员以上角色才能访问的接口,在中间件里再判断一次 req.user.role 就行。这里要提醒自己一件事:权限校验必须做在后端,不能只靠前端菜单隐藏,否则别人直接调接口就能越权。
3.3 商品管理接口:分页、筛选、排序的通用写法
商品列表接口是后台系统里最典型的"列表页"接口。设计要点有三个:分页、筛选字段、排序。前后端约定所有列表接口都返回同一结构:
json复制{
"code": 0,
"message": "ok",
"data": {
"list": [],
"total": 128,
"page": 1,
"pageSize": 10
}
}
接口定义成 GET /api/v1/products,查询参数包含 page、pageSize、keyword、categoryId、status。后端实现时,先用一个对象收集所有合法查询条件,再传给 Sequelize:
js复制const where = {};
if (keyword) {
where.name = { [Op.like]: `%${keyword}%` };
}
if (categoryId) where.category_id = categoryId;
if (status) where.status = status;
const { rows, count } = await Product.findAndCountAll({
where,
limit: pageSize,
offset: (page - 1) * pageSize,
order: [['create_time', 'DESC']],
});
findAndCountAll 一次查出当前页数据和总数,避免再写一条 count 查询。注意 keyword 用模糊搜索时,如果字段没有索引,数据量大后会很慢,可以在名称字段上加普通索引。
商品上下架状态我用 0 表示下架、1 表示上架,前台用户端只能看到 status=1 的商品,这个约束在后端查询里做,不能指望前端页面传参控制。
3.4 订单状态机与统计接口
订单管理的核心不是 CRUD,而是状态流转。设计上我把订单状态机收敛在 orderService 里,操作订单的方法名就是业务动作,比如 confirmOrder、shipOrder、refundOrder。每个方法内部先检查当前状态是否允许该操作,再执行更新,这样订单不可能出现"从待支付直接变成已完成"的情况。
统计模块我做了三个接口:销售趋势、订单量趋势、热销商品排行。销售趋势的 SQL 大致是这样:
sql复制SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS day,
SUM(pay_amount) AS amount
FROM orders
WHERE status IN (20, 30, 40)
AND create_time BETWEEN :start AND :end
GROUP BY DATE_FORMAT(create_time, '%Y-%m-%d')
ORDER BY day;
这里最容易被坑的是时区。服务器和 MySQL 如果时区不一致,统计出来的日期可能整体偏移一天。我最后统一把应用和数据库都设置成 Asia/Shanghai,字段类型也用 datetime,避免 timestamp 自动转时区带来的混乱。
4. 前端页面:动态路由、表格插槽、请求封装
4.1 通过路由守卫动态生成菜单和权限路由
后台系统前端最重要的一段逻辑,是让不同角色看到不同菜单。实现方式有很多,我采用后端返回菜单数据、前端根据数据动态注册路由的方案。
用户登录后,后端返回该角色可访问的菜单列表,结构包括:
json复制[
{ "path": "/dashboard", "name": "Dashboard", "meta": { "title": "数据概览" } },
{ "path": "/product", "redirect": "/product/list", "meta": { "title": "商品管理" } }
]
前端在 Pinia 里存一个 routeList,路由守卫判断当前用户是否已经注册了动态路由,如果没有,调用 addRoute 批量注册,再跳转。关键代码如下:
js复制router.beforeEach(async (to) => {
const userStore = useUserStore();
if (!userStore.token) {
return to.path === '/login' ? true : '/login';
}
if (!userStore.isRoutesLoaded) {
const menus = await getMenus();
menus.forEach(menu => router.addRoute(mapToRoute(menu)));
router.addRoute({ path: '/:pathMatch(.*)*', component: NotFound });
userStore.isRoutesLoaded = true;
return to.fullPath;
}
return true;
});
注意一个细节:404 页面必须在所有动态路由注册完之后再 addRoute,否则刷新页面时会因为动态路由还没加上,把正常页面误判成 404。
刷新导致动态路由丢失是新手最常问的问题。路由和菜单数据都存在 Pinia,一刷新内存就清空了。解决方案就是把菜单数据持久化到 localStorage,或者每次刷新后重新拉取一次。我采用重新拉取的方式,保证权限一变,刷新后立即生效,不用清缓存。
侧边栏菜单部分,我用 el-menu 绑定动态路由转换后的菜单树。关键点是 active 状态要和当前路由联动,用 route.path 作为 default-active 值,路由切换时菜单自动高亮;如果菜单有隐藏路由(比如商品详情页),需要配置一个 meta.activeMenu 字段指定高亮父菜单,否则详情页打开后左侧菜单会掉高亮。
4.2 Element Plus 表格和插槽:后台页面的核心组件用法
后台管理系统大量页面无非是"左侧菜单 + 顶部栏 + 表格 + 弹窗 + 表单"。Element Plus 的 el-table 足够强大,但也需要一点技巧,尤其是插槽。
商品列表里,我需要在表格里展示状态标签、缩略图、操作按钮,这些场景用插槽最合适:
vue复制<el-table :data="productList">
<el-table-column prop="name" label="商品名称" min-width="200" />
<el-table-column label="封面图" width="90">
<template #default="{ row }">
<el-image :src="row.cover" fit="cover" style="width: 50px; height: 50px;" />
</template>
</el-table-column>
<el-table-column label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'info'">
{{ row.status === 1 ? '上架' : '下架' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }">
<el-button link type="primary" @click="openEdit(row)">编辑</el-button>
<el-button link type="danger" @click="toggleStatus(row)">下架</el-button>
</template>
</el-table-column>
</el-table>
用插槽的好处是不用再手动拼 HTML,而且作用域里的 row 可以拿到当前行的完整数据,操作事件绑定起来非常直观。有一点要提醒:如果表格列很多,操作列建议加 fixed="right",不然横向滚动时用户找不到按钮。
4.3 Axios 封装:请求拦截、响应拦截与统一错误处理
前端所有接口请求必须走同一个 Axios 实例,不要每个页面直接 import axios 用,否则 token 注入、错误提示、接口前缀这些逻辑会重复写,改一处漏一处。我的封装思路是:
js复制const service = axios.create({
baseURL: '/api/v1',
timeout: 10000
});
// 请求拦截器:注入 token
service.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 响应拦截器:统一处理业务状态码
service.interceptors.response.use(
res => {
const code = res.data.code;
if (code === 0) return res.data.data;
if (code === 401) {
router.push('/login');
return Promise.reject(new Error('未登录或登录过期'));
}
ElMessage.error(res.data.message || '请求失败');
return Promise.reject(new Error(res.data.message));
},
err => {
if (err.response?.status === 401) {
router.push('/login');
}
ElMessage.error(err.response?.data?.message || '网络异常');
return Promise.reject(err);
}
);
这里最容易被忽略的是,后端返回的业务错误和 HTTP 状态错误是两条路径。业务层的 code 不等于 HTTP status,我在后端设计了 code=0 表示成功、code=401 表示未登录,这样前端拦截器里统一判断一次就够了,而 HTTP 层只处理网络层面的异常。
还有一个上传文件的坑:用 FormData 上传时,不要手动设置 Content-Type,浏览器会自动加上带 boundary 的 multipart/form-data。手动设置反而会让后端解析不到文件,这个坑我调试了大半天。
4.4 数据统计页面接入 ECharts
统计页面是后台项目的"门面",领导打开系统第一眼就是看图表。我用的 ECharts,接入方式不复杂:npm 安装 echarts,在组件里用 ref 挂载 DOM,mounted 里初始化,接口数据返回后 setOption。
需要注意两点。一是容器必须有明确高度,否则图表不显示;二是在组件销毁前调用 instance.dispose() 释放资源。如果用 resize 事件,要在销毁时移除监听,否则容易造成内存泄漏。封装一个通用 Chart 组件可以把这些细节集中处理,表格和图表类组件越沉淀越好维护。
5. 前后端联调、跨域与部署上线
5.1 本地联调通过代理解决跨域
开发阶段前端跑在 5173 端口,后端跑在 3000 端口,直接让前端请求 http://localhost:3000 会触发跨域问题。最简单的办法不是在 Axios 里写完整地址,而是所有请求都指向 /api 前缀,然后由开发服务器把 /api 代理到后端。Vite 的配置如下:
js复制server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
}
这样开发阶段前端代码里不需要出现后端地址,以后部署也是同一个模式:浏览器访问 Nginx,Nginx 把 /api 转到 Node 服务。前后端在浏览器眼里永远同源,省去了大量跨域烦恼。
后端仍然要加上 cors 中间件,防止以后有别的客户端(比如小程序管理端)直接调用接口。cors 配置 origin 白名单时,注意如果使用了带凭证的请求,不能写成 *,必须明确允许的域名。
5.2 数据库连接、端口占用、token 过期:三类问题排查实录
联调阶段问题最多,这里把遇到比较有代表性的几个问题列出来。
数据库连不上
现象是 Sequelize 报 ECONNREFUSED。大概率不是代码问题,是 MySQL 没启动,或者账号密码配置错。还有一个隐藏原因:MySQL 8 默认加密插件是 caching_sha2_password,老版本 mysql2 不支持,解决办法是升级 mysql2 版本,或者在创建用户时指定 mysql_native_password。
端口被占用
启动后端时报 EADDRINUSE,说明 3000 端口已经被别的进程占用。Windows 下用以下命令找到进程并结束:
bash复制netstat -ano | findstr :3000
taskkill /F /PID <进程号>
token 过期导致页面循环跳转
token 过期后,请求 401,拦截器跳转登录页。如果登录页也调了需要鉴权的接口,就会死循环。解决办法是拦截器里判断当前路由,如果是 /login 就不再进行跳转,同时清空本地过期 token。
5.3 上线部署:PM2 守护进程 + Nginx 反向代理
部署方案很简单:一台云服务器,装 Node、MySQL、Nginx。前端打包后用 Nginx 托管静态文件,后端用 PM2 守护。整体访问链路是:
text复制浏览器 -> Nginx(80端口) -> 前端静态文件
└-> /api 代理 -> Node(3000端口)
前端打包:
bash复制npm run build
把生成的 dist 目录上传到服务器,例如 /var/www/mall-admin。Nginx 配置的关键点:
nginx复制server {
listen 80;
server_name admin.example.com;
root /var/www/mall-admin/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
try_files 那行是 Vue Router history 模式必须的,否则刷新某个子路由页面会出现 404。
后端启动用 PM2:
bash复制pm2 start app.js --name mall-admin-api
pm2 save
pm2 logs mall-admin-api
PM2 的守护能力很重要,Node 进程挂掉后能自动重启。上线后第一件要做的事就是看日志,确认接口请求正常、数据库连接没有异常,再让运营同事开始用。
6. 项目复盘:哪些设计被证明是对的,哪些是过度设计
6.1 值得保留的设计
接口统一返回结构、分页参数一套标准、后端强制权限校验、订单状态机放在 service 层,这四个设计在整个开发过程中被反复验证是正确的。哪怕后面页面改版、字段调整,接口层和业务层的稳定性让改动成本变得很低。尤其是订单状态机,如果没有一开始就设计好,后期改退款逻辑非常容易出数据问题。
另一个体会是,目录分层不用太死板,但是分层的思想必须要有。不一定要 controller/service/repository 全套,哪怕只是把路由和业务函数分开,也比全部堆在 app.js 里强出一个量级。
6.2 可以避免的过度设计
反过来,我也在这次项目里做了不少多余的事。比如一开始就写了一个自定义的通用 CrudController,想通过配置自动生成增删改查接口,结果业务一复杂就发现大量地方要单独重写,通用逻辑反而成了障碍。后台系统的 CRUD 看似重复,实际每个模块的筛选条件、校验逻辑、关联查询都不同,过早抽象会把自己绕进去。
所以我现在更推荐的做法是:先用最直接的方式把每个业务接口写完,当发现 80% 的列表页确实长得一样时,再去抽公共的表格组件和查询 hook。抽象要跟随业务重复出现,而不是凭空预测。
6.3 给同样要做这个项目的人几点建议
- 先设计数据库,再写一行业务代码。表关系不清晰,后面所有接口都会塌。
- 前端请求封装、路由守卫这些基础设施,必须在写页面之前搭好,不然会返工。
- 不要把敏感配置提交到 Git,.env 文件和 node_modules 一样要加入 .gitignore。
- 日志和错误上报不是可选项,上线第一天就配置好 pm2 logs 和简单告警。
- 如果 Node 版本到了 22 以上,可以直接用 --experimental-strip-types 跑 TypeScript 文件,不用额外编译,做小项目很方便。
最后分享一个我在实际开发里的小技巧:当后端接口和前端的字段命名不统一时,不要靠前端做大量映射,优先改后端让接口返回结构保持稳定。前后端分离的项目,接口契约比任何代码规范都重要。这个商城后台做到现在,我已经可以很轻松地在上面新增模块了,因为骨架已经稳定,后面继续扩展的逻辑也就没那么难了。
