学习springBoot框架-开发一个酒店管理系统,来熟悉springboot框架语法~

想快速掌握一个框架,就是要不停的写项目,看别人的项目,让自己学习到的编程知识学以致用。今天就给大家分享我最近使用springboot2.7 开发的一个前端后分离项目:酒店管理系统,来练习自己的编程技术。

java的版本是:21

springboot版本是:2.7

数据库操作:mybatis-plus

前端使用的是 vue2 + element-ui

mysql:8

写这个项目主要是练习从0到1自己搭建一个项目并完成需求开发。因为是练习项目,功能做的也不是很多,主要做了: 首页统计 酒店管理 楼宇管理 房间管理 会员管理 开房登记 登记管理 设备维修

安全检查 管理员管理。

接下来跟大家分享一些页面效果:

首页:

后端代码:

bash 复制代码
package com.jsonll.base.controller;


import com.jsonll.base.core.R;
import com.jsonll.base.mapper.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * 首页控制器
 */
@RestController
@RequestMapping("/home")
public class HomeController extends BaseController {

    @Autowired
    private HotelMapper hotelMapper;
    
    @Autowired
    private HotelBuildingMapper hotelBuildingMapper;
    
    @Autowired
    private RoomMapper roomMapper;
    
    @Autowired
    private MemberMapper memberMapper;
    
    @Autowired
    private RoomRegistrationMapper roomRegistrationMapper;
    
    @Autowired
    private DeviceRepairMapper deviceRepairMapper;
    
    @Autowired
    private SafetyInspectionMapper safetyInspectionMapper;

    /**
     * 获取首页统计数据
     * @return 统计数据
     */
    @GetMapping("data")
    public R data(){
        Map<String, Object> result = new HashMap<>();
        
        // 酒店数量
        long hotelCount = hotelMapper.selectCount(null);
        // 楼宇数量
        long buildingCount = hotelBuildingMapper.selectCount(null);
        // 房间数量
        long roomCount = roomMapper.selectCount(null);
        // 会员数量
        long memberCount = memberMapper.selectCount(null);
        
        // 房间状态统计
        List<Map<String, Object>> roomStatusStats = roomMapper.getRoomStatusStats();
        
        // 入住登记统计(按月)
        List<Map<String, Object>> checkInMonthlyStats = roomRegistrationMapper.getCheckInMonthlyStats();
        
        // 设备维修统计
        List<Map<String, Object>> repairStatusStats = deviceRepairMapper.getRepairStatusStats();
        
        // 安全检查统计(按月)
        List<Map<String, Object>> safetyMonthlyStats = safetyInspectionMapper.getSafetyMonthlyStats();
        
        result.put("hotelCount", hotelCount);
        result.put("buildingCount", buildingCount);
        result.put("roomCount", roomCount);
        result.put("memberCount", memberCount);
        result.put("roomStatusStats", roomStatusStats);
        result.put("checkInMonthlyStats", checkInMonthlyStats);
        result.put("repairStatusStats", repairStatusStats);
        result.put("safetyMonthlyStats", safetyMonthlyStats);
        
        return R.successData(result);
    }
}

前端代码:

bash 复制代码
<template>
  <div class="home-container">
    <!-- 数量统计卡片 -->
    <div class="count-cards">
      <div class="count-card">
        <div class="card-icon">
          <i class="el-icon-office-building"></i>
        </div>
        <div class="card-content">
          <div class="card-value">{{ statsData.hotelCount }}</div>
          <div class="card-title">酒店数量</div>
        </div>
      </div>
      
      <div class="count-card">
        <div class="card-icon">
          <i class="el-icon-school"></i>
        </div>
        <div class="card-content">
          <div class="card-value">{{ statsData.buildingCount }}</div>
          <div class="card-title">楼宇数量</div>
        </div>
      </div>
      
      <div class="count-card">
        <div class="card-icon">
          <i class="el-icon-house"></i>
        </div>
        <div class="card-content">
          <div class="card-value">{{ statsData.roomCount }}</div>
          <div class="card-title">房间数量</div>
        </div>
      </div>
      
      <div class="count-card">
        <div class="card-icon">
          <i class="el-icon-user"></i>
        </div>
        <div class="card-content">
          <div class="card-value">{{ statsData.memberCount }}</div>
          <div class="card-title">会员数量</div>
        </div>
      </div>
    </div>
    
    <div class="chart-container">
      <!-- 左侧图表 -->
      <div class="chart-left">
        <!-- 房间状态统计图表 -->
        <div class="chart-item">
          <div class="chart-title">房间状态统计</div>
          <div ref="roomStatusChart" class="chart"></div>
        </div>
        
        <!-- 设备维修状态统计图表 -->
        <div class="chart-item">
          <div class="chart-title">设备维修状态统计</div>
          <div ref="repairStatusChart" class="chart"></div>
        </div>
      </div>
      
      <!-- 右侧图表 -->
      <div class="chart-right">
        <!-- 入住登记月度统计图表(近6个月) -->
        <div class="chart-item">
          <div class="chart-title">入住登记月度统计(近6个月)</div>
          <div ref="checkInMonthlyChart" class="chart"></div>
        </div>
        
        <!-- 安全检查月度统计图表(近6个月) -->
        <div class="chart-item">
          <div class="chart-title">安全检查月度统计(近6个月)</div>
          <div ref="safetyMonthlyChart" class="chart"></div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
