news 2026/9/30 2:28:00

基于SpringBoot+Vue的摄影预约系统的设计与实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于SpringBoot+Vue的摄影预约系统的设计与实现

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片!

一、 项目背景与意义

随着生活水平的提高和社交媒体的普及,人们对个性化、高品质的摄影服务需求日益增长。传统的摄影预约方式,如电话、微信沟通,存在信息记录易错、流程不透明、时间安排冲突、客户与摄影师沟通成本高等痛点。因此,开发一个线上摄影预约系统,将预约流程标准化、数字化,具有重要的现实意义。

项目意义:

  • 提升用户体验:客户可以随时随地在线浏览摄影师作品、套餐详情,并自主选择时间完成预约,流程清晰便捷。
  • 提高商家效率:系统自动化处理预约、排期、提醒等环节,减少人工操作失误和沟通成本,提升工作室运营效率。
  • 优化资源管理:摄影师、影棚、服装道具等资源可以通过系统进行统一管理和可视化调度,避免资源冲突。
  • 数据驱动决策:系统积累的预约、客户、销售数据可用于分析业务趋势,辅助商家进行营销策略和套餐优化。
  • 技术实践价值:本项目采用前后端分离的流行架构,是学习与整合 Spring Boot、Vue.js、MySQL 等主流技术的典型实践案例。

二、 技术栈选型

本项目采用前后端分离架构,后端提供 RESTful API,前端负责页面展示与用户交互。

2.1 后端技术栈 (Spring Boot)

  • 核心框架:Spring Boot 2.x
  • Web 框架:Spring MVC
  • 数据持久层:MyBatis-Plus (简化 CRUD 操作)
  • 数据库:MySQL 8.0
  • 数据库连接池:HikariCP
  • 权限认证:Spring Security + JWT (JSON Web Token)
  • API 文档:Swagger2 / Knife4j
  • 项目构建:Maven
  • 其他工具:Lombok (简化实体类), Hutool (工具包)

2.2 前端技术栈 (Vue.js)

  • 核心框架:Vue 3 (Composition API)
  • 构建工具:Vite
  • 路由:Vue Router 4
  • 状态管理:Pinia
  • UI 组件库:Element Plus
  • HTTP 客户端:Axios
  • CSS 预处理器:Sass/SCSS

2.3 开发与部署

  • 版本控制:Git
  • 后端部署:可打包为 Jar 包,使用 Docker 容器化部署或直接运行于服务器。
  • 前端部署:使用 Nginx 进行静态资源托管。

三、 系统核心功能模块设计

系统主要分为前台用户端和后台管理端。

3.1 前台用户端功能

  • 用户注册/登录:支持手机号、邮箱注册,JWT 认证。
  • 首页展示:轮播图、推荐摄影师、热门套餐。
  • 摄影师/作品浏览:分页列表、详情查看。
  • 套餐浏览与选择:查看不同摄影套餐的详情、价格、样片。
  • 在线预约:选择套餐、摄影师、预约日期与时间、填写个人信息及特殊要求。
  • 个人中心:查看我的预约记录(待支付、待拍摄、已完成、已取消)、修改个人信息。
  • 订单支付:集成支付宝/微信支付沙箱环境。

3.2 后台管理端功能

  • 管理员登录:权限控制。
  • 仪表盘:显示核心数据概览(今日预约数、总收入等)。
  • 预约管理:查看所有预约,支持按状态筛选、详情查看、手动确认/取消。
  • 用户管理:用户列表、信息查看、禁用/启用。
  • 摄影师管理:增删改查摄影师信息、上传作品集。
  • 套餐管理:摄影套餐的创建、编辑、上下架。
  • 资源管理:影棚、服装道具等资源的排期与管理。
  • 系统设置:基础参数配置。

四、 数据库设计核心表

