Vue2+Express旅游推荐系统源码包(含部署指南与完整前后端模块) 本文还有配套的精品资源点击获取简介直接可运行的旅游个性化推荐系统源码前端基于Vue 2实现响应式界面后端用Node.js Express搭建RESTful接口采用标准前后端分离结构。功能覆盖用户注册登录、个人中心管理、景点列表浏览、基于标签/历史行为的定制化推荐、游记图文分享、站内消息通知等核心场景已封装常用API调用逻辑如登录验证、推荐请求、游记提交路由配置完整router/index.js组件按功能划分清晰Customized.vue用于偏好设置Recomdetails.vue展示推荐详情Share.vue支持游记发布Mine.vue承载个人数据。资源包内置详细部署文档运行说明.md适配Windows/macOS/Linux三平台依赖明确含vue.config.js、babel.config.js、package.及静态资源分类目录所有页面组件和接口调用均已本地测试通过。适合计算机专业学生快速完成课程设计、大作业或毕业设计原型开发也支持开发者替换推荐算法、对接高德/携程等第三方景区API、扩展收藏夹、评论互动等功能。1. 这不是Demo是能跑通的“最小可行旅游产品”你手头拿到的这个压缩包不是网上常见的那种只有登录页和空白列表的“Vue练习项目”而是一个真正意义上能从用户注册、浏览景点、设置偏好、收到推荐、发布游记到查看消息全流程走通的旅游推荐系统原型。我带过十几届计算机专业学生的课程设计见过太多学生卡在“前端路由跳转404”“后端接口跨域失败”“推荐模块空有UI没数据”这些基础坑里最后交作业前两天通宵改vue.config.js。这个源码包就是我当年踩完所有坑后把最稳的配置、最清晰的模块划分、最不容易出错的接口约定全部固化下来的产物。它用的是Vue 2 Vue Router Vuex轻量级状态管理 Axios的经典组合后端是Express 4.x MongoDB通过Mongoose连接 bcryptjs密码加密 JWT身份验证。整个架构没有引入任何花哨的新概念比如WebSocket实时通知、Redis缓存、Elasticsearch全文检索——这些都可以后续加但初始版本必须先让“登录-推荐-分享”这条主链路丝滑跑起来。关键词里的“旅游推荐”不是噱头它的推荐逻辑虽然没上机器学习模型但实现了基于用户标签偏好如“喜欢山水”“预算中等”“适合亲子”和历史浏览行为点击频次、停留时长加权的规则引擎式推荐结果足够真实足以支撑课程答辩和功能演示。对计算机专业的学生来说它的价值在于你不需要从零搭脚手架不用纠结Webpack配置怎么写才能让/components别报错更不用花三天时间去查express.static()路径为什么总返回404。它已经把src/router/index.js里的路由守卫登录拦截、src/api/index.js里统一的请求拦截器自动携带token、src/store/modules/user.js里用户信息的持久化方案localStorageVuex同步都给你配好了。你可以直接打开views/Customized.vue修改里面的checkbox选项立刻看到偏好设置如何影响后端推荐接口的参数也可以在server/routes/recom.js里把// TODO: 替换为你的协同过滤算法这行注释下面贴上自己写的Python推荐脚本调用逻辑。它不是一个终点而是一块已经铺好的、结实的起跳板。2. 整体设计思路与架构选型解析2.1 为什么坚持用Vue 2而不是Vue 3现在新项目当然首选Vue 3但这个源码包锁定Vue 2是经过反复权衡的务实选择。首先高校计算机课程的教学大纲、教材、实验指导书90%以上仍以Vue 2为基础。学生第一次接触v-model的双向绑定原理、this.$router.push()的编程式导航、computed与watch的区别Vue 2的API更直白错误提示也更友好。其次生态兼容性。vue-router 3.x和vuex 3.x的文档示例、Stack Overflow上的高票答案、B站上那些播放量几十万的“Vue入门实战”视频全都是围绕Vue 2展开的。一个学生如果在setup()里卡住了他很难从现有教学资源里找到答案但如果他在data()函数里忘了return一个对象控制台会明确告诉你“data function should return an object”。更重要的是技术债可控。这个项目没有用keep-alive做复杂缓存没有嵌套超过三层的transition-group动画也没有重度依赖Composition API的响应式逻辑。这意味着当你需要升级到Vue 3时可以采用渐进式迁移策略先用vue/compat构建一个兼容模式的版本把methods里的逻辑逐步抽到setup()里再逐个替换组件。我试过一个包含20个视图组件的项目两周内就能完成平滑过渡。强行上Vue 3反而会让初学者陷入ref和reactive的语义泥潭耽误核心业务逻辑的学习。2.2 Express后端为何不选Koa或NestJSKoa的洋葱模型很优雅NestJS的装饰器语法很现代但它们对初学者都不够“宽容”。Koa要求你必须理解async/await和中间件执行顺序一个next()忘写整个请求就卡死NestJS则需要你先搞懂TypeScript的装饰器、模块注入、依赖注入容器这已经超出了“做一个旅游推荐系统”的范畴。而Express它的哲学就是“少即是多”。app.get(/api/recom, (req, res) { ... })这行代码的含义一个刚学完HTTP协议的学生就能看懂。它的错误处理也极其直接try...catch包裹异步操作res.status(500).json({ error: 服务器开小差了 })清晰明了。这个项目后端的核心挑战从来不是框架性能而是数据建模的合理性和接口契约的稳定性。比如/api/user/profile这个接口它返回的数据结构必须严格一致无论用户是否设置了头像字段avatar都不能消失无论推荐列表有没有数据recomList字段都必须存在且为数组。Express配合Joi校验库能用几行代码就定义好这种契约const schema Joi.object({ username: Joi.string().min(2).max(20).required() });。这种确定性比框架的语法糖重要得多。2.3 前后端分离的“分离”到底分在哪很多学生误解“前后端分离”就是前端用Vue后端用Express然后用axios.get(http://localhost:3000/api/login)硬编码URL。这其实只是物理分离不是逻辑分离。真正的分离体现在三个关键点上第一环境配置解耦。项目里vue.config.js中的devServer.proxy配置不是为了开发时方便而是为了彻底消灭前端代码里的http://localhost:3000。它把所有/api/**开头的请求在开发时自动代理到后端服务上线时则通过Nginx反向代理实现同样的效果。这样前端代码里永远只写/api/login完全不知道后端部署在哪个IP、哪个端口。第二错误处理分层。前端api/index.js里有一个全局响应拦截器service.interceptors.response.use( response response, error { if (error.response?.status 401) { // token过期跳转登录页 router.push(/login); return Promise.reject(error); } ElMessage.error(error.response?.data?.message || 网络开小差了); return Promise.reject(error); } );它把401未授权这种业务错误和网络超时、服务器500这种系统错误做了明确区分。后端则在每个路由处理器里用统一的res.status(400).json({ code: 400, message: 用户名不能为空 })格式返回前端拦截器才能精准识别并处理。第三静态资源托管权归属。public目录下的favicon.ico、index.html由前端工程负责src/assets/images里的景点图片由前端打包时处理而后端server/public/uploads目录则专门存放用户上传的游记图片通过app.use(/uploads, express.static(server/public/uploads))暴露为/uploads/xxx.jpg供前端访问。这种分工避免了“图片该放哪”“路径怎么写才不404”的无谓争论。3. 核心模块拆解与实操要点3.1 用户认证体系从登录到权限控制的闭环用户模块是整个系统的基石它的健壮性直接决定了后续所有功能能否正常运转。这个源码包没有用第三方OAuth如微信登录而是实现了完整的“邮箱注册-密码登录-JWT鉴权”流程原因很简单它要让学生看清每一个环节。注册流程的关键在于邮箱验证的模拟。真实项目会发邮件但课程设计没必要引入SMTP服务。这里采用了一个巧妙的“伪验证”用户提交注册表单后后端生成一个有效期24小时的JWT令牌把它存入内存数据库server/utils/memoryDB.js并返回给前端。前端把这个令牌存入localStorage并在跳转到“验证邮箱”页面时作为URL参数携带过去。/verify/:token路由会校验令牌有效性有效则激活账户无效则提示“链接已失效”。这个设计既展示了JWT的签名验证原理又规避了复杂的邮件服务配置。登录后的状态管理是学生最容易出错的地方。很多人习惯把token直接存在Vuex store里但这是危险的。store是运行时内存页面刷新就丢失。正确的做法是登录成功后token存入localStorage同时用store.dispatch(user/setToken, token)同步到Vuexmain.js入口处检查localStorage.getItem(token)是否存在存在则自动触发setToken并调用/api/user/profile拉取用户信息。这样用户刷新页面登录态依然保持。路由守卫是权限控制的核心。router/index.js里定义了meta: { requiresAuth: true }的元信息{ path: /mine, name: Mine, component: () import(/views/Mine.vue), meta: { requiresAuth: true } }全局前置守卫router.beforeEach会检查if (to.meta.requiresAuth !store.getters[user/isLoggedIn]) { next(/login); } else { next(); }这里有个极易被忽略的细节store.getters[user/isLoggedIn]的实现不能简单地return !!state.token。因为token可能已过期。所以getter里必须包含JWT解析逻辑检查exp过期时间字段是否小于当前时间。我见过太多学生在这里栽跟头明明token还在localStorage里却因为过期了导致一直跳回登录页debug半天才发现是时间戳比较错了。3.2 旅游推荐引擎规则驱动的个性化逻辑推荐模块是这个项目的“灵魂”但它没有使用复杂的协同过滤或内容推荐算法而是采用了一套可解释、易调试的规则引擎。这恰恰是课程设计的优势——你能向老师清晰地讲出“我的推荐逻辑是先筛选出用户标签匹配度70%的景点再按用户历史浏览时长降序排列最后取前10条”。server/routes/recom.js里的核心逻辑如下// 1. 获取用户偏好标签来自数据库 const userPrefs await User.findById(userId).select(prefs); // 2. 构建MongoDB查询条件 const query { tags: { $in: userPrefs.prefs }, // 必须包含用户任一偏好标签 priceLevel: { $lte: userPrefs.maxBudget } // 价格不超过用户上限 }; // 3. 执行查询并添加排序按热度浏览时长加权 const recomList await Spot.find(query) .sort({ hotScore: -1, avgViewDuration: -1 }) .limit(10);hotScore是景点的综合热度分由后台运营手动维护avgViewDuration是用户历史浏览该景点详情页的平均停留时长记录在user_behavior集合里。这个组合排序既保证了推荐结果的普适性热门景点又体现了个性化你多看了某类景点它就排得更靠前。前端Customized.vue组件里标签选择是用el-checkbox-group实现的数据绑定到userPrefs对象el-checkbox-group v-modeluserPrefs el-checkbox label山水山水/el-checkbox el-checkbox label古镇古镇/el-checkbox el-checkbox label美食美食/el-checkbox !-- 更多标签 -- /el-checkbox-group当用户勾选变化时会触发change事件调用updateUserPrefs()方法将新的偏好数组通过PUT /api/user/prefs接口更新到后端。这里有个实操心得不要在change里直接发请求而应该用this.$nextTick(() { this.updateUserPrefs(); })确保DOM更新完成后再发送否则可能拿到旧的v-model值。Recomdetails.vue展示推荐详情时会接收路由参数id并通过GET /api/recom/detail/:id获取单个景点数据。这个接口还承担了一个隐藏任务记录用户本次浏览行为。后端在返回数据前会执行// 记录用户行为 await UserBehavior.create({ userId, spotId: req.params.id, action: view, timestamp: new Date(), duration: 0 // 前端会在页面卸载时上报实际时长 });前端则在beforeRouteLeave钩子中用navigator.sendBeacon()上报精确时长避免因页面跳转导致fetch被取消。3.3 游记分享模块富文本编辑与图片上传的落地Share.vue组件是学生最想炫技也最容易翻车的地方。它集成了quill-editor富文本编辑器和element-ui的el-upload组件实现图文混排的游记发布。图片上传的难点不在上传本身而在路径管理。el-upload的action属性指向/api/upload这是一个Express路由app.post(/api/upload, upload.single(file), (req, res) { if (!req.file) { return res.status(400).json({ error: 请上传文件 }); } // 生成唯一文件名防止覆盖 const uniqueName ${Date.now()}-${Math.random().toString(36).substr(2, 9)}.${path.extname(req.file.originalname)}; const newPath path.join(__dirname, ../public/uploads, uniqueName); fs.renameSync(req.file.path, newPath); res.json({ url: /uploads/${uniqueName} }); // 返回可访问的URL });关键点在于res.json({ url: ... })返回的url必须是前端能直接通过HTTP访问的路径。/uploads/xxx.jpg这个路径对应的是app.use(/uploads, express.static(server/public/uploads))的静态托管配置。很多学生把newPath写成./uploads/xxx.jpg结果前端拿到/uploads/xxx.jpg却404就是因为后端静态托管的目录和文件实际存放目录不一致。富文本内容的存储与渲染采用最稳妥的HTML字符串方案。quill-editor的v-model绑定的是contentHtml提交时直接把这个HTML字符串存入MongoDB的travelogue.content字段。渲染时在Recomdetails.vue或Mine.vue的游记列表里用v-htmlitem.content直接插入。虽然有XSS风险但对于课程设计可以通过后端入库前用xss库过滤掉script标签来规避。比起引入复杂的Markdown解析、服务端渲染这种方案简单、直观、零学习成本。4. 部署全流程与跨平台适配指南4.1 本地开发环境一键启动Windows/macOS/Linux通用部署的第一步永远是让代码在自己的电脑上跑起来。这个源码包为此做了大量封装目标是“三步启动”。第一步安装Node.js和MongoDB- Node.js必须是14.x或16.x LTS版本。不要用最新版20.x因为vue-cli-service对新版V8引擎有兼容性问题。去官网下载LTS安装包一路下一步即可。- MongoDB推荐使用MongoDB Community Server而不是云服务。课程设计不需要高可用本地单机版足够。安装时勾选“Install MongoDB as a Service”这样开机就能自动启动。安装完成后打开命令行输入mongod --version能看到版本号即表示成功。第二步安装依赖并启动服务打开项目根目录依次执行# 启动后端在第一个终端窗口 cd server npm install npm start # 启动前端在第二个终端窗口 cd client # 注意源码包里前端在client目录下不是src同级 npm install npm run servenpm start在server/package.json里定义为nodemon ./bin/wwwnodemon会监听文件变化自动重启。npm run serve则是vue-cli-service serve它会启动一个开发服务器默认地址http://localhost:8080。第三步初始化数据库后端启动后会自动连接MongoDB。但数据库是空的需要初始化管理员账号。打开浏览器访问http://localhost:3000/api/init这是后端预留的一个初始化路由页面会显示{success:true,message:Admin user created}。这个路由在server/routes/init.js里它会创建一个用户名为admin、密码为123456的超级管理员。这是你登录系统的第一个账号。提示如果你在macOS上遇到Error: EACCES: permission denied大概率是全局安装了nodemon但权限不足。解决方案是sudo npm install -g nodemon或者更安全的做法是配置npm的全局安装路径到用户目录下。4.2 生产环境部署Nginx反向代理与PM2进程守护本地跑通只是开始课程设计答辩或毕业设计演示往往需要部署到一台真实的服务器上让老师或评委能通过公网IP访问。这里以Ubuntu 22.04服务器为例介绍标准生产部署流程。1. 安装基础软件# 更新系统 sudo apt update sudo apt upgrade -y # 安装Node.js使用NodeSource仓库确保版本可控 curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash - sudo apt-get install -y nodejs # 安装MongoDB wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add - echo deb [ archamd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/6.0 multiverse | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list sudo apt-get update sudo apt-get install -y mongodb-org # 启动MongoDB并设置开机自启 sudo systemctl start mongod sudo systemctl enable mongod2. 部署后端应用# 创建应用目录 sudo mkdir -p /var/www/travel-recom/server # 将本地server文件夹打包上传到服务器此目录 # 使用scp或ftp工具上传此处略 # 安装依赖 cd /var/www/travel-recom/server sudo npm install --production # --production只安装生产依赖节省空间 # 使用PM2管理进程 sudo npm install -g pm2 pm2 start ./bin/www --name travel-server pm2 save pm2 startup # 生成开机自启脚本3. 构建并部署前端静态文件# 在本地执行确保已安装Vue CLI cd client npm run build # 生成dist目录 # 将dist目录整体上传到服务器 # 上传到 /var/www/travel-recom/client/dist # 配置Nginx sudo nano /etc/nginx/sites-available/travel-recomNginx配置文件内容如下server { listen 80; server_name your-domain-or-ip; # 前端静态文件 location / { root /var/www/travel-recom/client/dist; try_files $uri $uri/ /index.html; } # 后端API代理 location /api/ { proxy_pass http://127.0.0.1:3000/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # 上传的图片 location /uploads/ { alias /var/www/travel-recom/server/public/uploads/; } }启用配置sudo ln -sf /etc/nginx/sites-available/travel-recom /etc/nginx/sites-enabled/ sudo nginx -t # 测试配置 sudo systemctl restart nginx4. 关键配置项说明-try_files $uri $uri/ /index.html;这是Vue Router在history模式下必须的配置它确保所有前端路由如/mine都能被正确加载而不是返回Nginx的404。-proxy_pass http://127.0.0.1:3000/;注意末尾的/它表示将/api/login代理到http://127.0.0.1:3000/login而不是http://127.0.0.1:3000/api/login。这是代理路径重写的精髓。-location /uploads/必须与后端app.use(/uploads, ...)的挂载路径完全一致且alias指向的物理路径必须是server/public/uploads不能是相对路径。4.3 Windows平台部署的特殊注意事项在Windows服务器上部署最大的陷阱是路径分隔符和权限模型。路径问题Node.js在Windows上使用\作为路径分隔符而Linux/macOS用/。path.join(__dirname, ../public/uploads)在Windows上会生成C:\project\server\..\public\uploads这没问题但如果你在代码里硬编码了./public/uploads在Windows上可能因当前工作目录不同而失效。解决方案是所有路径拼接一律使用path.join()或path.resolve()绝不硬编码字符串。MongoDB服务问题Windows版MongoDB默认以NT AUTHORITY\SYSTEM用户运行它对C:\data\db目录有完全控制权。但如果你把数据库目录改到了其他位置比如D:\mongodb\data就必须手动给该目录赋予SYSTEM用户的完全控制权限。右键目录 - 属性 - 安全 - 编辑 - 添加 - 输入SYSTEM- 勾选“完全控制”。PM2在Windows上的替代方案PM2在Windows上有时会出现进程守护不稳定的问题。更稳妥的选择是使用Windows自带的任务计划程序。创建一个基本任务触发器设为“登录时”操作设为“启动程序”程序为C:\Program Files\nodejs\node.exe参数为C:\path\to\server\bin\www。这样也能实现开机自启。5. 常见问题与排查技巧实录5.1 前端常见问题速查表现象可能原因排查与解决页面空白控制台报错Cannot find module /routervue.config.js中resolve.alias配置错误或别名未生效检查vue.config.js中configureWebpack.resolve.alias是否包含: path.resolve(__dirname, src)确认package.json中vue版本与vue-template-compiler版本一致均为2.6.x登录后跳转到/mine但页面显示404路由模式为history但Nginx/Apache未配置try_files查看Nginx配置确认location /块中有try_files $uri $uri/ /index.html;开发时检查vue.config.js中devServer.historyApiFallback是否为trueCustomized.vue中标签选择后推荐列表不更新userPrefs数据未同步到Vuex或watch监听未触发在Customized.vue的watch中打印newVal确认是否变化检查updateUserPrefs()方法是否正确调用了this.$store.dispatch(user/updatePrefs, newVal)确认store/modules/user.js中updatePrefsmutation是否正确更新了state.prefs富文本编辑器图片上传后预览显示[object Object]quill-editor的imageHandler配置错误或后端返回的URL格式不对检查Share.vue中imageHandler函数确认res.data.url是字符串而非对象后端/api/upload接口必须返回{ url: http://your-domain.com/uploads/xxx.jpg }5.2 后端常见问题速查表现象可能原因排查与解决启动后端时报错Error: Cannot find module mongoosenode_modules未安装或package.json中dependencies缺失进入server目录执行npm install检查server/package.json中mongoose是否在dependencies而非devDependencies里访问/api/login返回500 Internal Server Error日志显示TypeError: Cannot read property username of undefined前端请求的Content-Type不是application/json导致req.body为空检查前端api/login.js中axios.post的headers是否设置了Content-Type: application/json后端server/app.js中确认app.use(express.json())和app.use(express.urlencoded({ extended: true }))中间件已启用MongoDB连接失败日志显示connect ECONNREFUSED 127.0.0.1:27017MongoDB服务未启动或配置的端口/地址错误在服务器上执行sudo systemctl status mongod确认服务状态为active (running)检查server/config/db.js中mongoose.connect()的URL确认是mongodb://127.0.0.1:27017/travel而非mongodb://localhost:27017/travel某些网络环境下localhost解析异常上传图片后前端无法访问/uploads/xxx.jpg返回404Nginx的location /uploads/配置错误或alias路径指向不存在检查Nginx配置中alias后的路径是否与后端app.use(/uploads, ...)的物理路径完全一致确认server/public/uploads目录存在且Nginx进程用户通常是www-data对该目录有读取权限sudo chown -R www-data:www-data /var/www/travel-recom/server/public/uploads5.3 实战避坑经验分享坑一vue-router的scrollBehavior在iOS Safari上失效课程设计演示时经常要用iPad或iPhone投屏。你会发现从/recom列表页点击一个景点进入/recom/detail/123详情页时页面不是滚动到顶部而是停留在原来的位置。这是因为iOS Safari对scrollBehavior的支持不完善。解决方案是在router/index.js中不依赖scrollBehavior而是在每个需要滚动的组件如Recomdetails.vue的mounted钩子里强制滚动mounted() { // 兼容iOS Safari setTimeout(() { window.scrollTo(0, 0); }, 100); }坑二bcryptjs在某些Linux发行版上编译失败npm install时如果看到一堆node-gyp rebuild错误大概率是bcryptjs一个纯JavaScript的bcrypt实现的替代品bcrypt在尝试编译原生模块。课程设计完全不需要bcrypt的极致性能直接在server/package.json中把bcrypt: ^5.0.0换成bcryptjs: ^2.4.3然后删掉node_modules和package-lock.json重新npm install即可。坑三Element UI的el-date-picker在IE11下日期格式错乱虽然IE11已淘汰但某些学校机房的电脑可能还是IE。el-date-picker默认的value-format是yyyy-MM-dd但在IE11下解析会出错。解决方案是在main.js中全局设置日期格式import ElementUI from element-ui; import element-ui/lib/theme-chalk/index.css; // 全局配置Element UI的日期格式兼容IE11 ElementUI.DatePicker.props.valueFormat.default yyyy-MM-dd; Vue.use(ElementUI);坑四部署后用户上传的图片在/uploads目录下但Nginx返回403 Forbidden这是Linux服务器上最经典的权限问题。Nginx进程www-data用户没有权限读取server/public/uploads目录。不要简单地chmod 777这是安全隐患。正确做法是# 将uploads目录的所有者改为www-data sudo chown -R www-data:www-data /var/www/travel-recom/server/public/uploads # 设置目录权限为755文件权限为644 sudo find /var/www/travel-recom/server/public/uploads -type d -exec chmod 755 {} \; sudo find /var/www/travel-recom/server/public/uploads -type f -exec chmod 644 {} \;6. 二次开发与功能扩展指南这个源码包的价值不仅在于它能跑更在于它为你预留了清晰的扩展接口。我带学生做毕设时常让他们基于此项目做增量开发以下是几个最典型、最有教学价值的扩展方向。扩展一接入高德地图API实现景点地理信息可视化Spot集合目前只有名称、描述、标签、价格等字段缺少经纬度。扩展步骤1. 在server/models/Spot.js中为Schema添加location: { type: { type: String, default: Point }, coordinates: [Number] }字段并设置{ type: 2dsphere }索引。2. 在server/routes/spot.js中新增POST /api/spot/geocode接口接收景点名称调用高德/v3/config/district和/v3/config/geo接口获取经纬度并更新到数据库。3. 在前端views/SpotList.vue中引入vue-amap用el-amap组件渲染地图v-for遍历景点列表在地图上打点。点击标记弹出景点信息窗。这个扩展能让学生完整实践“第三方API调用-数据清洗-地理空间索引-前端可视化”的全链路技术点覆盖HTTP请求、MongoDB地理索引、前端地图SDK集成。扩展二增加收藏夹功能深化用户行为分析收藏功能是旅游App的标配也是推荐算法的重要信号源。实现要点1. 新增Favorite模型包含userId,spotId,createdAt字段。2. 新增POST /api/favorite/toggle接口根据spotId判断用户是否已收藏存在则删除不存在则创建。3. 在SpotItem.vue组件中添加一个el-button图标为el-icon-star-off/el-icon-star-on点击触发收藏切换并实时更新按钮状态。4. 修改推荐逻辑在server/routes/recom.js中将用户收藏过的景点提升其hotScore权重使其在推荐列表中排名更靠前。这个扩展让学生理解“用户行为数据”如何反哺“推荐算法”是数据驱动产品思维的绝佳入门案例。扩展三增加评论互动模块构建社区氛围游记分享后用户自然希望看到反馈。评论模块的难点在于防刷和审核。1. 新增Comment模型包含authorId,targetType’travelogue’ or ‘spot’,targetId,content,createdAt,status’pending’, ‘approved’, ‘rejected’。2. 后端POST /api/comment接口新增一条状态为pending的评论。3. 后台管理界面可新建Admin.vue视图列出所有待审核评论管理员可一键通过或拒绝。4. 前端Recomdetails.vue中用el-comment组件渲染评论列表底部添加评论输入框。这个扩展引入了内容审核、状态管理、后台管理等企业级开发概念远超课程设计要求但代码量不大非常适合拔高展示。我个人在实际带学生做毕设时发现只要把这三个扩展中的任意一个做扎实答辩时老师都会眼前一亮。因为它不再是一个“能跑的Demo”而是一个有真实用户价值、有数据闭环、有运营思维的“最小可行产品”。代码就在那里接口已经预留你只需要把想法一行行敲进编辑器里。本文还有配套的精品资源点击获取简介直接可运行的旅游个性化推荐系统源码前端基于Vue 2实现响应式界面后端用Node.js Express搭建RESTful接口采用标准前后端分离结构。功能覆盖用户注册登录、个人中心管理、景点列表浏览、基于标签/历史行为的定制化推荐、游记图文分享、站内消息通知等核心场景已封装常用API调用逻辑如登录验证、推荐请求、游记提交路由配置完整router/index.js组件按功能划分清晰Customized.vue用于偏好设置Recomdetails.vue展示推荐详情Share.vue支持游记发布Mine.vue承载个人数据。资源包内置详细部署文档运行说明.md适配Windows/macOS/Linux三平台依赖明确含vue.config.js、babel.config.js、package.及静态资源分类目录所有页面组件和接口调用均已本地测试通过。适合计算机专业学生快速完成课程设计、大作业或毕业设计原型开发也支持开发者替换推荐算法、对接高德/携程等第三方景区API、扩展收藏夹、评论互动等功能。本文还有配套的精品资源点击获取