// 引入echarts
import * as echarts from 'echarts'
import { getHomeData } from '@/api/home'

export default {
  name: 'Home',
  data() {
    return {
      // 图表实例
      roomStatusChartInstance: null,
      checkInMonthlyChartInstance: null,
      repairStatusChartInstance: null,
      safetyMonthlyChartInstance: null,
      
      // 统计数据
      statsData: {
        hotelCount: 0,
        buildingCount: 0,
        roomCount: 0,
        memberCount: 0,
        roomStatusStats: [],
        checkInMonthlyStats: [],
        repairStatusStats: [],
        safetyMonthlyStats: []
      }
    }
  },
  mounted() {
    // 初始化图表
    this.initCharts()
    // 获取数据
    this.fetchData()
  },
  methods: {
    // 初始化所有图表
    initCharts() {
      // 初始化房间状态图表
      this.roomStatusChartInstance = echarts.init(this.$refs.roomStatusChart)
      
      // 初始化入住登记月度图表
      this.checkInMonthlyChartInstance = echarts.init(this.$refs.checkInMonthlyChart)
      
      // 初始化设备维修状态图表
      this.repairStatusChartInstance = echarts.init(this.$refs.repairStatusChart)
      
      // 初始化安全检查月度图表
      this.safetyMonthlyChartInstance = echarts.init(this.$refs.safetyMonthlyChart)
      
      // 监听窗口大小变化,调整图表大小
      window.addEventListener('resize', this.resizeCharts)
    },
    
    // 调整所有图表大小
    resizeCharts() {
      this.roomStatusChartInstance && this.roomStatusChartInstance.resize()
      this.checkInMonthlyChartInstance && this.checkInMonthlyChartInstance.resize()
      this.repairStatusChartInstance && this.repairStatusChartInstance.resize()
      this.safetyMonthlyChartInstance && this.safetyMonthlyChartInstance.resize()
    },
    
    // 获取统计数据
    async fetchData() {
      try {
        const res = await getHomeData()
        if (res.code === 1000 && res.data) {
          this.statsData = res.data
          // 更新图表
          this.updateCharts()
        }
      } catch (error) {
        console.error('获取首页数据失败', error)
      }
    },
    
    // 更新所有图表
    updateCharts() {
      this.updateRoomStatusChart()
      this.updateCheckInMonthlyChart()
      this.updateRepairStatusChart()
      this.updateSafetyMonthlyChart()
    },
    
    // 更新房间状态图表
    updateRoomStatusChart() {
      // 房间状态映射
      const statusMap = {
        1: '空闲',
        2: '入住中',
        3: '维修中'
      }
      
      // 处理数据
      const data = this.statsData.roomStatusStats.map(item => {
        return {
          name: statusMap[item.status] || `状态${item.status}`,
          value: item.count
        }
      })
      
      // 设置图表配置
      const option = {
        tooltip: {
          trigger: 'item',
          formatter: '{a} <br/>{b}: {c} ({d}%)'
        },
        legend: {
          orient: 'vertical',
          left: 10,
          data: data.map(item => item.name)
        },
        series: [
          {
            name: '房间状态',
            type: 'pie',
            radius: ['50%', '70%'],
            avoidLabelOverlap: false,
            itemStyle: {
              borderRadius: 10,
              borderColor: '#fff',
              borderWidth: 2
            },
            label: {
              show: false,
              position: 'center'
            },
            emphasis: {
              label: {
                show: true,
                fontSize: '18',
                fontWeight: 'bold'
              }
            },
            labelLine: {
              show: false
            },
            data: data
          }
        ]
      }
      
      // 更新图表
      this.roomStatusChartInstance.setOption(option)
    },
    
    // 更新入住登记月度图表
    updateCheckInMonthlyChart() {
      // 处理数据
      const months = this.statsData.checkInMonthlyStats.map(item => item.month)
      const counts = this.statsData.checkInMonthlyStats.map(item => item.count)
      
      // 设置图表配置
      const option = {
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'shadow'
          }
        },
        grid: {
          left: '3%',
          right: '4%',
          bottom: '3%',
          containLabel: true
        },
        xAxis: {
          type: 'category',
          data: months,
          axisTick: {
            alignWithLabel: true
          }
        },
        yAxis: {
          type: 'value'
        },
        series: [
          {
            name: '入住登记数',
            type: 'bar',
            barWidth: '60%',
            data: counts,
            itemStyle: {
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: '#83bff6' },
                { offset: 0.5, color: '#188df0' },
                { offset: 1, color: '#188df0' }
              ])
            }
          }
        ]
      }
      
      // 更新图表
      this.checkInMonthlyChartInstance.setOption(option)
    },
    
    // 更新设备维修状态图表
    updateRepairStatusChart() {
      // 维修状态映射
      const statusMap = {
        1: '正在维修',
        2: '已维修',
        3: '放弃维修'
      }
      
      // 处理数据
      const data = this.statsData.repairStatusStats.map(item => {
        return {
          name: statusMap[item.status] || `状态${item.status}`,
          value: item.count
        }
      })
      
      // 设置图表配置
      const option = {
        tooltip: {
          trigger: 'item',
          formatter: '{a} <br/>{b}: {c} ({d}%)'
        },
        legend: {
          orient: 'vertical',
          left: 10,
          data: data.map(item => item.name)
        },
        series: [
          {
            name: '维修状态',
            type: 'pie',
            radius: '50%',
            data: data,
            emphasis: {
              itemStyle: {
                shadowBlur: 10,
                shadowOffsetX: 0,
                shadowColor: 'rgba(0, 0, 0, 0.5)'
              }
            }
          }
        ]
      }
      
      // 更新图表
      this.repairStatusChartInstance.setOption(option)
    },
    
    // 更新安全检查月度图表
    updateSafetyMonthlyChart() {
      // 处理数据
      const months = this.statsData.safetyMonthlyStats.map(item => item.month)
      const counts = this.statsData.safetyMonthlyStats.map(item => item.count)
      
      // 设置图表配置
      const option = {
        tooltip: {
          trigger: 'axis'
        },
        grid: {
          left: '3%',
          right: '4%',
          bottom: '3%',
          containLabel: true
        },
        xAxis: {
          type: 'category',
          boundaryGap: false,
          data: months
        },
        yAxis: {
          type: 'value'
        },
        series: [
          {
            name: '安全检查数',
            type: 'line',
            stack: '总量',
            areaStyle: {},
            emphasis: {
              focus: 'series'
            },
            data: counts
          }
        ]
      }
      
      // 更新图表
      this.safetyMonthlyChartInstance.setOption(option)
    }
  },
  beforeDestroy() {
    // 移除窗口大小变化监听
    window.removeEventListener('resize', this.resizeCharts)
    
    // 销毁图表实例
    this.roomStatusChartInstance && this.roomStatusChartInstance.dispose()
    this.checkInMonthlyChartInstance && this.checkInMonthlyChartInstance.dispose()
    this.repairStatusChartInstance && this.repairStatusChartInstance.dispose()
    this.safetyMonthlyChartInstance && this.safetyMonthlyChartInstance.dispose()
  }
}
</script>