-- 用户表 CREATE TABLE `sys_user` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `username` varchar(50) DEFAULT NULL COMMENT '用户名', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `email` varchar(100) DEFAULT NULL COMMENT '邮箱', `password` varchar(255) DEFAULT NULL COMMENT '密码', `avatar` varchar(500) DEFAULT NULL COMMENT '头像', `status` tinyint DEFAULT '1' COMMENT '状态 1正常 0禁用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_phone` (`phone`), UNIQUE KEY `uk_email` (`email`) ) ENGINE=InnoDB COMMENT='用户表'; -- 摄影师表 CREATE TABLE `photographer` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL COMMENT '姓名', `title` varchar(100) DEFAULT NULL COMMENT '头衔/简介', `avatar` varchar(500) DEFAULT NULL COMMENT '头像', `description` text COMMENT '详细描述', `score` decimal(3,2) DEFAULT '5.00' COMMENT '评分', `status` tinyint DEFAULT '1' COMMENT '状态 1上架 0下架', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB COMMENT='摄影师表'; -- 摄影套餐表 CREATE TABLE `package` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '套餐名称', `cover_image` varchar(500) DEFAULT NULL COMMENT '封面图', `description` text COMMENT '套餐描述', `price` decimal(10,2) NOT NULL COMMENT '价格', `original_price` decimal(10,2) DEFAULT NULL COMMENT '原价', `duration` int DEFAULT NULL COMMENT '预计拍摄时长(分钟)', `status` tinyint DEFAULT '1' COMMENT '状态 1上架 0下架', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB COMMENT='摄影套餐表'; -- 预约订单表 (核心业务表) CREATE TABLE `appointment_order` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单号', `order_no` varchar(32) NOT NULL COMMENT '订单编号', `user_id` bigint NOT NULL COMMENT '用户ID', `photographer_id` bigint DEFAULT NULL COMMENT '摄影师ID', `package_id` bigint NOT NULL COMMENT '套餐ID', `appoint_date` date NOT NULL COMMENT '预约日期', `time_slot` varchar(50) NOT NULL COMMENT '时间段', `contact_name` varchar(50) NOT NULL COMMENT '联系人', `contact_phone` varchar(20) NOT NULL COMMENT '联系电话', `special_request` text COMMENT '特殊要求', `total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额', `pay_status` tinyint DEFAULT '0' COMMENT '支付状态 0待支付 1已支付 2已退款', `order_status` tinyint DEFAULT '0' COMMENT '订单状态 0待确认 1已确认 2已完成 3已取消', `pay_time` datetime DEFAULT NULL COMMENT '支付时间', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_user_id` (`user_id`), KEY `idx_appoint_date` (`appoint_date`) ) ENGINE=InnoDB COMMENT='预约订单表';

五、 核心代码实现示例

5.1 后端核心代码 (Spring Boot + MyBatis-Plus)

1. 预约订单创建接口 (Controller)

