news 2026/9/11 23:18:07

Python+Vue+MySQL旅游推荐系统毕设模板

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python+Vue+MySQL旅游推荐系统毕设模板

简介:这是一套面向计算机专业本科生的毕业设计级旅游景点推荐系统实战资源,采用Python后端与Vue.js前端分离架构,完整覆盖需求分析、算法实现、前后端联调与数据库设计全流程,特别适合毕业设计、课程设计及期末大作业参考。资源包共710个文件,15.16MB,包含41个核心Python脚本(含协同过滤与内容推荐算法实现)、36个Vue组件(如IndexMain.vue、update-password.vue等)、162个SVG图标与30余张PNG/JPG界面截图、51个CSS样式文件及2个SQL建表与初始化脚本,辅以bat一键部署脚本和详细注释,新手可快速理解模块职责与数据流向。已有351人学习下载,资源附带完整论文文档与可直接运行的MySQL数据库文件,代码经个人手打优化,获导师高度认可,具备高分毕设所需的完整性、规范性与工程实践性。

1. 一个能跑通、能改、能交作业的旅游推荐系统:Python后端 + Vue前端 + MySQL数据库,三端分离不粘连

你不是在找“一个带论文的毕设模板”,而是在找一套能本地一键启动、字段可改、接口可调、数据库可导出导入、答辩时能现场演示增删改查的旅游景点推荐系统。它用 Python(Flask/Django 常见选型)做逻辑和数据服务,Vue 3(Composition API)做交互界面,MySQL 存景点、用户、评分、标签等结构化数据——三者通过 RESTful 接口通信,无硬编码耦合,部署时可分别打包、独立更新。这套组合不是为炫技,而是因为:Python 生态对推荐算法(如基于内容的相似度、协同过滤简易实现)支持成熟;Vue 的响应式和组件化让景点卡片流、搜索筛选、收藏状态同步变得直观;MySQL 则提供稳定事务与索引能力,支撑“按城市+标签+评分排序”这类多条件查询。适合计算机、信管、电商等专业本科生课程设计、毕业设计起步,也适合作为初学全栈开发的练手项目——只要你会 pip install、npm install、mysql -u root -p,就能从零拉起完整链路。

2. 后端核心:用 Flask 构建轻量推荐服务,暴露标准 REST 接口供 Vue 调用

2.1 为什么选 Flask 而非 Django?——聚焦推荐逻辑,避免框架冗余

旅游推荐系统的核心诉求是:快速响应景点查询、支持基于标签/城市/热度的排序、提供简单协同过滤(如“喜欢西湖的人也喜欢雷峰塔”)、返回结构化 JSON。Django 自带 ORM 和 Admin 后台虽强,但对本科毕设而言,其 MTV 结构、中间件、模板系统反而增加理解成本;而 Flask 仅保留路由、请求解析、JSON 响应三大能力,配合 SQLAlchemy 操作数据库、scikit-learn 或纯 Python 实现相似度计算,代码更透明、调试更直接。常见做法是:用flask-sqlalchemy管理模型,flask-cors解决跨域(Vue 开发服务器默认端口 5173,Flask 默认 5000),flask-migrate管理数据库变更——这三者构成最小可行后端骨架。

2.2 数据库模型设计:景点、用户、评分、标签四张表的关联逻辑

系统需支撑“用户浏览→收藏→评分→触发推荐”闭环,因此必须建立清晰关系。以下为实际可用的 SQLAlchemy 模型定义(models.py):

from flask_sqlalchemy import SQLAlchemy from datetime import datetime db = SQLAlchemy() class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(50), unique=True, nullable=False) email = db.Column(db.String(100), unique=True) class Attraction(db.Model): __tablename__ = 'attractions' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) city = db.Column(db.String(50), nullable=False) # 如"杭州" description = db.Column(db.Text) rating = db.Column(db.Float, default=0.0) # 当前平均分 tags = db.relationship('Tag', secondary='attraction_tags', backref='attractions') class Tag(db.Model): __tablename__ = 'tags' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(30), unique=True, nullable=False) # 如"自然风光"、"历史文化" # 关联表:景点-标签多对多 attraction_tags = db.Table('attraction_tags', db.Column('attraction_id', db.Integer, db.ForeignKey('attractions.id'), primary_key=True), db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'), primary_key=True) ) class Rating(db.Model): __tablename__ = 'ratings' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) attraction_id = db.Column(db.Integer, db.ForeignKey('attractions.id'), nullable=False) score = db.Column(db.Integer, nullable=False) # 1~5分 timestamp = db.Column(db.DateTime, default=datetime.utcnow)