<style lang="scss" scoped>
.home-container {
  padding: 20px;
  
  // 数量统计卡片样式
  .count-cards {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    margin-bottom: 20px;
    
    .count-card {
      width: 23%;
      background-color: #fff;
      border-radius: 4px;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
      padding: 20px;
      display: flex;
      align-items: center;
      margin-bottom: 15px;
      
      .card-icon {
        width: 60px;
        height: 60px;
        border-radius: 50%;
        background-color: #f0f9eb;
        display: flex;
        justify-content: center;
        align-items: center;
        margin-right: 15px;
        
        i {
          font-size: 30px;
          color: #67c23a;
        }
      }
      
      &:nth-child(2) .card-icon {
        background-color: #f2f6fc;
        i {
          color: #409eff;
        }
      }
      
      &:nth-child(3) .card-icon {
        background-color: #fdf6ec;
        i {
          color: #e6a23c;
        }
      }
      
      &:nth-child(4) .card-icon {
        background-color: #fef0f0;
        i {
          color: #f56c6c;
        }
      }
      
      .card-content {
        flex: 1;
        
        .card-value {
          font-size: 24px;
          font-weight: bold;
          color: #333;
          line-height: 1.2;
        }
        
        .card-title {
          font-size: 14px;
          color: #999;
          margin-top: 5px;
        }
      }
    }
  }
  
  .chart-container {
    display: flex;
    justify-content: space-between;
    
    .chart-left {
      width: 38%;
      
      .chart-item {
        height: 400px;
        margin-bottom: 20px;
        background-color: #fff;
        border-radius: 4px;
        box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
        padding: 20px;
        
        .chart-title {
          font-size: 18px;
          font-weight: bold;
          margin-bottom: 15px;
          color: #333;
        }
        
        .chart {
          width: 100%;
          height: calc(100% - 35px);
        }
      }
    }
    
    .chart-right {
      width: 60%;
      
      .chart-item {
        height: 400px;
        margin-bottom: 20px;
        background-color: #fff;
        border-radius: 4px;
        box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
        padding: 20px;
        
        .chart-title {
          font-size: 18px;
          font-weight: bold;
          margin-bottom: 15px;
          color: #333;
        }
        
        .chart {
          width: 100%;
          height: calc(100% - 35px);
        }
      }
    }
  }
}