package com.photobook.appointment.controller; import com.photobook.appointment.common.R; import com.photobook.appointment.dto.AppointmentCreateDTO; import com.photobook.appointment.service.AppointmentOrderService; import com.photobook.appointment.vo.AppointmentOrderVO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; @RestController @RequestMapping("/api/appointment") @RequiredArgsConstructor @Api(tags = "预约订单管理") public class AppointmentOrderController { private final AppointmentOrderService appointmentOrderService; @PostMapping("/create") @ApiOperation("创建预约订单") public R<AppointmentOrderVO> createAppointment(@Valid @RequestBody AppointmentCreateDTO dto, @RequestHeader("Authorization") String token) { // 从JWT token中解析出当前用户ID (实际项目中通过拦截器处理) Long userId = JwtUtil.getUserIdFromToken(token.replace("Bearer ", "")); AppointmentOrderVO orderVO = appointmentOrderService.createOrder(userId, dto); return R.success(orderVO); } }

2. 预约订单创建服务 (Service)

package com.photobook.appointment.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.photobook.appointment.dto.AppointmentCreateDTO; import com.photobook.appointment.entity.AppointmentOrder; import com.photobook.appointment.entity.Package; import com.photobook.appointment.entity.Photographer; import com.photobook.appointment.exception.BusinessException; import com.photobook.appointment.mapper.AppointmentOrderMapper; import com.photobook.appointment.mapper.PackageMapper; import com.photobook.appointment.mapper.PhotographerMapper; import com.photobook.appointment.service.AppointmentOrderService; import com.photobook.appointment.utils.OrderNoGenerator; import com.photobook.appointment.vo.AppointmentOrderVO; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; @Service @RequiredArgsConstructor public class AppointmentOrderServiceImpl implements AppointmentOrderService { private final AppointmentOrderMapper appointmentOrderMapper; private final PackageMapper packageMapper; private final PhotographerMapper photographerMapper; @Override @Transactional(rollbackFor = Exception.class) public AppointmentOrderVO createOrder(Long userId, AppointmentCreateDTO dto) { // 1. 校验套餐是否存在且上架 Package pkg = packageMapper.selectById(dto.getPackageId()); if (pkg == null || pkg.getStatus() != 1) { throw new BusinessException("套餐不存在或已下架"); } // 2. 校验摄影师是否存在且可用 (如果指定了摄影师) if (dto.getPhotographerId() != null) { Photographer photographer = photographerMapper.selectById(dto.getPhotographerId()); if (photographer == null || photographer.getStatus() != 1) { throw new BusinessException("指定的摄影师不可用"); } } // 3. 校验预约时间冲突 (简化示例:检查同一天同一时间段是否已有预约) LambdaQueryWrapper<AppointmentOrder> conflictQuery = new LambdaQueryWrapper<>(); conflictQuery.eq(AppointmentOrder::getAppointDate, dto.getAppointDate()) .eq(AppointmentOrder::getTimeSlot, dto.getTimeSlot()) .in(AppointmentOrder::getOrderStatus, 0, 1); // 待确认和已确认的订单 Long conflictCount = appointmentOrderMapper.selectCount(conflictQuery); if (conflictCount > 0) { throw new BusinessException("该时间段已被预约,请选择其他时间"); } // 4. 构建订单实体 AppointmentOrder order = new AppointmentOrder(); order.setOrderNo(OrderNoGenerator.generate()); order.setUserId(userId); order.setPhotographerId(dto.getPhotographerId()); order.setPackageId(dto.getPackageId()); order.setAppointDate(dto.getAppointDate()); order.setTimeSlot(dto.getTimeSlot()); order.setContactName(dto.getContactName()); order.setContactPhone(dto.getContactPhone()); order.setSpecialRequest(dto.getSpecialRequest()); order.setTotalAmount(pkg.getPrice()); order.setPayStatus(0); order.setOrderStatus(0); // 5. 保存订单 appointmentOrderMapper.insert(order); // 6. 返回VO对象 (实际项目中可使用MapStruct进行转换) return convertToVO(order, pkg); } private AppointmentOrderVO convertToVO(AppointmentOrder order, Package pkg) { AppointmentOrderVO vo = new AppointmentOrderVO(); // ... 属性拷贝 return vo; } }

5.2 前端核心代码 (Vue 3 + Element Plus)

1. 预约页面组件 (Appointment.vue)

<template> <div class="appointment-page"> <el-steps :active="activeStep" finish-status="success"> <el-step title="选择套餐"></el-step> <el-step title="选择时间"></el-step> <el-step title="填写信息"></el-step> <el-step title="确认预约"></el-step> </el-steps> <!-- 步骤1: 选择套餐 --> <div v-if="activeStep === 0"> <h3>选择摄影套餐</h3> <el-row :gutter="20"> <el-col :span="8" v-for="pkg in packageList" :key="pkg.id"> <el-card :class="{ 'selected': selectedPackageId === pkg.id }" @click="selectPackage(pkg)"> <img :src="pkg.coverImage" class="package-cover" /> <div style="padding: 14px;"> <h4>{{ pkg.name }}</h4> <p>{{ pkg.description }}</p> <div class="price"> <span class="current-price">¥{{ pkg.price }}</span> <span class="original-price" v-if="pkg.originalPrice">¥{{ pkg.originalPrice }}</span> </div> </div> </el-card> </el-col> </el-row> <el-button type="primary" @click="nextStep" :disabled="!selectedPackageId">下一步</el-button> </div> <!-- 步骤2: 选择时间 --> <div v-if="activeStep === 1"> <h3>选择预约时间</h3> <el-date-picker v-model="form.appointDate" type="date" placeholder="选择日期" :disabled-date="disabledDate" @change="loadTimeSlots" /> <el-select v-model="form.timeSlot" placeholder="选择时间段"> <el-option v-for="slot in availableTimeSlots" :key="slot" :label="slot" :value="slot" /> </el-select> <div style="margin-top: 20px;"> <el-button @click="prevStep">上一步</el-button> <el-button type="primary" @click="nextStep" :disabled="!form.appointDate || !form.timeSlot">下一步</el-button> </div> </div> <!-- 步骤3: 填写信息 --> <div v-if="activeStep === 2"> <h3

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

别再死磕复杂算法!普通人真正能用得上的算法思维,其实很简单

提到算法&#xff0c;绝大多数人的第一印象就是晦涩难懂、门槛极高。满屏的代码公式、动态规划、二叉树、贪心逻辑&#xff0c;听起来就是程序员、算法工程师的专属技能&#xff0c;和普通上班族、普通人完全不搭边。很多人甚至下意识觉得&#xff0c;我不写代码、不做技术&…

作者头像 李华
网站建设 2026/9/30 2:25:33

yocto: 13-systemd Service

第3课&#xff1a;自己写 systemd Service —— 掌握开机服务 摘要&#xff1a;本教程详细介绍了如何在 Raspberry Pi 上创建自定义 systemd 服务&#xff0c;实现开机自动运行程序。内容包括&#xff1a;systemd 基本概念、service 文件结构解析、Yocto 集成方法、recipe 编写…

作者头像 李华
网站建设 2026/9/30 2:25:00

在Switch上跑通B站客户端:wiliwili入门到进阶

在Switch上跑通B站客户端&#xff1a;wiliwili入门到进阶 【免费下载链接】wiliwili 第三方B站客户端&#xff0c;目前可以运行在PC全平台、PSVita、PS4 、Xbox 和 Nintendo Switch上 项目地址: https://gitcode.com/GitHub_Trending/wi/wiliwili wiliwili 是一款专为手…

作者头像 李华
网站建设 2026/9/30 2:24:33

华为鸿蒙免费主题壁纸工具—小羊免费壁纸

改天亮屏想换一张&#xff0c;收藏夹却糊成一堆&#xff0c;锁屏用的、桌面用的、周末才想看的全搅在一起——翻半天才找到昨天顺眼的那张&#xff0c;兴致也散了。想换图的工具又常先催会员、弹广告&#xff0c;手又缩回去了。我做成了 小羊免费壁纸——喜欢的图分开放&#x…

作者头像 李华
网站建设 2026/9/30 2:23:25

bzip2 命令详解:Linux 下 .bz2 高压缩率压缩与解压实战指南

文档教程 【免费下载链接】linux-command Linux命令大全搜索工具&#xff0c;内容包含Linux命令手册、详解、学习、搜集。https://git.io/linux 项目地址&#xff1a; https://gitcode.com/GitHub_Trending/linux/linux-command 点击查看 免费下载 本文基于 linux-command 仓库…

作者头像 李华