提示:attraction_tags是纯关联表,不继承db.Model,用db.Table定义更轻量;Rating表中user_idattraction_id组成联合唯一索引(防止重复评分),需在迁移脚本中补充Index('idx_user_attr', 'user_id', 'attraction_id', unique=True)

2.3 推荐接口实现:基于标签的相似景点推荐(Content-Based)

推荐功能不依赖复杂机器学习库,用向量化标签匹配即可满足毕设要求。核心逻辑:对目标景点 A,找出所有含相同标签的景点 B,按共同标签数加权得分,再按原始评分二次排序:

from flask import jsonify, request from sqlalchemy import func @bp.route('/api/recommend/<int:attraction_id>') def recommend_by_content(attraction_id): target = Attraction.query.get_or_404(attraction_id) target_tag_ids = [t.id for t in target.tags] # 查找含任意相同标签的景点(排除自身) candidates = db.session.query( Attraction, func.count(attraction_tags.c.tag_id).label('common_tags') ).join(attraction_tags).filter( attraction_tags.c.tag_id.in_(target_tag_ids), Attraction.id != attraction_id ).group_by(Attraction.id).order_by( func.count(attraction_tags.c.tag_id).desc(), Attraction.rating.desc() ).limit(10).all() result = [] for attr, common_count in candidates: result.append({ 'id': attr.id, 'name': attr.name, 'city': attr.city, 'rating': float(attr.rating), 'common_tags_count': int(common_count) }) return jsonify(result)
参数说明:
  • func.count(attraction_tags.c.tag_id).label('common_tags'):统计每个候选景点与目标景点共享的标签数量;
  • group_by(Attraction.id):确保每个景点只出现一次,避免因多标签关联产生重复行;
  • order_by(... .desc(), ... .desc()):先按共同标签数降序,再按景点评分降序,兼顾相关性与质量;
  • limit(10):控制返回数量,避免前端渲染压力。

2.4 启动与调试:用 Flask 开发服务器验证接口可用性

安装依赖并初始化数据库:

pip install flask flask-sqlalchemy flask-migrate flask-cors python-dotenv flask db init flask db migrate -m "init tables" flask db upgrade

创建.env文件配置数据库连接:

FLASK_APP=app.py FLASK_ENV=development DATABASE_URL=mysql+pymysql://root:password@127.0.0.1:3306/travel_db

运行后端:

flask run --host=0.0.0.0 --port=5000

此时访问http://127.0.0.1:5000/api/recommend/1(假设景点 ID 1 存在),应返回 JSON 数组。若报错sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (1045, "Access denied for user..."),说明 MySQL 用户密码不匹配,需检查.envDATABASE_URL的凭证;若提示No module named 'pymysql',则需pip install pymysql

3. 前端实现:Vue 3 + Pinia + Axios 构建景点浏览与交互界面

3.1 项目初始化与依赖安装:Vue CLI 创建标准工程结构

使用 Vue 官方脚手架创建项目,明确指定 TypeScript 和 Router(可选,但推荐用于多页面导航):

npm create vue@latest # 选择:✔ Add TypeScript? » Yes # ✔ Add JSX Support? » No # ✔ Add Vue Router for Single Page Application routing? » Yes # ✔ Add Pinia for state management? » Yes # ✔ Add Vitest for Unit Testing? » No # ✔ Add Cypress for both Unit and End-to-End testing? » No # ✔ Add ESLint for code quality? » Yes cd travel-vue npm install

注意:Vue 3.4+ 推荐使用create-vue而非旧版vue-cli,前者生成 Vite 工程,启动更快、HMR 更稳定;Pinia替代 Vuex,API 更简洁,适合管理用户登录态、收藏列表等全局状态。

3.2 封装 Axios 请求实例:统一 baseURL 与错误拦截

src/utils/request.ts中定义请求客户端,避免每个组件重复写axios.create({baseURL: 'http://localhost:5000'})

