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

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

开发教程 2025年12月4日
836 浏览

想快速掌握一个框架,就是要不停的写项目,看别人的项目,让自己学习到的编程知识学以致用。今天给大家分享最近使用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…

发表评论
暂无评论

还没有评论呢,快来抢沙发~

客服

点击联系客服 点击联系客服

在线时间:09:00-18:00

关注微信公众号

关注微信公众号
客服电话

400-888-8888

客服邮箱 122325244@qq.com

手机

扫描二维码

手机访问本站

扫描二维码
搜索