摘要
移动互联网时代,微信小程序凭借免安装、易传播的特点成为旅游服务的重要载体。本文以“云游旅行”为课题,设计并实现了一套面向游客用户的旅游服务微信小程序。小程序基于uni-app 3框架与Vue 3 Composition API开发,UI层采用uView Plus组件库,通过Vite编译为微信小程序运行包,对接Spring Boot 3后端RESTful接口与MySQL数据库。小程序共包含14个页面,底部TabBar划分为首页、线路、景点、我的四大模块,实现线路浏览与发布、景点门票检索与预订、订单管理、评价发表、收藏切换及个人资料维护等完整业务。在工程实践方面,本文总结了utils工具层封装、列表页load模式、GET参数cleanParams过滤、模板数据预处理及小程序平台兼容性处理等关键经验。经微信开发者工具联调与功能测试,各页面交互流畅、接口调用稳定,能够满足课程设计中“移动端旅游信息化应用”的实现目标。
技术栈:Spring Boot3+uni-app+Vue3+uViewPlus+Vite+MybatsiPlus+Echarts+微信小程序
数据库表:8张
🍅文末获取联系🍅
🍅文末获取联系🍅
作者介绍:专注计算机课设、毕设辅导,个人开发,坚持原创,非工作室,源码全网唯一。
✅技术主流:SpringBoot+Vue+uni-app前后端分离,MySQL,Echarts,可本地运行
✅配套资料:源码 + 数据库 + 实验报告/论文 + 答辩 PPT+部署演示+远程调试+问题解答
技术范围:SpringBoot、Vue、数据可视化、小程序、HLMT、Nodejs、uni-app、MySQL数据库、ElementUi等设计与开发。
适用范围:软件工程、软件技术、数据库课程设计、计算机科学与技术、数据库系统原理、JavaWeb开发、JavaEE、Java、Web应用开发、动态网页设计的课程设计、课设、大作业、课程实验、期末作业
实验报告参考内容
实验报告可供大家参考使用
功能展示
角色 | 主要功能 |
游客 TOURIST | 浏览/发布线路、浏览景点、门票预订、我的订单/评价/收藏、个人资料与头像 |
景区 STAFF | 线路管理、景点门票、订单处理(确认/取消/完成)、评价回复、资料修改 |
管理员 ADMIN | 数据统计、订单查询与删除、线路/景点管理、评论管理、游客/景区账号管理 |
模块 | 说明 |
认证模块 | 三角色登录、游客注册、JWT 签发与校验 |
线路模块 | 线路 CRUD、上下架、游客我的线路、详情浏览 |
景点模块 | 景点门票 CRUD、库存与在售状态 |
订单模块 | 下单、状态流转、取消、单条/批量删除 |
评价模块 | 发表、回复、管理员显示隐藏、删除 |
收藏模块 | 添加/取消收藏、我的收藏列表 |
统计模块 | 管理员 KPI 与图表(仅 ADMIN) |
用户管理 | 游客与景区账号的增删改查与启用禁用 |
小程序
管理员+旅游景区
数据库及架构
系统数据库设计为:
Controller及Service层核心代码写法:
package com.springboot.controller; import com.springboot.auth.RequireRole; import com.springboot.dto.*; import com.springboot.entity.Attraction; import com.springboot.entity.UserRole; import com.springboot.service.AttractionService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; //景点门票管理 @RestController @RequestMapping("/api/attractions") @RequiredArgsConstructor public class AttractionController { private final AttractionService attractionService; @GetMapping("/browse") @RequireRole({UserRole.TOURIST}) public ApiResponse<PageResult<Attraction>> browse( @RequestParam(required = false) String keyword, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size) { return ApiResponse.ok(attractionService.browse(keyword, page, size)); } @GetMapping("/{id}") @RequireRole({UserRole.TOURIST, UserRole.STAFF, UserRole.ADMIN}) public ApiResponse<Attraction> get(@PathVariable Long id) { return ApiResponse.ok(attractionService.getById(id)); } @GetMapping @RequireRole({UserRole.STAFF, UserRole.ADMIN}) public ApiResponse<PageResult<Attraction>> list( @RequestParam(required = false) String keyword, @RequestParam(required = false) String status, @RequestParam(required = false) Long staff_id, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size) { return ApiResponse.ok(attractionService.list(keyword, status, staff_id, page, size)); } @PostMapping @RequireRole({UserRole.STAFF, UserRole.ADMIN}) public ApiResponse<Attraction> create(@Valid @RequestBody AttractionDTO dto) { return ApiResponse.ok("保存成功", attractionService.create(dto)); } @PutMapping("/{id}") @RequireRole({UserRole.STAFF, UserRole.ADMIN}) public ApiResponse<Attraction> update(@PathVariable Long id, @RequestBody AttractionDTO dto) { return ApiResponse.ok("保存成功", attractionService.update(id, dto)); } @PutMapping("/{id}/status") @RequireRole({UserRole.STAFF, UserRole.ADMIN}) public ApiResponse<Attraction> updateStatus(@PathVariable Long id, @RequestBody StatusDTO dto) { return ApiResponse.ok("状态已更新", attractionService.updateStatus(id, dto.getStatus())); } @DeleteMapping("/batch") @RequireRole({UserRole.STAFF, UserRole.ADMIN}) public ApiResponse<Void> batchDelete(@RequestBody IdsDTO dto) { attractionService.batchDelete(dto.getIds()); return ApiResponse.ok("删除成功", null); } @DeleteMapping("/{id}") @RequireRole({UserRole.STAFF, UserRole.ADMIN}) public ApiResponse<Void> delete(@PathVariable Long id) { attractionService.delete(id); return ApiResponse.ok("删除成功", null); } } package com.springboot.service; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.springboot.auth.AuthContext; import com.springboot.dto.AttractionDTO; import com.springboot.dto.PageResult; import com.springboot.entity.Attraction; import com.springboot.entity.Review; import com.springboot.entity.Staff; import com.springboot.mapper.AttractionMapper; import com.springboot.mapper.ReviewMapper; import com.springboot.mapper.StaffMapper; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import java.util.*; import java.util.stream.Collectors; //景点门票管理 @Service @RequiredArgsConstructor public class AttractionService { private final AttractionMapper attractionMapper; private final StaffMapper staffMapper; private final ReviewMapper reviewMapper; private final StaffService staffService; public PageResult<Attraction> browse(String keyword, int page, int size) { var wrapper = Wrappers.<Attraction>lambdaQuery() .eq(Attraction::getStatus, "ON_SALE") .like(StringUtils.hasText(keyword), Attraction::getName, keyword) .orderByDesc(Attraction::getId); Page<Attraction> result = attractionMapper.selectPage(new Page<>(page, size), wrapper); enrich(result.getRecords()); return PageResult.of(result); } public Attraction getById(Long id) { Attraction attraction = attractionMapper.selectById(id); if (attraction == null) throw new RuntimeException("景点不存在"); enrich(List.of(attraction)); return attraction; } public PageResult<Attraction> list(String keyword, String status, Long staffId, int page, int size) { if (AuthContext.isStaff()) staffId = staffService.getCurrentStaffId(); var wrapper = Wrappers.<Attraction>lambdaQuery() .eq(staffId != null, Attraction::getStaff_id, staffId) .like(StringUtils.hasText(keyword), Attraction::getName, keyword) .eq(StringUtils.hasText(status), Attraction::getStatus, status) .orderByDesc(Attraction::getId); Page<Attraction> result = attractionMapper.selectPage(new Page<>(page, size), wrapper); enrich(result.getRecords()); return PageResult.of(result); } @Transactional public Attraction create(AttractionDTO dto) { Long staffId = dto.getStaff_id(); if (AuthContext.isStaff()) staffId = staffService.getCurrentStaffId(); if (staffMapper.selectById(staffId) == null) throw new RuntimeException("景区不存在"); Attraction attraction = build(new Attraction(), dto); attraction.setStaff_id(staffId); if (!StringUtils.hasText(attraction.getStatus())) attraction.setStatus("ON_SALE"); if (attraction.getStock() == null) attraction.setStock(0); attractionMapper.insert(attraction); enrich(List.of(attraction)); return attraction; } @Transactional public Attraction update(Long id, AttractionDTO dto) { Attraction attraction = getOwned(id); if (AuthContext.isStaff()) dto.setStaff_id(attraction.getStaff_id()); build(attraction, dto); attractionMapper.updateById(attraction); enrich(List.of(attraction)); return attraction; } @Transactional public Attraction updateStatus(Long id, String status) { Attraction attraction = getOwned(id); attraction.setStatus(status); attractionMapper.updateById(attraction); enrich(List.of(attraction)); return attraction; } @Transactional public void delete(Long id) { Attraction attraction = getOwned(id); attractionMapper.deleteById(attraction.getId()); } @Transactional public void batchDelete(List<Long> ids) { if (ids == null || ids.isEmpty()) throw new RuntimeException("请选择要删除的数据"); for (Long id : ids) delete(id); } private Attraction getOwned(Long id) { Attraction attraction = attractionMapper.selectById(id); if (attraction == null) throw new RuntimeException("景点不存在"); if (AuthContext.isStaff() && !Objects.equals(attraction.getStaff_id(), staffService.getCurrentStaffId())) { throw new RuntimeException("无权操作该景点"); } return attraction; } private Attraction build(Attraction attraction, AttractionDTO dto) { if (dto.getStaff_id() != null) attraction.setStaff_id(dto.getStaff_id()); attraction.setName(dto.getName()); attraction.setLocation(dto.getLocation()); attraction.setImage_url(dto.getImage_url()); attraction.setDescription(dto.getDescription()); attraction.setTicket_price(dto.getTicket_price()); if (dto.getStock() != null) attraction.setStock(dto.getStock()); if (StringUtils.hasText(dto.getStatus())) attraction.setStatus(dto.getStatus()); return attraction; } private void enrich(List<Attraction> list) { if (list.isEmpty()) return; Set<Long> staffIds = list.stream().map(Attraction::getStaff_id).filter(Objects::nonNull).collect(Collectors.toSet()); Map<Long, Staff> staffMap = staffIds.isEmpty() ? Map.of() : staffMapper.selectBatchIds(staffIds).stream().collect(Collectors.toMap(Staff::getId, s -> s, (a, b) -> a)); for (Attraction a : list) { Staff staff = staffMap.get(a.getStaff_id()); if (staff != null) a.setScenic_name(StringUtils.hasText(staff.getScenic_name()) ? staff.getScenic_name() : staff.getUsername()); List<Review> reviews = reviewMapper.selectList(Wrappers.<Review>lambdaQuery() .eq(Review::getTarget_type, "ATTRACTION") .eq(Review::getTarget_id, a.getId()) .eq(Review::getStatus, "VISIBLE")); a.setReview_count((long) reviews.size()); if (!reviews.isEmpty()) { a.setAvg_rating(reviews.stream().mapToInt(Review::getRating).average().orElse(0)); } } } }擅长:功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文降重、长期答辩答疑辅导、腾讯会议一对一专业讲解辅导答辩、模拟答辩演练、和理解代码逻辑思路等。
获取联系
项目功能完整,可在本地运行,并可远程调试,确保运行顺利!
👇🏻👇🏻获取联系方式👇🏻👇🏻
课程设计获取
https://blog.csdn.net/qq_59059632/article/details/163685632?spm=1001.2014.3001.5501