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

Java教程 2025-10-22

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

java的版本是:21

springboot版本是:2.7

数据库操作:mybatis-plus

前端使用的是 vue2 + element-ui

mysql:8

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

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

首页:

image.png

后端代码:

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);
    }
}

前端代码:

<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">
        
        <div class="chart-item">
          <div class="chart-title">入住登记月度统计(近6个月)div>
          <div ref="checkInMonthlyChart" class="chart">div>
        div>
        
        
        <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} 
{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}
{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>

登记入住页面效果:

image.png

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);
    }
}

前端代码:

<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,可以从零基础开始尝试搭建一个系统。你也可以参考这个系统,并结合自己的想法,开发出一个更完善的管理系统。希望对你有所帮助

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