资讯动态

Node.js+Vue+ElementUI实践:旅游门票与酒店预订系统全栈开发

发布时间:2026/9/9 15:00:06 来源:尧图企业网站定制
去年完整做了一个旅游景点门票购买和酒店预订系统技术栈是 Node.js Vue ElementUI从需求梳理到上线部署都是自己一个人撸的。做之前以为就是个普通的 CRUD 管理后台真正动手才发现这套系统的核心难点全在业务链路设计上——景点信息和酒店数据可以随便展示但“选景点/酒店 → 查余票/查房态 → 下单 → 支付 → 核销/入住”这条链路一旦断掉项目就是一堆页面壳子。这篇文章把整个项目的设计思路、技术选型、数据库设计、核心功能实现、环境搭建和踩坑记录全部整理出来。如果你正在做类似的门票预订、酒店预订、场馆预约这类系统或者刚接触 Node.js 和 Vue 的全栈开发可以直接照着这个思路走少走很多弯路。1. 项目整体定位与核心业务拆解1.1 这类系统到底在解决什么问题旅游景点门票购买和酒店预订系统放到业务层面看其实是一个典型的“商品交易 库存管理”系统。景点门票是商品酒店房间也是商品只不过它们都带有时间和库存两个维度门票通常绑定日期和场次酒店房间绑定入住离店日期库存都是有限资源。我一开始把重点放在页面样式上觉得 ElementUI 表格和表单做漂亮点就行。做完一版拿给朋友试用发现问题一堆用户下单同一时段的门票库存扣重了酒店房间被两个订单同时占用订单状态只有“未支付”和“已支付”退改签完全没法处理。后来才意识到这类系统的灵魂是订单状态机和库存扣减策略页面只是表皮。所以如果你要做类似系统先别急着写代码把下面几个业务问题想清楚用户从浏览到完成支付中间需要哪些步骤门票和房间的库存什么时候扣减、什么时候释放订单有哪几种状态状态之间怎么流转管理员需要哪些权限用户需要哪些功能把这些想透后面开发会顺畅很多。1.2 功能模块梳理我把整个系统拆成了用户端和管理端两个大模块。用户端包括注册登录、首页景点和酒店展示、景点详情与门票选择、酒店详情与房型选择、下单支付、个人订单中心。管理端包括景点管理、门票管理、酒店管理、房间管理、订单管理、用户管理。这个拆分方式比较常规但有一个很关键的设计原则用户端和管理端共用同一套后端接口只是通过角色权限控制访问范围。我见过不少项目把用户端和管理端做成两套独立系统接口也各写一份维护成本直接翻倍。实际上用 Node.js 写接口前端用 Vue 做两个工程共用一套 API效率高得多。权限控制用最简单的中间件方案就可以搞定用户登录后返回一个 token前端请求时带上后端中间件解析 token再判断当前用户的角色是普通用户还是管理员决定是否放行。2. 技术选型解析为什么是这套组合2.1 后端为什么选 Node.js这个项目用 Node.js 做后端不是因为它能写出性能天花板级的服务而是因为它非常适合这类“业务逻辑清晰、接口数量多、开发周期紧”的项目。Node.js 用的是事件驱动和非阻塞 I/O 模型对 IO 密集型场景很友好。旅游系统的接口大部分是查数据库、查缓存、返回 JSON这类操作耗时主要在 IO 而不是 CPU 计算Node.js 的表现很合适。再加上 JavaScript 全栈的统一性——前端写 Vue 用 JavaScript后端写 Node.js 还是 JavaScript类型心智负担小上下文切换成本低。我自己做这个项目的感受是后端接口调不通的时候直接在前端 debugger 打断点沿着请求链一路查到 controller 层全程不需要切换语言思维。另一个重要原因是 npm 生态。Express 框架本身很轻自己往上加中间件做路由、日志、鉴权、参数校验整个过程可控制很强。相比 Java Spring Boot 全家桶Node.js 项目的启动和迭代速度快太多适合个人开发者或小团队快速落地。2.2 Vue 和 ElementUI 的组合价值Vue 在国内中小型项目里的统治力不用多说它的响应式数据绑定和组件化开发方式对快速搭建交互复杂的页面帮助很大。ElementUI 是 Vue 2 生态里最成熟的桌面端组件库表格、表单、弹窗、分页、日期选择器、上传组件这些都是现成的省掉大量造轮子的时间。做管理端的时候ElementUI 的价值特别明显。el-table 自带排序、多选、自定义列模板el-form 配合验证规则做表单校验el-pagination 处理分页这些组件拼装起来就是一套完整的管理界面。我在做订单管理页的时候用 el-table 展示订单列表el-tag 显示订单状态el-dialog 做订单详情弹窗el-select 做状态筛选大概一个下午就把页面功能做完了换成手写 HTML 不敢想。不过 ElementUI 也有需要注意的地方最典型的就是数据更新后页面不刷新的问题。这个后面在常见问题板块细说这里先记住一个原则ElementUI 是基于 Vue 2 的Vue 2 的响应式系统对新增属性和数组下标操作存在检测盲区遇到数据变了页面没反应的情况优先考虑用this.$set解决。2.3 前后端分离架构的设计考虑这套系统用的是标准的前后端分离架构后端提供 RESTful API返回 JSON 数据前端 Vue 工程通过 axios 调用接口。前端工程用 Vue CLI 构建开发时通过代理转发请求到后端避免跨域问题生产环境用 Nginx 托管前端静态文件同时反向代理 API 请求到 Node.js 服务。我重点说一下代理配置这是新人最容易卡住的地方。开发环境下前端跑在 8080 端口后端跑在 3000 端口直接请求肯定跨域。我的做法是在 Vue 项目的vue.config.js里配置 devServer 代理module.exports { devServer: { proxy: { /api: { target: http://localhost:3000, changeOrigin: true } } } }这样前端请求/api/user/login时开发服务器会把请求转发到http://localhost:3000/api/user/login浏览器端不会感知到跨域。生产环境同理Nginx 配置一个 location 把/api路径转发到后端服务就行。这种架构的好处是前后端可以并行开发。我一开始先把接口文档写好后端用 Postman 自测通过后前端同事其实就是我自己再开始对接出了问题直接定位到具体层调试效率高很多。3. 数据库设计与接口规划3.1 数据表设计核心思路数据库我用的是 MySQL 8.0表结构设计是整个项目里最需要耐心的部分。下面几张表是系统的核心我按业务链路逐个说明。用户表要包含基本信息但要区分角色用户角色和和管理员角色。我在用户表加了一个role字段0表示普通用户1表示管理员权限判断全靠这个字段。景点表主要存景点的基础信息标题、描述、封面图、所在城市、具体地址、开放时间、联系电话。要注意的是景点和门票不是一对一的关系。一个景点往往有多种门票比如成人票、儿童票、学生票或者普通票和联票所以景点表和门票表是一对多关系门票表通过scenic_id外键关联到景点表。门票表除了名称和价格还要有库存字段。这里的库存我建议按日期维度去设计因为门票通常是分日期售卖的今天有票不代表明天有票。我用了date字段加stock字段的组合每天每种门票一条库存记录下单时锁定对应日期的库存。这个设计在实现时会有点繁琐但非常必要。酒店表和房间表也是类似的一对多关系。房间表要记录房型名称、面积、床型、设施、价格、剩余房间数。酒店的库存和门票不太一样酒店房间的库存是“连续区间”概念——用户预订 6 月 1 日到 6 月 3 日两晚这期间房间都是占用状态。订单表是重中之重字段多且逻辑复杂。除了关联用户、景点或酒店的 ID还要记录订单类型、数量、总金额、支付状态、订单状态、创建时间、支付时间等。我额外加了一个order_no字段用时间戳加随机数生成唯一的订单号方便后续对账和用户查询。核心表的字段明细如下表名关键字段说明userid, username, password, role, phone, create_timerole 区分用户和管理员scenicid, name, description, cover, city, address, open_time景点基础信息ticketid, scenic_id, name, price, date, stock每天每种门票一条库存记录hotelid, name, description, cover, city, address, stars酒店基础信息roomid, hotel_id, name, price, stock, area, bed房间类型和总库存ordersid, order_no, user_id, type, item_id, date_start, date_end, quantity, amount, statustype 区分门票/酒店item_id 存对应业务 ID3.2 接口设计与权限控制接口设计遵循 RESTful 风格资源用名词复数操作通过 HTTP 方法区分。用户模块提供注册、登录、获取用户信息接口景点模块提供列表、详情、门票列表接口酒店模块类似订单模块提供创建订单、支付、取消、查询列表、查询详情接口管理端额外提供景点新增修改删除、酒店新增修改删除、订单状态修改等接口。权限控制我用 JWT 方案。登录成功后端返回一个 token前端存在 localStorage每次请求在 header 里带Authorization: Bearer token。后端写一个authMiddleware解析 token把用户信息挂到req.user上。管理员接口再套一层adminMiddleware判断req.user.role是否为管理员。这个方案本身不复杂但我踩过一个坑订单创建接口必须校验用户身份不能只靠前端传用户 ID。第一版我用前端传userId的方式创建订单结果发现在浏览器开发者工具里改请求参数就能给别人的账户下单。后来改成从 token 里解析用户 ID前端只管提交商品数据用户身份完全由后端确认这个漏洞才堵上。3.3 订单状态机设计订单状态是整个系统最容易出问题的地方。我设计的状态流转如下待支付订单创建成功但未支付已支付用户完成支付等待消费或入住已完成门票已核销或酒店已退房已取消用户主动取消或超时未支付自动取消已退款管理员退款后置为退款状态订单创建和支付之间应该设置一个支付超时时间。我最初没有做这个限制导致大量脏数据堆积——用户创建了订单不支付库存一直占着后面真实用户买不到票。后来加了一个定时任务超过 30 分钟未支付的订单自动取消库存回滚情况立刻好转。库存扣减的逻辑也需要统一。我采用的是“创建订单时锁定库存支付成功后正式扣减取消时释放库存”。这个方案的优点是简单直观缺点是会有少量库存被中间状态的订单占用但因为设置了超时自动取消影响很小。如果是高并发场景可以考虑 Redis 做分布式锁但 99% 的旅游项目用不上这个复杂度。4. 开发环境搭建与初始化实操4.1 Node.js 安装与环境变量配置Node.js 的安装本身不复杂但环境配置有不少细节。官网下载 LTS 版本安装包一路下一步安装即可安装完成后会自动把 Node.js 和 npm 添加到系统 PATH。验证是否安装成功打开终端执行node -v npm -v如果能输出版本号说明 Node.js 安装成功。如果提示“node 不是内部或外部命令”说明环境变量没有配置好。手动打开系统环境变量设置在 PATH 里添加 Node.js 的安装目录比如C:\Program Files\nodejs保存后重新打开终端。npm 默认镜像源在国外国内下载依赖速度不稳定。我建议安装后第一时间切换镜像源npm config set registry https://registry.npmmirror.com执行完可以查看当前源确认是否生效npm config get registry这一步看起来不起眼实际开发中能省下大量等待时间。npm 下载 Electron、Puppeteer 这类带二进制包依赖时就算切换了镜像也可能卡住遇到这种问题再单独配npm config set electron_mirror之类的镜像属于特殊情况单独处理。4.2 解决 npm 脚本执行权限报错安装完 Node.js 后很多人第一次在终端执行 npm 命令时会报错npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1 因为在此系统上禁止运行脚本。这个问题的根源是 Windows PowerShell 的脚本执行策略限制默认情况下禁止运行.ps1脚本。npm 命令在 PowerShell 里实际是通过npm.ps1脚本执行的被安全策略拦下来。我当时的解决办法是用管理员身份打开 PowerShell执行Set-ExecutionPolicy -Scope CurrentUser RemoteSigned然后输入Y确认。RemoteSigned策略表示本地脚本可以运行从网上下载的脚本必须有数字签名。这个策略对日常开发完全够用。如果你不想修改执行策略还有一个临时办法在命令提示符 CMD 里执行 npm 命令不受 PowerShell 策略限制。但长期开发下来还是在 PowerShell 里跑命令更方便建议直接设置执行策略。4.3 前端工程创建与 ElementUI 引入前端工程我用 Vue CLI 创建。先全局安装脚手架npm install -g vue/cli创建项目vue create travel-front创建过程中会询问预设配置选择 Manually select features勾选 Router 和 Vuex如果不需要可以只选 RouterVue 版本选择 2.x。创建完成后cd travel-front npm run serve看到编译成功提示浏览器访问http://localhost:8080就能看到默认页面。接下来安装 ElementUInpm i element-ui -S然后在main.js里完整引入import Vue from vue import App from ./App.vue import router from ./router import ElementUI from element-ui import element-ui/lib/theme-chalk/index.css Vue.use(ElementUI) Vue.config.productionTip false new Vue({ router, render: h h(App) }).$mount(#app)虽然 ElementUI 支持按需引入配合 babel-plugin-component但我个人建议新手阶段先完整引入跑通整个流程等项目大了再考虑按需引入优化打包体积。完整引入的方式配置少、不容易出错维护起来也省心。4.4 后端工程初始化后端我用 Express 框架。手动创建项目目录然后初始化mkdir travel-server cd travel-server npm init -y npm install express mysql2 jsonwebtoken cors body-parser项目目录结构根据自己的习惯组织我用的分层结构比较清晰travel-server/ ├── app.js # 应用入口加载中间件和路由 ├── config/ │ └── db.js # 数据库连接配置 ├── routes/ # 路由定义 │ ├── user.js │ ├── scenic.js │ ├── hotel.js │ └── order.js ├── controllers/ # 控制器处理业务逻辑 ├── middleware/ │ ├── auth.js # token 鉴权中间件 │ └── admin.js # 管理员权限中间件 └── utils/ └── response.js # 统一响应格式app.js核心代码const express require(express) const cors require(cors) const bodyParser require(body-parser) const userRouter require(./routes/user) const scenicRouter require(./routes/scenic) const hotelRouter require(./routes/hotel) const orderRouter require(./routes/order) const app express() app.use(cors()) app.use(bodyParser.json()) app.use(/api/user, userRouter) app.use(/api/scenic, scenicRouter) app.use(/api/hotel, hotelRouter) app.use(/api/order, orderRouter) app.listen(3000, () { console.log(Server running at http://localhost:3000) })这样注册路由的方式模块化程度高每个业务模块各管各的接口后期加功能不用动主文件。5. 核心功能实现门票购买与酒店预订全流程5.1 用户注册登录与 token 鉴权注册登录是系统的入口逻辑不算复杂但安全性细节不能省。密码不能明文存数据库我用bcryptjs做哈希处理。安装依赖npm install bcryptjs注册接口的核心代码const bcrypt require(bcryptjs) const jwt require(jsonwebtoken) // 注册req.body 包含 username 和 password async function register(req, res) { const { username, password, phone } req.body // 检查用户名是否已存在 const [rows] await db.query(SELECT id FROM user WHERE username ?, [username]) if (rows.length 0) { return res.json({ code: 1, msg: 用户名已存在 }) } // 密码加盐哈希 const salt bcrypt.genSaltSync(10) const hash bcrypt.hashSync(password, salt) // 插入数据库 await db.query(INSERT INTO user (username, password, phone, role) VALUES (?, ?, ?, 0), [username, hash, phone]) res.json({ code: 0, msg: 注册成功 }) }登录接口验证密码后签发 token// 登录 async function login(req, res) { const { username, password } req.body const [rows] await db.query(SELECT * FROM user WHERE username ?, [username]) if (rows.length 0) { return res.json({ code: 1, msg: 用户不存在 }) } const user rows[0] // 比对密码 const isValid bcrypt.compareSync(password, user.password) if (!isValid) { return res.json({ code: 1, msg: 密码错误 }) } // 签发 token过期时间设 7 天 const token jwt.sign( { id: user.id, username: user.username, role: user.role }, your-secret-key, { expiresIn: 7d } ) res.json({ code: 0, msg: 登录成功, data: { token, username: user.username, role: user.role } }) }前端 axios 请求拦截器统一带上 tokenaxios.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer token } return config })5.2 景点门票购买流程实现门票购买流程是用户浏览景点列表点进详情页看门票类型和日期余票选择日期和数量提交订单模拟支付。门票列表接口返回某个景点所有日期的门票库存async function getTickets(req, res) { const { scenicId, date } req.query const [rows] await db.query( SELECT * FROM ticket WHERE scenic_id ? AND date ?, [scenicId, date] ) res.json({ code: 0, data: rows }) }前端选择日期后重新请求门票数据ElementUI 的el-date-picker绑定日期监听 change 事件触发数据加载。这里要注意把日期格式化为YYYY-MM-DD格式再传给后端否则查询可能匹配不上。创建订单接口async function createOrder(req, res) { const userId req.user.id // 从 token 解析而不是前端传 const { type, itemId, dateStart, dateEnd, quantity } req.body // 根据订单类型查价格 let amount 0 let stockField if (type ticket) { const [ticket] await db.query(SELECT * FROM ticket WHERE id ?, [itemId]) if (!ticket) return res.json({ code: 1, msg: 门票不存在 }) amount ticket.price * quantity stockField ticket.stock } // 检查库存是否充足 if (stockField quantity) { return res.json({ code: 1, msg: 库存不足 }) } // 生成订单号并插入订单记录 const orderNo Date.now() Math.floor(Math.random() * 10000) await db.query( INSERT INTO orders (order_no, user_id, type, item_id, date_start, date_end, quantity, amount, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?), [orderNo, userId, type, itemId, dateStart, dateEnd, quantity, amount, pending] ) // 扣减库存 await db.query(UPDATE ticket SET stock stock - ? WHERE id ?, [quantity, itemId]) res.json({ code: 0, msg: 订单创建成功, data: { orderNo } }) }这里需要留意事务问题。创建订单和扣减库存是两个数据库操作理想情况下应该在同一个数据库事务里避免出现订单创建成功但库存扣减失败的中间状态。我用mysql2的 promise 连接时可以手动开启事务const conn await db.getConnection() try { await conn.beginTransaction() // 执行创建订单和扣减库存的 SQL await conn.commit() } catch (err) { await conn.rollback() throw err } finally { conn.release() }我第一版没有开事务测试时用脚本并发下单确实出现了超卖的情况。加了事务之后这个问题基本消失了。5.3 酒店预订与房态管理酒店预订比门票复杂的地方在于它是区间占用。用户选择酒店、房型、入住日期和离店日期需要校验这段时间内房间是否有空房。我在专门设计了一张订单记录表来支撑这个逻辑。房源表和订单表都关联了房间和日期范围判断房间是否可订写一条 SQL 查询该房间在入住日期和离店日期之间是否存在状态为已支付或待支付的订单async function checkRoomAvailable(roomId, dateStart, dateEnd) { const [rows] await db.query( SELECT COUNT(*) AS cnt FROM orders WHERE type hotel AND item_id ? AND status IN (pending, paid) AND date_start ? AND date_end ?, [roomId, dateEnd, dateStart] ) return rows[0].cnt 0 }这个 SQL 的原理是区间重叠判断已有订单的入住日期早于目标订单的离店日期且已有订单的离店日期晚于目标订单的入住日期说明两个订单的时间段有交集。查询结果为 0 表示无重叠可以预订。日期重叠判断这个写法是整个酒店预订模块的核心理解了这个逻辑房态管理的实现就顺了。5.4 ElementUI 关键组件实战分页和表单校验这两个组件在系统里用得最多。门票和酒店列表页都用了el-pagination分页。关键点在于分页组件的当前页码和每页条数要跟后端接口联动。前端监听current-change和size-change事件重新请求数据。后端接口接收page和pageSize参数返回{ list, total }格式前端把 total 传给组件的total属性。一般新手容易漏掉的一点是每页条数改变后要把当前页码重置为 1否则可能出现当前页超出总页数导致列表空白。订单提交表单用了el-form加校验规则。日期选择器限制最早日期为今天入住日期必须早于离店日期联系方式必填并校验手机号格式。ElementUI 的校验规则配置很简单rules: { dateStart: [ { required: true, message: 请选择入住日期, trigger: change } ], dateEnd: [ { required: true, message: 请选择离店日期, trigger: change } ], phone: [ { required: true, message: 请填写手机号, trigger: blur }, { pattern: /^1[3-9]\d{9}$/, message: 手机号格式不正确, trigger: blur } ] }要注意日期范围选择时dateEnd的动态校验。我写了一个自定义验证函数检查离店日期晚于入住日期不满足则提示错误。这个校验在el-form-item的propdateEnd上配置 validator 方法即可。5.5 管理端数据看板与图表展示管理端除了常规的增删改查我加了一个数据看板页面用 ECharts 展示近 7 天订单量和销售额。Vue 工程里安装 echarts在组件里引入并使用import * as echarts from echarts页面加载完成后初始化图表mounted() { this.chart echarts.init(this.$refs.chart) this.loadOrderStats() }, methods: { async loadOrderStats() { const res await getOrderStats() this.chart.setOption({ xAxis: { data: res.data.dates }, yAxis: {}, series: [{ type: line, data: res.data.counts }] }) } }这里有一个常见问题如果res.data返回的数据结构与图表要求的格式不一致会导致图表渲染失败。我的做法是后端直接返回 xAxis 和 series 需要的格式前端拿到数据直接拿来用不在前端做复杂的数据转换减少出错点。6. 高频坑点与排查实录6.1 npm 全家桶报错排查开发过程中 npm 相关的报错遇到不少最典型的是上面说的npm.ps1脚本禁止运行问题。还有一类是依赖版本冲突比如 Vue 2 项目里不小心装了 Vue 3 版本的组件库导致页面白屏且控制台报错。排查方式很简单看完整报错信息用 npm list 查看依赖树确认版本对不对。组件库安装版本不对的情况很隐蔽因为安装过程不报错运行时才出问题。我的建议是安装依赖时尽量指定版本比如npm i element-ui2.15.8避免意外装到大版本。6.2 el-dialog 拖拽与调整大小实现ElementUI 的el-dialog默认不支持拖拽和调整宽高但网上有很多现成实现方案。我常用的是在el-dialog的mounted钩子里给 dialog 头部添加 mousedown 事件实现拖拽调整宽高用 CSS 的resize属性。核心简化版拖拽逻辑拿到 dialog 元素的头部节点监听mousedown记录鼠标初始位置和对话框初始位置鼠标mousemove时计算偏移量更新对话框的left和top鼠标mouseup时移除事件监听。这个功能是纯前端交互增强不涉及业务数据所以实现起来比较独立不影响其他逻辑。需要注意的是拖拽时可能会选中文本要加上user-select: none样式或者调用e.preventDefault()否则拖到一半文本选中会很别扭。6.3 下拉多选与全选功能门票筛选和酒店设施筛选用了el-select的多选模式需要加一个全选选项。实现思路是给 options 数组最前面加一个值为all的选项监听 change 事件判断当前选中值里是否包含all。如果包含就把所有可选值都选中如果取消all就清空选择。这个场景看似简单但细节不少。全选状态下用户取消某个子项需要判断是否把全选项从选中值里剔除否则会出现选中了全部子项但全选项还亮着的状态。我写的时候是每次 change 都检查选中列表是否等于全部子项集合相等就自动补上all不相等就移除all。逻辑判断虽然多一点但用户体验完整很多。6.4 数据变更页面不刷新的经典解法ElementUI 选择框数据变化后页面不刷新这个问题的根源基本都在 Vue 2 的响应式原理上。Vue 2 使用Object.defineProperty劫持数据对象新增属性或者数组按下标修改时无法触发视图更新。我遇到的一个具体场景是订单列表接口返回的数据有statusText字段初始数据里没有这个字段是前端根据status映射出来的。直接给对象赋值row.statusText 已支付页面完全不更新。后来改成this.$set(row, statusText, 已支付)视图立刻正常显示了。如果你在 ElementUI 项目里遇到类似“数据变了页面没反应”的问题第一个排查动作就是看是不是给对象添加了新属性或者修改了数组下标如果是用this.$set替代直接赋值。6.5 ElementUI 时间线自定义插槽订单时间线展示支付记录、核销记录、入住记录时用了el-timeline组件。自定义每个节点的图标和内容用 ElementUI 提供的具名插槽#dot和默认插槽。timestamp 的格式化通过 slot 自定义实现el-timeline el-timeline-item v-for(item, index) in timelineData :keyindex :timestampitem.time template #dot i :classitem.icon/i /template span{{ item.title }}/span /el-timeline-item /el-timeline如果需要自定义 timestamp 的展示样式比如改成红色加粗、带自定义前缀就不能依赖timestamp属性了要自己写 HTMLel-timeline-item template #timestamp span classcustom-time{{ item.time }}/span /template div{{ item.title }}/div /el-timeline-item插槽方案比改 ElementUI 源码或全局样式要干净得多升级组件库也不容易被覆盖。7. 项目部署与后续扩展建议开发完成后部署上线我用的是一台云服务器Linux 系统Nginx 做反向代理。前端打包npm run build生成dist目录把里面的静态文件传到 Nginx 的 html 目录。Nginx 配置里把/api路径反代到 Node 服务server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html; index index.html; 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加上try_files把所有请求都指向index.html就能解决。如果不想配置后端 fallback直接用 hash 模式路由也行但不美观。后端进程管理我用的是 PM2不需要额外安装守护进程工具安装、启动、看日志一条命令搞定npm install pm2 -g pm2 start app.js --name travel-server pm2 logs travel-server系统跑起来之后后续可以扩展的方向很多对接真实支付渠道微信支付、支付宝、增加短信验证码登录、基于 Redis 做热点景区缓存、导出订单报表等。如果你现在做的项目还没上线我建议先把订单状态机、库存一致性这两个核心问题吃透这是这类系统的价值所在。我个人做完这个项目的体会是Node.js Vue ElementUI 这套组合非常适合快速构建中小型业务管理系统。它不需要你花大量时间在环境搭建和框架配置上能让你把精力集中在业务逻辑本身。但也别因为上手快就忽视设计——业务链路理顺了代码怎么写都是顺的业务链路一塌糊涂换什么技术栈都救不了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价