import axios from 'axios' const request = axios.create({ baseURL: 'http://localhost:5000/api', // 后端地址 timeout: 10000, }) // 请求拦截:自动携带 token(若已登录) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) // 响应拦截:统一处理 4xx/5xx 错误 request.interceptors.response.use( response => response.data, error => { if (error.response?.status === 401) { localStorage.removeItem('token') window.location.href = '/login' } return Promise.reject(error) } ) export default request
关键点说明:
  • baseURL设为/api,后续所有请求如request.get('/attractions')实际访问http://localhost:5000/api/attractions
  • localStorage.getItem('token')读取登录态,此 token 由后端登录接口返回(如/api/login返回{token: "xxx"}),前端存入本地存储;
  • response.data直接返回响应体,省去.then(res => res.data)的重复书写;
  • 401错误触发登出跳转,符合前后端分离项目中 token 失效的标准处理流程。

3.3 景点列表页:使用 Composition API + 异步 setup 获取数据

src/views/AttractionList.vue实现景点卡片流展示,支持城市筛选与标签过滤:

<script setup lang="ts"> import { ref, onMounted } from 'vue' import request from '@/utils/request' import type { AttractionItem } from '@/types' interface FilterParams { city?: string tag?: string } const attractions = ref<AttractionItem[]>([]) const loading = ref(false) const filters = ref<FilterParams>({}) const loadAttractions = async () => { loading.value = true try { const res = await request.get('/attractions', { params: filters.value }) attractions.value = res } catch (err) { console.error('加载景点失败:', err) } finally { loading.value = false } } onMounted(() => { loadAttractions() }) </script> <template> <div class="container"> <h2>热门景点</h2> <div class="filters"> <select v-model="filters.city" @change="loadAttractions"> <option value="">全部城市</option> <option value="杭州">杭州</option> <option value="北京">北京</option> <option value="西安">西安</option> </select> <select v-model="filters.tag" @change="loadAttractions"> <option value="">全部标签</option> <option value="自然风光">自然风光</option> <option value="历史文化">历史文化</option> </select> </div> <div v-if="loading" class="loading">加载中...</div> <div v-else class="grid"> <div v-for="a in attractions" :key="a.id" class="card"> <h3>{{ a.name }}</h3> <p>{{ a.city }} · ⭐ {{ a.rating.toFixed(1) }}</p> <p>{{ a.description?.slice(0, 60) }}...</p> <button @click="$router.push(`/detail/${a.id}`)">查看详情</button> </div> </div> </div> </template>
类型定义补充(src/types/index.ts):
export interface AttractionItem { id: number name: string city: string description: string rating: number tags: string[] // 后端需在 /attractions 接口返回 tags 字段 }

注意:v-model绑定filters.city@change触发重新加载,比@input更节省请求次数;a.description?.slice(0, 60)使用可选链避免空值报错;卡片点击跳转至详情页,需提前在src/router/index.ts中配置path: '/detail/:id'的动态路由。

3.4 路由与状态管理:Pinia 存储用户收藏,实现跨组件同步

创建src/stores/favorites.ts

import { defineStore } from 'pinia' export const useFavoritesStore = defineStore('favorites', { state: () => ({ list: [] as number[], // 存储已收藏的景点 ID 数组 }), actions: { async toggle(id: number) { const index = this.list.indexOf(id) if (index > -1) { this.list.splice(index, 1) } else { this.list.push(id) } // 实际项目中此处应调用 API 同步到后端 await this.saveToServer() }, async saveToServer() { // 示例:发送收藏列表到 /api/favorites/update // await request.post('/favorites/update', { ids: this.list }) } } })

在景点卡片组件中使用:

<!-- 在 AttractionsList.vue 的 card 内 --> <button @click="favorites.toggle(a.id)"> {{ favorites.list.includes(a.id) ? '已收藏' : '收藏' }} </button>

引入 store:

import { useFavoritesStore } from '@/stores/favorites' const favorites = useFavoritesStore()

4. 数据库操作与部署:MySQL 初始化、数据导入及生产环境适配

4.1 创建数据库与导入初始数据:使用 SQL 脚本而非图形工具

项目附带的travel_db.sql文件应包含CREATE DATABASECREATE TABLEINSERT INTO语句。执行前需确保 MySQL 服务运行,并拥有 root 权限:

# 登录 MySQL mysql -u root -p # 执行建库与建表 mysql> CREATE DATABASE IF NOT EXISTS travel_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; mysql> USE travel_db; mysql> SOURCE /path/to/travel_db.sql;

travel_db.sql中未显式指定字符集,可能导致中文乱码。安全写法是在建表语句末尾添加:

ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

提示:utf8mb4支持 emoji 和生僻汉字;COLLATE=utf8mb4_unicode_ci提供更准确的中文排序与比较。

4.2 数据库字段设计验证:关键索引与外键约束检查

运行以下 SQL 验证表结构是否合理:

-- 检查 attractions 表主键与索引 SHOW INDEX FROM attractions; -- 应存在: -- PRIMARY KEY (id) -- KEY idx_city (city) -- 支持按城市查询 -- KEY idx_rating (rating) -- 支持按评分排序 -- 检查 ratings 表外键 SELECT CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = 'travel_db' AND TABLE_NAME = 'ratings';

若缺失idx_city索引,手动添加:

ALTER TABLE attractions ADD INDEX idx_city (city);

4.3 生产环境数据库连接配置:区分开发与生产 URL

在 Flask 项目中,通过config.py管理不同环境配置:

import os class Config: SQLALCHEMY_TRACK_MODIFICATIONS = False class DevelopmentConfig(Config): DEBUG = True DATABASE_URL = os.environ.get('DEV_DATABASE_URL') or \ 'mysql+pymysql://root:devpass@127.0.0.1:3306/travel_db' class ProductionConfig(Config): DEBUG = False DATABASE_URL = os.environ.get('PROD_DATABASE_URL') or \ 'mysql+pymysql://prod_user:prod_pass@prod-db-host:3306/travel_db' config = { 'development': DevelopmentConfig, 'production': ProductionConfig, 'default': DevelopmentConfig }

启动时指定配置:

export FLASK_ENV=production export PROD_DATABASE_URL="mysql+pymysql://user:pwd@aliyun-rds-host:3306/dbname" flask run --host=0.0.0.0 --port=5000

4.4 Vue 项目构建与静态资源部署:Nginx 代理反向转发

Vue 项目构建后生成dist/目录,需通过 Web 服务器托管。Nginx 配置示例(/etc/nginx/conf.d/travel.conf):

server { listen 80; server_name travel.example.com; root /var/www/travel-vue/dist; index index.html; location / { 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; } }

注意:try_files $uri $uri/ /index.html;解决 Vue Router history 模式下刷新 404 问题;location /api将前端所有以/api开头的请求转发至http://127.0.0.1:5000,避免跨域;proxy_set_header传递真实客户端 IP,便于后端日志记录。

5. 论文撰写与答辩准备:从系统架构图到核心算法伪代码的呈现技巧

5.1 系统架构图绘制:突出三层分离与数据流向,禁用模糊框图

使用 draw.io 或 PlantUML 绘制清晰架构图,必须包含以下元素:

  • 前端层:标注 Vue 3、Vite、Pinia、Axios,箭头指向“REST API”;
  • 后端层:标注 Flask、SQLAlchemy、MySQL,注明“推荐算法模块”位置;
  • 数据层:MySQL 图标,列出四张核心表名(users, attractions, tags, ratings);
  • 双向箭头:在前端↔后端间画实线箭头,标注HTTP GET/POST;在后端↔数据库间画实线箭头,标注SQL Query
  • 虚线框:将“推荐算法模块”单独框出,内部写Content-Based Similarity

提示:答辩 PPT 中架构图不宜超过 1/4 页面,文字用 18pt 以上;避免使用“云”“服务器集群”等虚化概念,本科毕设强调可复现性,图中每个组件都应有对应代码文件。

5.2 核心算法描述:用伪代码替代数学公式,体现工程落地能力

在论文“系统设计”章节中,推荐算法部分应写为可执行逻辑,而非抽象公式:

算法:基于标签的景点相似度推荐 输入:目标景点 A 的 ID 输出:最多 10 个推荐景点列表 1. 查询景点 A 的所有标签集合 T_A 2. 查询数据库:SELECT * FROM attractions AS B JOIN attraction_tags AS BT ON B.id = BT.attraction_id WHERE BT.tag_id IN (T_A) AND B.id != A.id 3. 对结果按 GROUP BY B.id 统计共同标签数量 count_common 4. 按 count_common DESC, B.rating DESC 排序 5. 取前 10 条,构造 {id, name, city, rating, common_tags_count} 对象返回
为什么这样写?
  • 第 2 步明确写出 SQL 片段,证明你理解数据库关联查询;
  • 第 3 步强调GROUP BY,避免学生误以为直接SELECT *即可;
  • 第 4 步排序逻辑分两层,体现业务权重(相关性优先,质量次之);
  • 输出字段与前端实际渲染字段一致,表明前后端契约清晰。

5.3 数据库设计说明书:用表格呈现字段含义与约束,替代 ER 图堆砌

在论文附录或“数据库设计”小节,用 Markdown 表格列出attractions表:

字段名类型是否为空键类型说明
idINTNOT NULLPRIMARY KEY自增主键
nameVARCHAR(100)NOT NULL景点名称,如“西湖”
cityVARCHAR(50)NOT NULLINDEX所属城市,用于筛选
descriptionTEXTYES简介文本,支持长内容
ratingFLOATDEFAULT 0.0动态计算的平均分,精度 0.1

注意:INDEX列写明“用于 WHERE city = ? 查询加速”,而非仅写“加索引”;DEFAULT 0.0明确初始值,避免答辩时被问“新景点评分如何初始化”。

5.4 答辩演示脚本:设计 3 分钟可完成的连贯操作流

准备一段无需解释、纯操作的演示路径,确保评委看到完整闭环:

  1. 打开浏览器 → 输入http://localhost:5173(Vue 开发服务器)
  2. 点击“杭州”下拉框 → 展示西湖、灵隐寺等卡片(验证城市筛选)
  3. 点击“西湖”卡片 → 进入详情页 → 点击“收藏”按钮 → 右上角收藏图标变红(验证状态同步)
  4. 点击“推荐”标签 → 显示“雷峰塔”“苏堤”等结果 → 悬停显示“共同标签:自然风光、历史文化”(验证推荐逻辑)
  5. 打开 Postman → GEThttp://localhost:5000/api/recommend/1→ 展示返回 JSON(验证后端接口)

提示:演示前关闭所有无关窗口;Postman 请求 URL 手动输入,不依赖历史记录;收藏状态变化需提前在数据库插入测试数据(如INSERT INTO ratings VALUES (1,1,5,NOW())),确保首次演示即生效。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 23:17:32

2026遂宁化工产品成分分析检测排名 TOP5 CMA 资质提供含量检测、纯度检测、元素分析 联系方式推荐

遂宁化工产品成分分析检测领域&#xff0c;大大小小的检测机构鳞次栉比&#xff0c;实力却鱼龙混杂。化工企业、新材料厂商、日化生产工厂、橡塑制造业以及食品医药企业的研发质检部门&#xff0c;在挑选合作方时&#xff0c;稍有不慎便可能误入无正规资质的陷阱。这类机构出具…

作者头像 李华
网站建设 2026/9/11 23:17:15

纯NumPy手写BP神经网络:前向传播与反向传播实现

简介&#xff1a;本资源是一份面向人工智能初学者与Python编程学习者的BP神经网络实践教程&#xff0c;聚焦反向传播原理与完整代码实现&#xff0c;适用于课程设计、课程实验及入门级项目开发。压缩包共19个文件&#xff0c;含4个核心Python源码&#xff08;net.py、train.py、…

作者头像 李华
网站建设 2026/9/11 23:12:09

机械毕业设计之C6132普通车床横向进给系统的数控改造

题目&#xff1a;毕业设计之C6132普通车床横向进给系统的数控改造一、项目介绍针对C6132普通车床横向进给系统加工精度低、生产效率不足、手动操作劳动强度大等问题&#xff0c;结合中小制造企业经济性需求&#xff0c;开展其数控改造研究。首先&#xff0c;通过现场检测明确原…

作者头像 李华
网站建设 2026/9/11 23:10:46

焊缝X射线底片缺陷检测实战:2647张数据集与YOLOv8训练全流程

简介&#xff1a;面向工业焊缝缺陷检测任务的目标检测数据集&#xff0c;包含2647张X射线底片清晰原图&#xff0c;对裂纹、未熔合、未渗透、空隙、夹渣及未融化6类缺陷进行矩形框标注&#xff0c;累计4766个目标框。数据采用VOC与YOLO双格式存储&#xff0c;图片、XML标注与TX…

作者头像 李华
网站建设 2026/9/11 23:09:21

Android投屏源码实战:基于Qt的H.264实时采集、UDP传输与渲染

简介&#xff1a;基于Qt的Android实时投屏软件源码项目&#xff0c;专为需要将Android手机画面实时同步至PC桌面的开发者准备&#xff0c;适配毕业设计、课程大作业、初期项目演示等场景&#xff0c;也适合具备一定C/Qt基础的学生进阶学习。资源包共31个文件&#xff0c;包含12…

作者头像 李华