@media screen and (max-width: 1200px) {
  .home-container .chart-container .chart-item {
    width: 100%;
  }
  
  .home-container .count-cards .count-card {
    width: 48%;
  }
}

@media screen and (max-width: 768px) {
  .home-container .count-cards .count-card {
    width: 100%;
  }
}
</style>

登记入住页面效果:

bash 复制代码
package com.jsonll.base.controller;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jsonll.base.core.R;
import com.jsonll.base.entity.RoomRegistration;
import com.jsonll.base.request.RegistrationRequest;
import com.jsonll.base.service.IRoomRegistrationService;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.Map;

/**
 * 房间登记 控制器
 */
@RestController
@RequestMapping("/registration")
public class RoomRegistrationController {

    @Resource
     IRoomRegistrationService roomRegistrationService;

    /**
     * 分页查询房间登记列表
     */
    @PostMapping("/page")
    public R page(@RequestBody RegistrationRequest request) {
        Page<RoomRegistration> page = roomRegistrationService.pageList(request);
        return R.successData(page);
    }

    /**
     * 登记入住
     */
    @PostMapping("/register")
    public R register(@RequestBody RegistrationRequest request) {
        boolean result = roomRegistrationService.register(request);
        return result ? R.success() : R.error("登记入住失败");
    }
    
    /**
     * 获取房间当前有效的登记信息
     */
    @GetMapping("/getCurrentRegistration/{roomId}")
    public R getCurrentRegistration(@PathVariable Integer roomId) {
        RoomRegistration registration = roomRegistrationService.getCurrentRegistration(roomId);
        return R.successData(registration);
    }
    
    /**
     * 续期入住
     */
    @PostMapping("/renew")
    public R renew(@RequestBody RegistrationRequest request) {
        boolean result = roomRegistrationService.renew(request);
        return result ? R.success() : R.error("续期入住失败");
    }
    
    /**
     * 退房
     */
    @PostMapping("/checkout/{roomId}")
    public R checkout(@PathVariable Integer roomId) {
        boolean result = roomRegistrationService.checkout(roomId);
        return result ? R.success() : R.error("退房失败");
    }
    
    /**
     * 获取房间登记详情(包含子表数据)
     */
    @GetMapping("/detail/{id}")
    public R getDetail(@PathVariable Integer id) {
        Map<String, Object> detailMap = roomRegistrationService.getRegistrationDetail(id);
        return R.successData(detailMap);
    }
}

前端代码:

