
简介本资源是一套基于Python全栈技术实现的旅游景点智能推荐系统完整工程包面向Web开发初学者与全栈实践者解决旅游信息个性化匹配与交互式管理问题。系统采用Flask构建轻量后端APIVue实现响应式前端界面MySQL存储景点、用户及门票数据并集成OpenCV图像识别模块提升推荐精准度适用于课程设计、毕业项目或中小规模旅游平台原型开发。压缩包共533个文件72.64MB涵盖84个Vue组件文件如IndexMain.vue、BreadCrumbs.vue、42个核心Py脚本含Flask路由与OpenCV处理逻辑、41个JS交互文件、54张JPG/PNG景点图及161个SVG图标资源另含init_sql.bat等6个批处理脚本、1个MP4演示视频、1个SQL建库文件及完整说明文档。目前已有113人学习下载提供开箱即用的运行环境含安装/启动bat、结构清晰的前后端分离目录、关键模块备份文件.bak及管理员后台全套功能源码便于快速部署、调试与二次开发。1. 这不是个“前后端分离演示项目”而是一套可落地的旅游推荐服务骨架Flask 提供结构化 APIVue 实现动态交互界面两者通过标准 REST 协议解耦协作专为中小型文旅平台、景区导览小程序后台或高校课程设计项目定制。它不依赖第三方云服务或 SaaS 接口所有推荐逻辑如基于景点标签的协同过滤、热度加权排序、地理位置邻近度计算均在本地 Python 层实现Vue 端不嵌入任何外部播放器 SDK也不处理视频流协议如 m3u8仅负责渲染 JSON 数据、响应用户筛选动作并提交搜索请求。如果你正需要一个能快速部署、便于二次开发、且不涉及敏感网络行为的旅游信息展示系统——这个 Flask-Vue 组合就是当前最轻量、最可控、文档最完整的起点。2. Flask 后端从路由设计到推荐算法落地的三层结构2.1 初始化 Flask 应用与核心配置项设置Flask 作为轻量级 Web 框架其优势在于对 HTTP 请求生命周期的完全掌控。本系统采用经典工厂模式组织代码app/__init__.py中完成应用实例创建与扩展初始化# app/__init__.py from flask import Flask from flask_sqlalchemy import SQLAlchemy from flask_cors import CORS import os db SQLAlchemy() def create_app(config_namedefault): app Flask(__name__) # 从环境变量或默认值加载配置 app.config[SQLALCHEMY_DATABASE_URI] os.getenv(DATABASE_URL, sqlite:///data/tourism.db) app.config[SQLALCHEMY_TRACK_MODIFICATIONS] False app.config[JSON_SORT_KEYS] False # 保持字典插入顺序利于前端展示一致性 # 允许 Vue 开发服务器跨域请求仅开发阶段启用 if os.getenv(FLASK_ENV) development: CORS(app, origins[http://localhost:8080]) db.init_app(app) # 注册蓝图 from app.api.v1 import bp as api_v1_bp app.register_blueprint(api_v1_bp, url_prefix/api/v1) return app注意CORS(app, origins[http://localhost:8080])是开发阶段必需配置用于允许 Vue CLI 启动的http://localhost:8080前端访问后端接口生产部署时应移除该行改由 Nginx 反向代理统一处理跨域避免暴露开发配置。2.2 景点数据模型与 SQLite 初始化脚本系统使用 SQLite 作为嵌入式数据库兼顾开发便捷性与零运维成本。app/models.py定义三个核心实体ScenicSpot景点主表、Tag标签词典、SpotTag多对多关联表# app/models.py from app import db class Tag(db.Model): __tablename__ tags id db.Column(db.Integer, primary_keyTrue) name db.Column(db.String(32), uniqueTrue, nullableFalse, indexTrue) class ScenicSpot(db.Model): __tablename__ scenic_spots id db.Column(db.Integer, primary_keyTrue) name db.Column(db.String(100), nullableFalse) description db.Column(db.Text) latitude db.Column(db.Float) # WGS84 坐标系 longitude db.Column(db.Float) popularity_score db.Column(db.Float, default0.0) # 人工设定或日志统计得出 created_at db.Column(db.DateTime, defaultdb.func.now()) class SpotTag(db.Model): __tablename__ spot_tags spot_id db.Column(db.Integer, db.ForeignKey(scenic_spots.id), primary_keyTrue) tag_id db.Column(db.Integer, db.ForeignKey(tags.id), primary_keyTrue)初始化数据库需执行以下命令首次运行flask shell from app import db db.create_all() exit()该操作将在项目根目录生成data/tourism.db文件包含三张表结构。后续可通过flask seed自定义命令批量导入 CSV 格式景点数据如国家 A 级景区名录无需手动 INSERT。2.3 推荐接口实现热度标签匹配双路召回策略app/api/v1/recommends.py实现/api/v1/recommends接口支持两种推荐模式hot按热度排序与tag按用户选择标签召回。关键逻辑如下# app/api/v1/recommends.py from flask import Blueprint, request, jsonify from app.models import ScenicSpot, SpotTag, Tag from sqlalchemy import func, and_ bp Blueprint(recommends, __name__) bp.route(, methods[GET]) def get_recommendations(): mode request.args.get(mode, hot) # 默认返回热门景点 limit min(int(request.args.get(limit, 10)), 50) # 防止恶意拉取全量数据 tags request.args.getlist(tag) # 支持多个 tag 查询如 ?tag山水tag古迹 if mode hot: spots ScenicSpot.query.order_by(ScenicSpot.popularity_score.desc()).limit(limit).all() elif mode tag and tags: # 多标签交集查询仅返回同时拥有全部指定标签的景点 subquery db.session.query(SpotTag.spot_id).filter( SpotTag.tag_id.in_(db.session.query(Tag.id).filter(Tag.name.in_(tags))) ).group_by(SpotTag.spot_id).having(func.count(SpotTag.tag_id) len(tags)) spot_ids [row[0] for row in subquery.all()] spots ScenicSpot.query.filter(ScenicSpot.id.in_(spot_ids)).order_by( ScenicSpot.popularity_score.desc() ).limit(limit).all() else: return jsonify({error: invalid mode or missing tags}), 400 return jsonify([{ id: s.id, name: s.name, description: s.description[:200] ... if len(s.description) 200 else s.description, latitude: s.latitude, longitude: s.longitude, popularity_score: round(s.popularity_score, 2) } for s in spots])参数说明与调用示例GET /api/v1/recommends?modehotlimit6→ 返回热度 Top6 景点GET /api/v1/recommends?modetagtag古镇tag摄影→ 返回同时具备“古镇”和“摄影”标签的景点limit参数最大限制为 50防止接口被滥用导致数据库压力激增该实现未引入复杂机器学习模型而是采用可解释、易调试的规则型推荐逻辑适合教学、原型验证及中小规模数据场景。3. Vue 前端构建响应式景点浏览与筛选界面3.1 Vue 3 Composition API 初始化与路由配置项目使用 Vue CLI 创建标准工程结构src/router/index.js定义单页应用路由// src/router/index.js import { createRouter, createWebHistory } from vue-router import HomeView from ../views/HomeView.vue import DetailView from ../views/DetailView.vue const router createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: /, name: home, component: HomeView }, { path: /spot/:id, name: detail, component: DetailView, props: true } ] }) export default router提示import.meta.env.BASE_URL是 Vue 3.3 推荐的环境变量写法替代旧版process.env.BASE_URL确保构建产物路径正确若使用较老版本 Vue CLI请替换为对应语法。3.2 主页组件调用 Flask 推荐接口并渲染卡片列表src/views/HomeView.vue使用onMounted生命周期钩子发起初始请求并通过ref管理状态!-- src/views/HomeView.vue -- template div classcontainer h2热门景点推荐/h2 div classfilter-bar label筛选标签/label select v-modelselectedTags multiple size3 option value山水山水/option option value古镇古镇/option option value宗教宗教/option option value红色红色/option option value摄影摄影/option /select button clickfetchRecommendations应用筛选/button /div div classspot-grid router-link v-forspot in spots :keyspot.id :to/spot/${spot.id} classspot-card h3{{ spot.name }}/h3 p{{ spot.description }}/p small热度{{ spot.popularity_score }}/small /router-link /div /div /template script setup import { ref, onMounted } from vue import axios from axios const spots ref([]) const selectedTags ref([]) const fetchRecommendations async () { try { const params selectedTags.value.length 0 ? { mode: tag, tag: selectedTags.value } : { mode: hot } const res await axios.get(/api/v1/recommends, { params }) spots.value res.data } catch (err) { console.error(获取推荐失败:, err.response?.data || err.message) } } onMounted(() { fetchRecommendations() }) /script style scoped .container { max-width: 1200px; margin: 0 auto; padding: 20px; } .spot-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px; } .spot-card { border: 1px solid #e0e0e0; border-radius: 8px; padding: 16px; text-decoration: none; color: inherit; } .spot-card:hover { background-color: #f9f9f9; } /style关键细节说明router-link直接跳转至详情页不触发页面刷新符合 SPA 体验axios.get(/api/v1/recommends, { params })自动将对象序列化为 URL 查询参数如?modetagtag古镇tag摄影v-modelselectedTags绑定多选下拉框selectedTags类型为string[]天然适配tag参数数组格式错误处理捕获err.response?.data便于前端定位后端返回的具体错误信息如 400 Bad Request。3.3 环境配置与开发代理设置Vue 项目需配置代理以解决跨域问题。在vue.config.js中添加// vue.config.js module.exports { devServer: { proxy: { /api: { target: http://localhost:5000, // Flask 默认端口 changeOrigin: true, pathRewrite: { ^/api: /api // 保持前缀一致不重写 } } } } }此配置使前端axios.get(/api/v1/recommends)实际请求转发至http://localhost:5000/api/v1/recommends绕过浏览器同源策略且无需后端开启 CORS —— 这是生产部署前必须切换的配置项。4. 本地联调与部署流程从启动到上线的完整链路4.1 启动 Flask 后端服务确保已安装 Python 3.8 及依赖# 创建虚拟环境推荐 python -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate # Windows # 安装依赖 pip install -r requirements.txt # 设置环境变量Linux/macOS export FLASK_APPapp:create_app export FLASK_ENVdevelopment flask run --host0.0.0.0 --port5000注意Windows 用户请使用set FLASK_APPapp:create_app和set FLASK_ENVdevelopment设置环境变量若提示flask命令未找到请确认venv/bin或venv\Scripts已加入 PATH。启动成功后访问http://localhost:5000/api/v1/recommends?modehot应返回 JSON 格式景点列表状态码为 200。4.2 启动 Vue 前端开发服务器在 Vue 项目根目录执行npm install npm run serve此时前端运行于http://localhost:8080自动代理/api请求至http://localhost:5000。打开浏览器即可看到首页景点卡片点击任一卡片跳转至详情页。4.3 生产构建与 Nginx 静态部署方案Vue 构建产物为纯静态文件Flask 可直接托管但更推荐分离部署# 构建 Vue 产物 npm run build # 输出至 dist/ 目录Nginx 配置示例/etc/nginx/sites-available/tourismserver { listen 80; server_name tourism.example.com; # 前端静态资源 location / { root /var/www/tourism/dist; try_files $uri $uri/ /index.html; } # API 请求反向代理至 Flask location /api/ { proxy_pass http://127.0.0.1:5000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }启用配置后重载 Nginxsudo ln -sf /etc/nginx/sites-available/tourism /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx此时访问http://tourism.example.com即可获得完整服务所有 API 请求经 Nginx 转发前端无跨域风险符合生产安全规范。5. 推荐效果增强技巧三类低成本优化方向5.1 地理位置加权在推荐结果中融入距离因子当前推荐仅依赖热度与标签若用户明确提供当前位置如通过浏览器 Geolocation API可在后端增加距离衰减权重。修改get_recommendations函数中的查询逻辑# 在 tag/hot 分支后追加 elif mode nearby: lat float(request.args.get(lat, 0)) lng float(request.args.get(lng, 0)) if not (-90 lat 90 and -180 lng 180): return jsonify({error: invalid coordinates}), 400 # 使用 Haversine 公式近似计算球面距离单位公里 # 此处简化为平面欧氏距离适用于小范围城市内 distance_expr func.sqrt( func.pow(ScenicSpot.latitude - lat, 2) func.pow(ScenicSpot.longitude - lng, 2) ) * 111.3 # 粗略换算为公里 spots ScenicSpot.query.order_by( distance_expr.asc(), # 距离越近越靠前 ScenicSpot.popularity_score.desc() # 同距离下按热度排序 ).limit(limit).all()前端调用方式变为GET /api/v1/recommends?modenearbylat39.9042lng116.4074该增强无需额外数据库索引仅增加一次数学运算即可实现“附近热门景点”功能显著提升用户体验。5.2 标签体系扩展支持用户自定义标签与热度反馈闭环系统默认标签来自预设列表但可通过新增UserFeedback模型记录用户点击行为形成简单反馈闭环# app/models.py 新增 class UserFeedback(db.Model): __tablename__ user_feedback id db.Column(db.Integer, primary_keyTrue) spot_id db.Column(db.Integer, db.ForeignKey(scenic_spots.id)) action db.Column(db.String(10)) # click, favorite, share timestamp db.Column(db.DateTime, defaultdb.func.now())在推荐接口中每次返回结果前更新对应景点的popularity_score# 在 get_recommendations 返回前追加 for s in spots: # 每次曝光 0.1模拟基础热度积累 s.popularity_score round(s.popularity_score 0.1, 2) db.session.commit()提示实际项目中应使用 Redis 缓存计数器避免高频写库此处为演示简化逻辑仅作概念验证。5.3 Vue 端性能优化懒加载详情页与骨架屏DetailView.vue可结合defineAsyncComponent实现组件级懒加载!-- src/views/HomeView.vue 中 -- router-view v-slot{ Component } keep-alive component :isComponent / /keep-alive /router-view并在路由配置中改为异步导入// src/router/index.js const DetailView () import(../views/DetailView.vue)同时在DetailView.vue内部使用skeleton组件如vue-skeleton-loader替代 loading 文字提升视觉连贯性。此类优化对首屏加载时间影响显著尤其在网络波动时体现明显。以上三项技巧均无需重构核心架构仅在现有 Flask-Vue 结构上叠加即可快速提升系统实用性与专业感。本文还有配套的精品资源点击获取