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里用户信息的持久化方案(localStorage+Vuex同步)都给你配好了。你可以直接打开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或NestJS?
Koa的洋葱模型很优雅,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)同步到Vuex;main.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-model="userPrefs"> <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-html="item.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/www,nodemon会监听文件变化自动重启。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 [ arch=amd64,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 '@/router' | vue.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是否为true |
Customized.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 'mongoose' | node_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:27017 | MongoDB服务未启动,或配置的端口/地址错误 | 在服务器上执行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,返回404 | Nginx的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/uploads |
5.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、扩展收藏夹、评论互动等功能。
本文还有配套的精品资源,点击获取