bash 复制代码
<template>
  <div class="checkin-container">
    <!-- 上部分:搜索条件 -->
    <div class="search-container">
      <el-form :inline="true" :model="searchForm" class="search-form">
        <el-form-item label="酒店">
          <el-select v-model="searchForm.hotelId" placeholder="请选择酒店" @change="handleHotelChange">
            <el-option
              v-for="item in hotelOptions"
              :key="item.id"
              :label="item.hotelName"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="楼宇">
          <el-select v-model="searchForm.buildingId" placeholder="请选择楼宇" @change="handleBuildingChange" :disabled="!searchForm.hotelId">
            <el-option
              v-for="item in buildingOptions"
              :key="item.id"
              :label="item.buildingName"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="楼层">
          <el-select v-model="searchForm.floorId" placeholder="请选择楼层" @change="handleSearch" :disabled="!searchForm.buildingId">
            <el-option
              v-for="item in floorOptions"
              :key="item.id"
              :label="item.floorName"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">查询</el-button>
          <el-button @click="resetSearch">重置</el-button>
        </el-form-item>
      </el-form>
    </div>

    <!-- 下部分:房间列表 -->
    <div class="room-container">
      <div class="room-list">
        <div 
          v-for="room in roomList" 
          :key="room.id" 
          class="room-item"
          :class="getRoomStatusClass(room.roomStatus)"
        >
          <div class="room-icon">
            <i class="el-icon-house"></i>
          </div>
          <div class="room-info">
            <div class="room-number">{{ room.roomNumber }}</div>
            <div class="room-name">{{ room.roomName }}</div>
            <div class="room-status">{{ getRoomStatusText(room.roomStatus) }}</div>
            <div class="room-features">
              <span class="feature-item">
                <i class="el-icon-sunny"></i>
                {{ room.isSouth==1?'朝南' : '非朝南' }}
              </span>
              <span class="feature-item">
                <i class="el-icon-view"></i>
                {{ room.hasWindow === 1 ? '有窗' : '无窗' }}
              </span>
            </div>
          </div>
          <div class="room-actions">
            <el-button 
              v-if="room.roomStatus == 1" 
              type="primary" 
              size="mini" 
              @click="handleRegister(room)"
            >登记</el-button>
            <template v-if="room.roomStatus == 2">
              <el-button type="warning" size="mini" @click="handleRenew(room)">续期</el-button>
              <el-button type="danger" size="mini" @click="handleCheckout(room)">退房</el-button>
            </template>
          </div>
        </div>
        <div v-if="roomList.length === 0" class="no-data">
          <span>暂无房间数据</span>
        </div>
      </div>
    </div>

    <!-- 登记弹窗 -->
    <el-dialog title="房间登记" :visible.sync="registerDialogVisible" width="900px">
      <el-form :model="registerForm" :rules="registerRules" ref="registerForm" label-width="100px" class="register-form">
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="登记类型" prop="registrationType">
              <el-radio-group v-model="registerForm.registrationType">
                <el-radio :label="1">临时入驻</el-radio>
                <el-radio :label="2">会员入驻</el-radio>
              </el-radio-group>
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row :gutter="20" v-if="registerForm.registrationType === 2">
          <el-col :span="12">
            <el-form-item label="会员" prop="memberId">
              <el-select v-model="registerForm.memberId" placeholder="请选择会员" @change="handleMemberChange" filterable>
                <el-option
                  v-for="item in memberOptions"
                  :key="item.id"
                  :label="item.memberName"
                  :value="item.id"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="入住人姓名" prop="guestName">
              <el-input v-model="registerForm.guestName" placeholder="请输入入住人姓名"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="入住人电话" prop="guestPhone">
              <el-input v-model="registerForm.guestPhone" placeholder="请输入入住人电话"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="身份证" prop="idCard">
              <el-input v-model="registerForm.idCard" placeholder="请输入入住人身份证"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="是否早餐" prop="needBreakfast">
              <el-switch
                v-model="registerForm.needBreakfast"
                :active-value="1"
                :inactive-value="0"
              ></el-switch>
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="入住开始时间" prop="checkInTime">
              <el-date-picker
                v-model="registerForm.checkInTime"
                type="datetime"
                placeholder="选择入住开始时间"
                style="width: 100%"
              ></el-date-picker>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="入住到期时间" prop="checkOutTime">
              <el-date-picker
                v-model="registerForm.checkOutTime"
                type="datetime"
                placeholder="选择入住到期时间"
                style="width: 100%"
              ></el-date-picker>
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="随行人员">
              <div v-for="(companion, index) in registerForm.companions" :key="index" class="companion-item">
                <el-input v-model="companion.name" placeholder="姓名" style="width: 200px; margin-right: 10px;"></el-input>
                <el-input v-model="companion.idCard" placeholder="身份证" style="width: 300px; margin-right: 10px;"></el-input>
                <el-button type="danger" icon="el-icon-delete" circle @click="removeCompanion(index)"></el-button>
              </div>
              <el-button type="primary" icon="el-icon-plus" @click="addCompanion">添加随行人员</el-button>
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="备注">
              <el-input type="textarea" v-model="registerForm.remarks" placeholder="请输入备注信息"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row :gutter="20">
          <el-col :span="8">
            <el-form-item label="订单金额">
              <el-input-number v-model="registerForm.orderAmount" :precision="2" :step="10" :min="0"></el-input-number>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="支付金额">
              <el-input-number v-model="registerForm.paymentAmount" :precision="2" :step="10" :min="0"></el-input-number>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="优惠金额">
              <el-input-number v-model="registerForm.discountAmount" :precision="2" :step="10" :min="0"></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="registerDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="submitRegister">确 定</el-button>
      </div>
    </el-dialog>
    
    <!-- 续期弹窗 -->
    <el-dialog title="房间续期" :visible.sync="renewDialogVisible" width="1200px">
      <el-form :model="renewForm" :rules="renewRules" ref="renewForm" label-width="120px" class="renew-form">
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="入住人姓名">
              <el-input v-model="renewForm.guestName" disabled></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="入住人联系电话">
              <el-input v-model="renewForm.guestPhone" disabled></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="入住开始时间">
              <el-date-picker
                v-model="renewForm.checkInTime"
                type="datetime"
                placeholder="选择入住开始时间"
                style="width: 100%"
                disabled
              ></el-date-picker>
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="入住到期时间" prop="checkOutTime">
              <el-date-picker
                v-model="renewForm.checkOutTime"
                type="datetime"
                placeholder="选择入住到期时间"
                style="width: 100%"
              ></el-date-picker>
            </el-form-item>
          </el-col>
        </el-row>
        
        <el-row :gutter="20">
          <el-col :span="8">
            <el-form-item label="订单金额">
              <el-input-number v-model="renewForm.orderAmount" :precision="2" :step="10" :min="0"></el-input-number>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="支付金额">
              <el-input-number v-model="renewForm.paymentAmount" :precision="2" :step="10" :min="0"></el-input-number>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="优惠金额">
              <el-input-number v-model="renewForm.discountAmount" :precision="2" :step="10" :min="0"></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="renewDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="submitRenew">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { getRoomList, registerRoom, getMemberList, getHotelList, getBuildingList, getFloorList, getCurrentRegistration, renewRegistration, checkoutRoom } from '@/api/registration'
import { parseTime } from '@/utils'

export default {
  name: 'Checkin',
  data() {
    return {
      // 搜索表单
      searchForm: {
        hotelId: '',
        buildingId: '',
        floorId: ''
      },
      // 下拉选项
      hotelOptions: [],
      buildingOptions: [],
      floorOptions: [],
      memberOptions: [],
      // 房间列表
      roomList: [],
      // 登记弹窗
      registerDialogVisible: false,
      // 续期弹窗
      renewDialogVisible: false,
      // 登记表单
      registerForm: {
        registrationType: 1, // 1临时入驻 2会员入驻
        memberId: null,
        guestName: '',
        guestPhone: '',
        roomId: null,
        checkInTime: parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}'),
        checkOutTime: '',
        idCard: '',
        companions: [],
        remarks: '',
        needBreakfast: 0,
        orderAmount: 0,
        paymentAmount: 0,
        discountAmount: 0
      },
      // 续期表单
      renewForm: {
        id: '', // 当前登记记录ID
        guestName: '',
        guestPhone: '',
        checkInTime: '',
        checkOutTime: '',
        orderAmount: 0,
        paymentAmount: 0,
        discountAmount: 0
      },
      // 表单验证规则
      registerRules: {
        guestName: [
          { required: true, message: '请输入入住人姓名', trigger: 'blur' }
        ],
        guestPhone: [
          { required: true, message: '请输入入住人电话', trigger: 'blur' },
          { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
        ],
        idCard: [
          { required: true, message: '请输入入住人身份证', trigger: 'blur' },
        ],
        checkInTime: [
          { required: true, message: '请选择入住时间', trigger: 'change' }
        ],
        checkOutTime: [
          { required: true, message: '请选择到期时间', trigger: 'change' }
        ],
        memberId: [
          { required: true, message: '请选择会员', trigger: 'change' }
        ]
      },
      // 续期表单验证规则
      renewRules: {
        checkOutTime: [
          { required: true, message: '请选择入住到期时间', trigger: 'change' }
        ]
      }
    }
  },
  created() {
    this.fetchHotelList();
    this.handleSearch();
  },
  methods: {
    // 获取酒店列表
    fetchHotelList() {
      getHotelList().then(response => {
        if (response.code === 1000) {
          this.hotelOptions = response.data.records || []
        }
      })
    },
    // 获取楼宇列表
    fetchBuildingList(hotelId) {
      getBuildingList({ hotelId }).then(response => {
        if (response.code === 1000) {
          this.buildingOptions = response.data.records || []
        }
      })
    },
    // 获取楼层列表
    fetchFloorList(buildingId) {
      getFloorList({ buildingId }).then(response => {
        if (response.code === 1000) {
          this.floorOptions = response.data.records || []
        }
      })
    },
    // 获取房间列表
    fetchRoomList() {
      const params = { ...this.searchForm }
      getRoomList(params).then(response => {
        if (response.code === 1000) {
          this.roomList = response.data.records || []
        }
      })
    },
    // 获取会员列表
    fetchMemberList() {
      getMemberList().then(response => {
        if (response.code === 1000) {
          this.memberOptions = response.data.records || []
        }
      })
    },
    // 酒店选择变化
    handleHotelChange(val) {
      this.searchForm.buildingId = ''
      this.searchForm.floorId = ''
      this.buildingOptions = []
      this.floorOptions = []
      if (val) {
        this.fetchBuildingList(val)
        // 选择酒店后自动触发房间查询
        this.fetchRoomList()
      }
    },
    // 楼宇选择变化
    handleBuildingChange(val) {
      this.searchForm.floorId = ''
      this.floorOptions = []
      if (val) {
        this.fetchFloorList(val)
        // 选择楼宇后自动触发房间查询
        this.fetchRoomList()
      }
    },
    // 搜索
    handleSearch() {
      this.fetchRoomList()
    },
    // 重置搜索
    resetSearch() {
      this.searchForm = {
        hotelId: '',
        buildingId: '',
        floorId: ''
      }
      this.buildingOptions = []
      this.floorOptions = []
      this.roomList = []
    },
    // 获取房间状态样式类
    getRoomStatusClass(status) {
      // 将字符串类型的状态转换为数字
      const statusNum = parseInt(status)
      switch (statusNum) {
        case 1: return 'room-free'
        case 2: return 'room-occupied'
        case 3: return 'room-maintenance'
        default: return ''
      }
    },
    // 获取房间状态文本
    getRoomStatusText(status) {
      // 将字符串类型的状态转换为数字
      const statusNum = parseInt(status)
      switch (statusNum) {
        case 1: return '空闲'
        case 2: return '入住中'
        case 3: return '维修中'
        default: return '未知'
      }
    },
    // 处理登记
    handleRegister(room) {
      this.registerForm = {
        registrationType: 1,
        memberId: null,
        guestName: '',
        guestPhone: '',
        roomId: room.id,
        checkInTime: parseTime(new Date(), '{y}-{m}-{d} {h}:{i}:{s}'),
        checkOutTime: '',
        idCard: '',
        companions: [],
        remarks: '',
        needBreakfast: 0
      }
      this.fetchMemberList()
      this.registerDialogVisible = true
    },
    // 处理续期
    handleRenew(room) {
      console.log('续期按钮被点击', room)
      this.currentRoom = room
      // 获取当前房间的登记信息
      getCurrentRegistration(room.id).then(response => {
        if (response.code === 1000) {
          const registration = response.data
          if (registration) {
            // 填充续期表单
            this.renewForm = {
              id: registration.id,
              guestName: registration.guestName,
              guestPhone: registration.guestPhone,
              roomId: registration.roomId,
              checkInTime: registration.checkInTime,
              checkOutTime: new Date(registration.checkOutTime),
              orderAmount: 0,
              paymentAmount: 0,
              discountAmount: 0
            }
            // 显示续期弹窗
            this.renewDialogVisible = true
          } else {
            this.$message.warning('该房间没有有效的登记信息')
          }
        } else {
          this.$message.error(response.msg || '获取登记信息失败')
        }
      }).catch(err => {
        console.error('获取登记信息失败', err)
        this.$message.error('获取登记信息失败')
      })
    },
    
    // 提交续期表单
    submitRenew() {
      this.$refs.renewForm.validate(valid => {
        if (valid) {
          // 检查续期时间是否大于当前时间
          const now = new Date()
          if (new Date(this.renewForm.checkOutTime) <= now) {
            this.$message.warning('续期时间必须大于当前时间')
            return
          }
          
          // 构建续期请求参数
          const renewRequest = {
            id: this.renewForm.id,
            roomId: this.currentRoom.id,
            checkOutTime: parseTime(this.renewForm.checkOutTime, '{y}-{m}-{d} {h}:{i}:{s}'),
            orderAmount: this.renewForm.orderAmount,
            paymentAmount: this.renewForm.paymentAmount,
            discountAmount: this.renewForm.discountAmount
          }
          
          // 调用续期API
          renewRegistration(renewRequest).then(response => {
            if (response.code === 1000) {
              this.$message.success('房间续期成功')
              this.renewDialogVisible = false
              // 刷新房间列表
                this.fetchRoomList()
            } else {
              this.$message.error(response.msg || '房间续期失败')
            }
          }).catch(err => {
            console.error('房间续期失败', err)
            this.$message.error('房间续期失败')
          })
        } else {
          return false
        }
      })
    },
    // 处理退房
    handleCheckout(room) {
      // 显示确认对话框
      this.$confirm(`确定要为${room.roomNumber}房间办理退房吗?`, '退房确认', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        // 用户确认退房,调用退房接口
        checkoutRoom(room.id).then(response => {
          if (response.code === 1000) {
            this.$message.success('退房成功')
            // 刷新房间列表
                this.fetchRoomList()
          } else {
            this.$message.error(response.msg || '退房失败')
          }
        }).catch(err => {
          console.error('退房失败', err)
          this.$message.error('退房失败')
        })
      }).catch(() => {
        // 用户取消退房
        this.$message.info('已取消退房操作')
      })
    },
    // 会员选择变化
    handleMemberChange(memberId) {
      if (memberId) {
        const member = this.memberOptions.find(item => item.id === memberId)
        if (member) {
          this.registerForm.guestName = member.memberName
          this.registerForm.guestPhone = member.contact
        }
      }
    },
    // 添加随行人员
    addCompanion() {
      this.registerForm.companions.push({ name: '', idCard: '' })
    },
    // 移除随行人员
    removeCompanion(index) {
      this.registerForm.companions.splice(index, 1)
    },
    // 提交登记
    submitRegister() {
      this.$refs.registerForm.validate(valid => {
        if (valid) {
          // 处理随行人员数据
          const companions = this.registerForm.companions.filter(item => item.name && item.idCard)
          const params = {
            ...this.registerForm,
            companions: JSON.stringify(companions)
          }

          params.checkOutTime=parseTime(params.checkOutTime, '{y}-{m}-{d} {h}:{i}:{s}'),
          
          registerRoom(params).then(response => {
            if (response.code === 1000) {
              this.$message.success('登记成功')
              this.registerDialogVisible = false
              this.fetchRoomList() // 刷新房间列表
            } else {
              this.$message.error(response.msg || '登记失败')
            }
          })
        }
      })
    }
  }
}
</script>

<style scoped>
.checkin-container {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.search-container {
  padding: 15px;
  background-color: #fff;
  border-radius: 4px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
  margin-bottom: 15px;
}

.room-container {
  flex: 1;
  background-color: #fff;
  border-radius: 4px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
  padding: 15px;
  overflow-y: auto;
}

.room-list {
  display: flex;
  flex-wrap: wrap;
  gap: 15px;
}

.room-item {
  width: 220px;
  height: 220px;
  border-radius: 8px;
  padding: 15px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  transition: all 0.3s;
  position: relative;
  overflow: hidden;
}

.room-item:hover {
  transform: translateY(-5px);
  box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}

.room-free {
  background-color: #f0f9eb;
  border: 1px solid #e1f3d8;
}

.room-occupied {
  background-color: #fef0f0;
  border: 1px solid #fde2e2;
}

.room-maintenance {
  background-color: #f4f4f5;
  border: 1px solid #e9e9eb;
}

.room-icon {
  text-align: center;
  margin-bottom: 10px;
}

.room-icon i {
  font-size: 28px;
  color: #409EFF;
}

.room-info {
  text-align: center;
}

.room-number {
  font-size: 18px;
  font-weight: bold;
  margin-bottom: 5px;
}

.room-name {
  font-size: 14px;
  color: #606266;
  margin-bottom: 5px;
}

.room-status {
  display: inline-block;
  padding: 2px 8px;
  font-size: 12px;
  border-radius: 10px;
  background-color: #f0f0f0;
  margin-bottom: 8px;
}

.room-features {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-top: 5px;
  font-size: 12px;
}

.feature-item {
  display: flex;
  align-items: center;
  color: #606266;
}

.feature-item i {
  margin-right: 3px;
  color: #409EFF;
}

.room-free .room-status {
  background-color: #67c23a;
  color: #fff;
}

.room-occupied .room-status {
  background-color: #f56c6c;
  color: #fff;
}

.room-maintenance .room-status {
  background-color: #909399;
  color: #fff;
}

.room-actions {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-top: 10px;
}

.no-data {
  width: 100%;
  height: 200px;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #909399;
}

.companion-item {
  display: flex;
  margin-bottom: 10px;
  align-items: center;
}

.companion-input {
  margin-right: 10px;
}
</style>

如果你是刚开始学习 Java,可以从零基础开始尝试搭建一个系统。你也可以参考这个系统,并结合自己的想法,开发出一个更完善的管理系统。希望对你有所帮助。

为了更好的帮助到学习编程,但是没有想法的小伙伴,我把我写的这个项目搭建了一个预览地址,方便大家预览参考~
https://test.wwwoop.com/?s=jiu-dian-guan-li&no=Hotel-001&rand=0.4133917792569839

相关推荐
Victor3563 小时前
Redis(74)Redis分布式锁与ZooKeeper分布式锁有何区别?
后端
风象南3 小时前
SpringBoot “分身术”:同时监听多个端口
后端
Victor3564 小时前
Redis(75)Redis分布式锁的性能如何优化?
后端
JaguarJack4 小时前
PHP 8.5 新特性 闭包可以作为常量表达式了
后端·php
毕业设计制作和分享7 小时前
springboot150基于springboot的贸易行业crm系统
java·vue.js·spring boot·后端·毕业设计·mybatis
编啊编程啊程8 小时前
【011】宠物共享平台
spring boot·log4j·maven·dubbo·宠物
你的人类朋友11 小时前
【Node】认识multer库
前端·javascript·后端
deng-c-f12 小时前
Linux C/C++ 学习日记(28):KCP协议(四):如何实现更复杂的业务:将连接状态的管理进行封装,用户只需实现发送、接收、断开的处理逻辑。
学习·网络编程·kcp
lang2015092812 小时前
Spring Boot 官方文档精解:构建与依赖管理
java·spring boot·后端