You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1283 lines
62 KiB
1283 lines
62 KiB
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>🏨 酒店管理系统</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
|
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
|
<style>
|
|
body { background-color: #f8f9fa; }
|
|
.sidebar { background: linear-gradient(180deg, #2c3e50 0%, #34495e 100%); min-height: 100vh; color: white; padding: 20px; }
|
|
.sidebar h3 { margin-bottom: 30px; font-weight: bold; border-bottom: 2px solid #e74c3c; padding-bottom: 10px; }
|
|
.nav-section { margin-bottom: 25px; }
|
|
.nav-section-title { font-size: 12px; color: #95a5a6; font-weight: bold; margin-bottom: 10px; text-transform: uppercase; }
|
|
.sidebar .nav-link { color: #ecf0f1; border-radius: 5px; margin-bottom: 8px; padding: 10px 15px; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; }
|
|
.sidebar .nav-link:hover { background-color: #e74c3c; transform: translateX(5px); }
|
|
.sidebar .nav-link.active { background-color: #e74c3c; color: white; font-weight: bold; }
|
|
.sidebar .nav-link i { margin-right: 10px; }
|
|
.main-content { padding: 30px; overflow-y: auto; }
|
|
.page-section { display: none; }
|
|
.page-section.active { display: block; animation: fadeIn 0.3s; }
|
|
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
.card { border: none; box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin-bottom: 20px; border-radius: 8px; }
|
|
.card-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; font-weight: bold; }
|
|
.table-hover tbody tr:hover { background-color: #f5f5f5; cursor: pointer; }
|
|
.header-title { color: #2c3e50; border-bottom: 3px solid #e74c3c; padding-bottom: 15px; margin-bottom: 25px; }
|
|
.stat-card { padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 10px; margin-bottom: 15px; text-align: center; }
|
|
.stat-card h5 { margin-top: 10px; font-size: 14px; }
|
|
.stat-number { font-size: 32px; font-weight: bold; }
|
|
.status-badge { padding: 5px 10px; border-radius: 20px; font-size: 12px; font-weight: bold; }
|
|
.status-available { background-color: #2ecc71; color: white; }
|
|
.status-occupied { background-color: #e74c3c; color: white; }
|
|
.status-cleaning { background-color: #f39c12; color: white; }
|
|
.status-maintenance { background-color: #9b59b6; color: white; }
|
|
.btn-group-vertical { width: 100%; }
|
|
.action-buttons { display: flex; gap: 5px; }
|
|
.action-buttons .btn { flex: 1; padding: 5px 10px; font-size: 12px; }
|
|
.modal-lg { max-width: 600px; }
|
|
.form-section { margin-bottom: 20px; padding: 15px; background-color: #f8f9fa; border-radius: 5px; }
|
|
.form-section h5 { color: #2c3e50; margin-bottom: 15px; font-weight: bold; border-bottom: 2px solid #e74c3c; padding-bottom: 10px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app" class="d-flex">
|
|
<div class="sidebar" style="width: 280px;">
|
|
<h3>🏨 酒店管理系统</h3>
|
|
|
|
<div class="nav-section">
|
|
<div class="nav-section-title">核心业务</div>
|
|
<a @click="switchPage('checkin')" :class="['nav-link', {active: currentPage === 'checkin'}]">🚪 入住登记</a>
|
|
<a @click="switchPage('checkout')" :class="['nav-link', {active: currentPage === 'checkout'}]">🔑 退房管理</a>
|
|
<a @click="switchPage('rooms')" :class="['nav-link', {active: currentPage === 'rooms'}]">🛏️ 房间状态</a>
|
|
<a @click="switchPage('services')" :class="['nav-link', {active: currentPage === 'services'}]">🔧 服务请求</a>
|
|
</div>
|
|
|
|
<div class="nav-section">
|
|
<div class="nav-section-title">管理模块</div>
|
|
<a @click="switchPage('reservations')" :class="['nav-link', {active: currentPage === 'reservations'}]">📅 预订管理</a>
|
|
<a @click="switchPage('guests')" :class="['nav-link', {active: currentPage === 'guests'}]">👥 宾客信息</a>
|
|
<a @click="switchPage('billing')" :class="['nav-link', {active: currentPage === 'billing'}]">💰 账单结算</a>
|
|
</div>
|
|
|
|
<div class="nav-section">
|
|
<div class="nav-section-title">系统管理</div>
|
|
<a @click="switchPage('employees')" :class="['nav-link', {active: currentPage === 'employees'}]">👔 员工管理</a>
|
|
<a @click="switchPage('dashboard')" :class="['nav-link', {active: currentPage === 'dashboard'}]">📊 数据统计</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="main-content" style="flex: 1;">
|
|
<!-- 仪表板 -->
|
|
<div id="dashboard" class="page-section active">
|
|
<h2 class="header-title">📊 今日数据统计</h2>
|
|
<div class="row mb-4">
|
|
<div class="col-md-3">
|
|
<div class="stat-card">
|
|
<div class="stat-number">{{ stats.occupancy }}</div>
|
|
<h5>入住房间</h5>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="stat-card">
|
|
<div class="stat-number">{{ stats.available }}</div>
|
|
<h5>空房间数</h5>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="stat-card">
|
|
<div class="stat-number">{{ stats.revenue }}</div>
|
|
<h5>今日收入(元)</h5>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="stat-card">
|
|
<div class="stat-number">{{ stats.checkins }}</div>
|
|
<h5>今日入住</h5>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button @click="loadAllData" class="btn btn-primary">刷新统计</button>
|
|
</div>
|
|
|
|
<!-- 入住登记 -->
|
|
<div id="checkin" class="page-section">
|
|
<h2 class="header-title">🚪 入住登记</h2>
|
|
<div class="mb-3">
|
|
<button @click="showCheckinModal" class="btn btn-success btn-lg">+ 新增入住</button>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-header">今日入住列表</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>宾客名称</th>
|
|
<th>房间号</th>
|
|
<th>入住时间</th>
|
|
<th>预计退房</th>
|
|
<th>房价(元/晚)</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="g in checkins" :key="g.order_id" v-if="checkins.length > 0">
|
|
<td><strong>{{ g.name }}</strong></td>
|
|
<td><span class="badge bg-info">{{ g.room }}</span></td>
|
|
<td>{{ formatDate(g.checkin) }}</td>
|
|
<td>{{ formatDate(g.checkout) }}</td>
|
|
<td>{{ g.price }}</td>
|
|
<td>
|
|
<button @click="viewGuestDetails(g)" class="btn btn-sm btn-info">详情</button>
|
|
</td>
|
|
</tr>
|
|
<tr v-else>
|
|
<td colspan="6" class="text-center text-muted py-4">暂无入住宾客</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 退房管理 -->
|
|
<div id="checkout" class="page-section">
|
|
<h2 class="header-title">🔑 退房管理</h2>
|
|
<div class="card">
|
|
<div class="card-header">待退房宾客</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>宾客名称</th>
|
|
<th>房间号</th>
|
|
<th>入住天数</th>
|
|
<th>应收费用(元)</th>
|
|
<th>状态</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="g in checkouts" :key="g.order_id" v-if="checkouts.length > 0">
|
|
<td><strong>{{ g.name }}</strong></td>
|
|
<td><span class="badge bg-warning">{{ g.room }}</span></td>
|
|
<td>{{ g.nights }}晚</td>
|
|
<td class="fw-bold">{{ g.total }}</td>
|
|
<td><span class="badge bg-primary">待结算</span></td>
|
|
<td>
|
|
<div class="action-buttons">
|
|
<button @click="showCheckoutModal(g)" class="btn btn-sm btn-success">结账</button>
|
|
<button @click="markRoomCleaning(g.room)" class="btn btn-sm btn-warning">标记清洁</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<tr v-else>
|
|
<td colspan="6" class="text-center text-muted py-4">暂无待退房宾客</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 房间状态 -->
|
|
<div id="rooms" class="page-section">
|
|
<h2 class="header-title">🛏️ 房间管理</h2>
|
|
<div class="mb-3">
|
|
<button @click="filterRooms('available')" class="btn btn-outline-success">可用</button>
|
|
<button @click="filterRooms('occupied')" class="btn btn-outline-danger">已占用</button>
|
|
<button @click="filterRooms('cleaning')" class="btn btn-outline-warning">清洁中</button>
|
|
<button @click="filterRooms('maintenance')" class="btn btn-outline-secondary">维修中</button>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-6" v-for="r in filteredRooms" :key="r.room_id">
|
|
<div class="card" :style="getRoomCardStyle(r.status)">
|
|
<div class="card-body">
|
|
<h5 class="card-title">房间 {{ r.room_id }} <span :class="['status-badge', 'status-' + r.status]">{{ getStatusText(r.status) }}</span></h5>
|
|
<p class="card-text">
|
|
<small class="text-muted">
|
|
楼层: {{ r.floor }} | 朝向: {{ r.orientation }}<br>
|
|
面积: {{ r.usable_area }}㎡
|
|
</small>
|
|
</p>
|
|
<div class="action-buttons">
|
|
<button @click="updateRoomStatus(r, 'available')" class="btn btn-sm btn-success">设为可用</button>
|
|
<button @click="updateRoomStatus(r, 'cleaning')" class="btn btn-sm btn-warning">清洁</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 服务请求 -->
|
|
<div id="services" class="page-section">
|
|
<h2 class="header-title">🔧 服务请求</h2>
|
|
<div class="mb-3">
|
|
<button @click="showServiceModal" class="btn btn-success btn-lg">+ 新增请求</button>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-header">服务工单</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>房间号</th>
|
|
<th>服务类型</th>
|
|
<th>描述</th>
|
|
<th>优先级</th>
|
|
<th>状态</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="s in services" :key="s.id" v-if="services.length > 0">
|
|
<td><span class="badge bg-info">{{ s.room_id }}</span></td>
|
|
<td>{{ s.type }}</td>
|
|
<td>{{ s.description }}</td>
|
|
<td><span class="badge" :class="'bg-' + getPriorityColor(s.priority)">{{ s.priority }}</span></td>
|
|
<td><span class="badge bg-primary">{{ s.status }}</span></td>
|
|
<td>
|
|
<button @click="completeService(s.id)" class="btn btn-sm btn-success">完成</button>
|
|
</td>
|
|
</tr>
|
|
<tr v-else>
|
|
<td colspan="6" class="text-center text-muted py-4">暂无服务请求</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 预订管理 -->
|
|
<div id="reservations" class="page-section">
|
|
<h2 class="header-title">📅 预订管理</h2>
|
|
<div class="mb-3">
|
|
<button @click="showReservationModal" class="btn btn-success btn-lg">+ 新增预订</button>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-header">预订列表</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>预订号</th>
|
|
<th>宾客名称</th>
|
|
<th>入住日期</th>
|
|
<th>退房日期</th>
|
|
<th>房型</th>
|
|
<th>状态</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="o in reservations" :key="o.order_id" v-if="reservations.length > 0">
|
|
<td class="fw-bold">{{ o.order_id }}</td>
|
|
<td>{{ o.guest_name }}</td>
|
|
<td>{{ formatDate(o.checkin_date) }}</td>
|
|
<td>{{ formatDate(o.checkout_date) }}</td>
|
|
<td>{{ o.room_type }}</td>
|
|
<td><span class="badge bg-warning">{{ o.status }}</span></td>
|
|
<td>
|
|
<button @click="confirmReservation(o.order_id)" class="btn btn-sm btn-success">确认</button>
|
|
<button @click="cancelReservation(o.order_id)" class="btn btn-sm btn-danger">取消</button>
|
|
</td>
|
|
</tr>
|
|
<tr v-else>
|
|
<td colspan="7" class="text-center text-muted py-4">暂无预订</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 宾客信息 -->
|
|
<div id="guests" class="page-section">
|
|
<h2 class="header-title">👥 宾客信息库</h2>
|
|
<div class="mb-3">
|
|
<button @click="showGuestModal" class="btn btn-success">+ 新增宾客</button>
|
|
<input v-model="searchGuest" type="text" placeholder="搜索宾客名称或电话..." class="form-control d-inline-block w-auto">
|
|
</div>
|
|
<div class="card">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>宾客ID</th>
|
|
<th>姓名</th>
|
|
<th>联系电话</th>
|
|
<th>身份证</th>
|
|
<th>入住次数</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="c in filteredGuests" :key="c.customer_id" v-if="guests.length > 0">
|
|
<td>{{ c.customer_id }}</td>
|
|
<td>{{ c.name }}</td>
|
|
<td>{{ c.phone }}</td>
|
|
<td>{{ c.id_card }}</td>
|
|
<td><span class="badge bg-info">{{ c.stay_count || 0 }}</span></td>
|
|
<td>
|
|
<button @click="editGuest(c)" class="btn btn-sm btn-warning me-2">编辑</button>
|
|
<button @click="deleteGuest(c.customer_id)" class="btn btn-sm btn-danger">删除</button>
|
|
</td>
|
|
</tr>
|
|
<tr v-else>
|
|
<td colspan="6" class="text-center text-muted py-4">暂无宾客信息</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 账单结算 -->
|
|
<div id="billing" class="page-section">
|
|
<h2 class="header-title">💰 账单结算</h2>
|
|
<div class="card">
|
|
<div class="card-header">待结账单</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>账单号</th>
|
|
<th>宾客名称</th>
|
|
<th>房价</th>
|
|
<th>额外费用</th>
|
|
<th>总金额(元)</th>
|
|
<th>支付方式</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="b in bills" :key="b.bill_id" v-if="bills.length > 0">
|
|
<td class="fw-bold">{{ b.bill_id }}</td>
|
|
<td>{{ b.guest_name }}</td>
|
|
<td>{{ b.room_fee }}</td>
|
|
<td>{{ b.additional_fee }}</td>
|
|
<td class="fw-bold text-success">{{ b.total_amount }}</td>
|
|
<td>
|
|
<select v-model="b.payment_method" class="form-select form-select-sm">
|
|
<option value="cash">现金</option>
|
|
<option value="card">卡支付</option>
|
|
<option value="online">在线支付</option>
|
|
</select>
|
|
</td>
|
|
<td>
|
|
<button @click="confirmPayment(b.bill_id)" class="btn btn-sm btn-success">确认支付</button>
|
|
</td>
|
|
</tr>
|
|
<tr v-else>
|
|
<td colspan="7" class="text-center text-muted py-4">暂无待结账单</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 员工管理 -->
|
|
<div id="employees" class="page-section">
|
|
<h2 class="header-title">👔 员工管理</h2>
|
|
<div class="mb-3">
|
|
<button @click="showEmployeeModal" class="btn btn-success">+ 新增员工</button>
|
|
</div>
|
|
<div class="card">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>员工ID</th>
|
|
<th>姓名</th>
|
|
<th>职位</th>
|
|
<th>权限</th>
|
|
<th>电话</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="e in employees" :key="e.employee_id" v-if="employees.length > 0">
|
|
<td>{{ e.employee_id }}</td>
|
|
<td>{{ e.name }}</td>
|
|
<td><span class="badge bg-secondary">{{ e.role }}</span></td>
|
|
<td><small>{{ e.permission }}</small></td>
|
|
<td>{{ e.phone }}</td>
|
|
<td>
|
|
<button @click="editEmployee(e)" class="btn btn-sm btn-warning me-2">编辑</button>
|
|
<button @click="deleteEmployee(e.employee_id)" class="btn btn-sm btn-danger">删除</button>
|
|
</td>
|
|
</tr>
|
|
<tr v-else>
|
|
<td colspan="6" class="text-center text-muted py-4">暂无员工</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal fade" id="checkinModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-success text-white">
|
|
<h5 class="modal-title">入住登记</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form @submit.prevent="processCheckin">
|
|
<div class="form-section">
|
|
<h5>宾客信息</h5>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<label class="form-label">宾客名称 *</label>
|
|
<input v-model="checkinForm.name" class="form-control" required>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">联系电话 *</label>
|
|
<input v-model="checkinForm.phone" class="form-control" required>
|
|
</div>
|
|
</div>
|
|
<div class="row mt-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label">身份证号 *</label>
|
|
<input v-model="checkinForm.id_card" class="form-control" required>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">性别</label>
|
|
<select v-model="checkinForm.gender" class="form-select">
|
|
<option value="M">男</option>
|
|
<option value="F">女</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="form-section">
|
|
<h5>房间分配</h5>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<label class="form-label">选择房间 *</label>
|
|
<select v-model="checkinForm.room_id" class="form-select" required>
|
|
<option value="">-- 可用房间 --</option>
|
|
<option v-for="r in availableRooms" :key="r.room_id" :value="r.room_id">
|
|
房间 {{ r.room_id }} ({{ r.orientation }})
|
|
</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">入住天数 *</label>
|
|
<input v-model.number="checkinForm.nights" type="number" class="form-control" min="1" required>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="form-section">
|
|
<h5>住宿信息</h5>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<label class="form-label">入住时间 *</label>
|
|
<input v-model="checkinForm.checkin_date" type="datetime-local" class="form-control" required>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">每晚房价(元) *</label>
|
|
<input v-model.number="checkinForm.price" type="number" class="form-control" required>
|
|
</div>
|
|
</div>
|
|
<div class="mt-2">
|
|
<small class="text-muted">预计退房: {{ calculateCheckout }}</small>
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-success btn-lg w-100">确认入住</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal fade" id="checkoutModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-warning text-dark">
|
|
<h5 class="modal-title">结账退房</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form @submit.prevent="processCheckout">
|
|
<div class="form-section">
|
|
<h5>结账信息</h5>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<label class="form-label">宾客名称</label>
|
|
<input v-model="checkoutForm.name" class="form-control" readonly>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">房间号</label>
|
|
<input v-model="checkoutForm.room_id" class="form-control" readonly>
|
|
</div>
|
|
</div>
|
|
<div class="row mt-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label">入住天数</label>
|
|
<input v-model="checkoutForm.nights" class="form-control" readonly>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">房价/晚(元)</label>
|
|
<input v-model="checkoutForm.price" class="form-control" readonly>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="form-section">
|
|
<h5>费用明细</h5>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<label class="form-label">房费(元)</label>
|
|
<input v-model.number="checkoutForm.room_fee" type="number" class="form-control">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">额外费用(元)</label>
|
|
<input v-model.number="checkoutForm.additional_fee" type="number" class="form-control" placeholder="服务费、餐饮等">
|
|
</div>
|
|
</div>
|
|
<div class="mt-3">
|
|
<h6>总计: <span class="text-success fw-bold">¥{{ totalCheckoutAmount }}</span></h6>
|
|
</div>
|
|
</div>
|
|
<div class="form-section">
|
|
<h5>支付方式</h5>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<label class="form-label">选择支付方式 *</label>
|
|
<select v-model="checkoutForm.payment_method" class="form-select" required>
|
|
<option value="">-- 请选择 --</option>
|
|
<option value="cash">现金</option>
|
|
<option value="card">银行卡</option>
|
|
<option value="online">在线支付</option>
|
|
<option value="transfer">转账</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">备注</label>
|
|
<input v-model="checkoutForm.remarks" class="form-control" placeholder="特殊说明等">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-warning btn-lg w-100">确认结账退房</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal fade" id="serviceModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-info text-white">
|
|
<h5 class="modal-title">新增服务请求</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form @submit.prevent="submitService">
|
|
<div class="mb-3">
|
|
<label class="form-label">房间号 *</label>
|
|
<select v-model.number="serviceForm.room_id" class="form-select" required>
|
|
<option value="">-- 请选择房间 --</option>
|
|
<option v-for="r in occupiedRooms" :key="r.room_id" :value="r.room_id">房间 {{ r.room_id }}</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">服务类型 *</label>
|
|
<select v-model="serviceForm.type" class="form-select" required>
|
|
<option value="">-- 请选择 --</option>
|
|
<option value="客房服务">客房服务(送餐、毛巾等)</option>
|
|
<option value="设施维修">设施维修(空调、电视等)</option>
|
|
<option value="紧急救助">紧急救助</option>
|
|
<option value="清洁服务">清洁服务(加洁具、打扫等)</option>
|
|
<option value="投诉处理">投诉处理</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">描述 *</label>
|
|
<textarea v-model="serviceForm.description" class="form-control" rows="3" placeholder="详细说明" required></textarea>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">优先级 *</label>
|
|
<select v-model="serviceForm.priority" class="form-select" required>
|
|
<option value="低">低 - 可延后处理</option>
|
|
<option value="中">中 - 正常处理</option>
|
|
<option value="高">高 - 立即处理</option>
|
|
<option value="紧急">紧急 - 马上处理</option>
|
|
</select>
|
|
</div>
|
|
<button type="submit" class="btn btn-info btn-lg w-100">提交请求</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal fade" id="guestModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">{{ editingGuest ? '编辑宾客' : '添加宾客' }}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form @submit.prevent="saveGuest">
|
|
<div class="mb-3">
|
|
<label class="form-label">姓名 *</label>
|
|
<input v-model="guestForm.name" class="form-control" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">电话 *</label>
|
|
<input v-model="guestForm.phone" class="form-control" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">身份证 *</label>
|
|
<input v-model="guestForm.id_card" class="form-control" required>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary w-100">保存</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal fade" id="reservationModal" tabindex="-1">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-primary text-white">
|
|
<h5 class="modal-title">新增预订</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form @submit.prevent="saveReservation">
|
|
<div class="form-section">
|
|
<h5>宾客信息</h5>
|
|
<div class="mb-3">
|
|
<label class="form-label">选择宾客或新增 *</label>
|
|
<input v-model="reservationForm.guest_name" class="form-control" placeholder="输入宾客名称" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">联系电话 *</label>
|
|
<input v-model="reservationForm.phone" class="form-control" required>
|
|
</div>
|
|
</div>
|
|
<div class="form-section">
|
|
<h5>预订信息</h5>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<label class="form-label">入住日期 *</label>
|
|
<input v-model="reservationForm.checkin_date" type="date" class="form-control" required>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">退房日期 *</label>
|
|
<input v-model="reservationForm.checkout_date" type="date" class="form-control" required>
|
|
</div>
|
|
</div>
|
|
<div class="row mt-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label">房型 *</label>
|
|
<select v-model="reservationForm.room_type" class="form-select" required>
|
|
<option value="">-- 请选择 --</option>
|
|
<option value="标间">标间</option>
|
|
<option value="单间">单间</option>
|
|
<option value="套房">套房</option>
|
|
<option value="豪华房">豪华房</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">预订渠道 *</label>
|
|
<select v-model="reservationForm.channel" class="form-select" required>
|
|
<option value="">-- 请选择 --</option>
|
|
<option value="电话预约">电话预约</option>
|
|
<option value="直接预约">直接预约</option>
|
|
<option value="OTA平台">OTA平台</option>
|
|
<option value="网络预约">网络预约</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary btn-lg w-100">创建预订</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal fade" id="employeeModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">{{ editingEmployee ? '编辑员工' : '新增员工' }}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form @submit.prevent="saveEmployee">
|
|
<div class="mb-3">
|
|
<label class="form-label">姓名 *</label>
|
|
<input v-model="employeeForm.name" class="form-control" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">职位 *</label>
|
|
<select v-model="employeeForm.role" class="form-select" required>
|
|
<option value="">-- 请选择 --</option>
|
|
<option value="前台员工">前台员工</option>
|
|
<option value="管理">部门管理</option>
|
|
<option value="系统管理员">系统管理员</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">权限 *</label>
|
|
<input v-model="employeeForm.permission" class="form-control" placeholder="逗号分隔, 如: 查看订单,修改房间">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label">电话 *</label>
|
|
<input v-model="employeeForm.phone" class="form-control" required>
|
|
</div>
|
|
<div v-if="!editingEmployee" class="mb-3">
|
|
<label class="form-label">账户 *</label>
|
|
<input v-model="employeeForm.account" class="form-control" required>
|
|
</div>
|
|
<div v-if="!editingEmployee" class="mb-3">
|
|
<label class="form-label">初始密码 *</label>
|
|
<input v-model="employeeForm.password" type="password" class="form-control" required>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary w-100">保存</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script>
|
|
const { createApp } = Vue;
|
|
createApp({
|
|
data() {
|
|
return {
|
|
currentPage: 'dashboard',
|
|
checkins: [], checkouts: [], reservations: [], services: [], bills: [], guests: [], rooms: [], employees: [],
|
|
stats: { occupancy: 0, totalRevenue: 0, todayCheckins: 0, pendingServices: 0 },
|
|
|
|
// 表单数据
|
|
checkinForm: { name: '', phone: '', id_card: '', gender: 'M', room_id: '', nights: 1, checkin_date: '', price: '' },
|
|
checkoutForm: { name: '', room_id: '', nights: 1, price: 0, room_fee: 0, additional_fee: 0, payment_method: '', remarks: '' },
|
|
serviceForm: { room_id: '', type: '', description: '', priority: '中' },
|
|
guestForm: { name: '', phone: '', id_card: '' },
|
|
reservationForm: { guest_name: '', phone: '', checkin_date: '', checkout_date: '', room_type: '', channel: '' },
|
|
employeeForm: { name: '', role: '', permission: '', phone: '', account: '', password: '' },
|
|
|
|
editingGuest: null,
|
|
editingEmployee: null,
|
|
searchGuest: '',
|
|
roomStatusFilter: 'all'
|
|
};
|
|
},
|
|
computed: {
|
|
availableRooms() {
|
|
return this.rooms.filter(r => r.status === '空闲');
|
|
},
|
|
occupiedRooms() {
|
|
return this.rooms.filter(r => r.status === '已占用');
|
|
},
|
|
calculateCheckout() {
|
|
if (!this.checkinForm.checkin_date || !this.checkinForm.nights) return '';
|
|
const checkIn = new Date(this.checkinForm.checkin_date);
|
|
const checkOut = new Date(checkIn.getTime() + this.checkinForm.nights * 24 * 60 * 60 * 1000);
|
|
return checkOut.toLocaleString('zh-CN');
|
|
},
|
|
totalCheckoutAmount() {
|
|
return (this.checkoutForm.room_fee || 0) + (this.checkoutForm.additional_fee || 0);
|
|
},
|
|
filteredGuests() {
|
|
if (!this.searchGuest) return this.guests;
|
|
const search = this.searchGuest.toLowerCase();
|
|
return this.guests.filter(g =>
|
|
(g.name && g.name.toLowerCase().includes(search)) ||
|
|
(g.phone && g.phone.includes(search))
|
|
);
|
|
},
|
|
filteredRooms() {
|
|
if (this.roomStatusFilter === 'all') return this.rooms;
|
|
const statusMap = {
|
|
'available': '空闲',
|
|
'occupied': '已占用',
|
|
'cleaning': '清洁中',
|
|
'maintenance': '维修中'
|
|
};
|
|
return this.rooms.filter(r => r.status === statusMap[this.roomStatusFilter]);
|
|
}
|
|
},
|
|
methods: {
|
|
switchPage(page) {
|
|
this.currentPage = page;
|
|
document.querySelectorAll('.page-section').forEach(el => el.classList.remove('active'));
|
|
const elem = document.getElementById(page);
|
|
if (elem) elem.classList.add('active');
|
|
},
|
|
|
|
// 数据加载方法
|
|
async loadGuests() {
|
|
try {
|
|
const res = await axios.get('http://127.0.0.1:8000/api/guests');
|
|
this.guests = res.data;
|
|
} catch (err) { console.error('加载宾客失败:', err.message); }
|
|
},
|
|
async loadRooms() {
|
|
try {
|
|
const res = await axios.get('http://127.0.0.1:8000/api/rooms');
|
|
this.rooms = res.data;
|
|
this.calculateStats();
|
|
} catch (err) { console.error('加载房间失败:', err.message); }
|
|
},
|
|
async loadEmployees() {
|
|
try {
|
|
const res = await axios.get('http://127.0.0.1:8000/api/employees');
|
|
this.employees = res.data;
|
|
} catch (err) { console.error('加载员工失败:', err.message); }
|
|
},
|
|
async loadServices() {
|
|
try {
|
|
// 当前服务请求暂时使用模拟数据
|
|
// 未来可以添加专门的服务请求表和 API
|
|
this.services = [
|
|
{ id: 1, room: 101, type: '客房服务', status: '待处理', priority: '中', time: '14:30' },
|
|
{ id: 2, room: 205, type: '设施维修', status: '处理中', priority: '高', time: '14:15' }
|
|
];
|
|
this.stats.pendingServices = this.services.filter(s => s.status === '待处理').length;
|
|
} catch (err) { console.error('加载服务失败:', err.message); }
|
|
},
|
|
async loadReservations() {
|
|
try {
|
|
const res = await axios.get('http://127.0.0.1:8000/api/reservations');
|
|
this.reservations = res.data;
|
|
} catch (err) {
|
|
// 如果后端还没有这个端点,使用本地模拟数据
|
|
this.reservations = [
|
|
{ order_id: 'RES001', guest_name: '张三', phone: '13800138000', checkin_date: '2024-01-15', checkout_date: '2024-01-17', room_type: '标间', status: '已确认' },
|
|
{ order_id: 'RES002', guest_name: '李四', phone: '13900139000', checkin_date: '2024-01-16', checkout_date: '2024-01-18', room_type: '套房', status: '待确认' }
|
|
];
|
|
}
|
|
},
|
|
async loadBills() {
|
|
try {
|
|
const res = await axios.get('http://127.0.0.1:8000/api/bills');
|
|
this.bills = res.data;
|
|
} catch (err) {
|
|
console.error('加载账单失败:', err.message);
|
|
this.bills = [];
|
|
}
|
|
},
|
|
async loadAllData() {
|
|
await Promise.all([
|
|
this.loadGuests(),
|
|
this.loadRooms(),
|
|
this.loadEmployees(),
|
|
this.loadServices(),
|
|
this.loadReservations(),
|
|
this.loadBills()
|
|
]);
|
|
},
|
|
|
|
calculateStats() {
|
|
const occupied = this.rooms.filter(r => r.status === '已占用').length;
|
|
this.stats.occupancy = this.rooms.length > 0 ? Math.round((occupied / this.rooms.length) * 100) : 0;
|
|
this.stats.totalRevenue = Math.random() * 10000 + 5000;
|
|
},
|
|
|
|
getCurrentDateTime() {
|
|
const now = new Date();
|
|
return now.toISOString().slice(0, 16);
|
|
},
|
|
|
|
// 入住管理
|
|
showCheckinForm() {
|
|
this.checkinForm = { name: '', phone: '', id_card: '', gender: 'M', room_id: '', nights: 1, checkin_date: this.getCurrentDateTime(), price: '' };
|
|
new bootstrap.Modal(document.getElementById('checkinModal')).show();
|
|
},
|
|
async processCheckin() {
|
|
if (!this.checkinForm.name || !this.checkinForm.room_id || !this.checkinForm.nights) {
|
|
alert('请完整填写必填字段');
|
|
return;
|
|
}
|
|
try {
|
|
const response = await axios.post('http://127.0.0.1:8000/api/checkin', {
|
|
name: this.checkinForm.name,
|
|
phone: this.checkinForm.phone,
|
|
id_card: this.checkinForm.id_card,
|
|
gender: this.checkinForm.gender,
|
|
room_id: parseInt(this.checkinForm.room_id),
|
|
nights: this.checkinForm.nights,
|
|
checkin_date: this.checkinForm.checkin_date,
|
|
price: this.checkinForm.price
|
|
});
|
|
alert(`宾客 ${this.checkinForm.name} 已成功入住房间 ${this.checkinForm.room_id}`);
|
|
bootstrap.Modal.getInstance(document.getElementById('checkinModal')).hide();
|
|
await this.loadRooms();
|
|
} catch (error) {
|
|
alert('入住失败: ' + (error.response?.data?.detail || error.message));
|
|
}
|
|
},
|
|
|
|
// 退房管理
|
|
showCheckoutForm(checkout) {
|
|
this.checkoutForm = {
|
|
name: checkout?.guest_name || '',
|
|
room_id: checkout?.room_id || '',
|
|
nights: checkout?.nights || 1,
|
|
price: checkout?.price || 0,
|
|
room_fee: checkout?.total_price || 0,
|
|
additional_fee: 0,
|
|
payment_method: '',
|
|
remarks: ''
|
|
};
|
|
new bootstrap.Modal(document.getElementById('checkoutModal')).show();
|
|
},
|
|
async processCheckout() {
|
|
if (!this.checkoutForm.payment_method) {
|
|
alert('请选择支付方式');
|
|
return;
|
|
}
|
|
try {
|
|
const response = await axios.post('http://127.0.0.1:8000/api/checkout', {
|
|
order_id: this.checkoutForm.order_id || 'TEMP' + Date.now(),
|
|
room_id: this.checkoutForm.room_id,
|
|
room_fee: this.checkoutForm.room_fee,
|
|
additional_fee: this.checkoutForm.additional_fee,
|
|
payment_method: this.checkoutForm.payment_method,
|
|
remarks: this.checkoutForm.remarks
|
|
});
|
|
alert(`房间 ${this.checkoutForm.room_id} 已完成结账,总费用: ¥${this.totalCheckoutAmount}`);
|
|
bootstrap.Modal.getInstance(document.getElementById('checkoutModal')).hide();
|
|
await this.loadRooms();
|
|
} catch (error) {
|
|
alert('退房失败: ' + (error.response?.data?.detail || error.message));
|
|
}
|
|
},
|
|
|
|
// 服务请求
|
|
showServiceForm() {
|
|
this.serviceForm = { room_id: '', type: '', description: '', priority: '中' };
|
|
new bootstrap.Modal(document.getElementById('serviceModal')).show();
|
|
},
|
|
async submitService() {
|
|
if (!this.serviceForm.room_id || !this.serviceForm.type || !this.serviceForm.description) {
|
|
alert('请完整填写必填字段');
|
|
return;
|
|
}
|
|
try {
|
|
const response = await axios.post('http://127.0.0.1:8000/api/services', {
|
|
room_id: parseInt(this.serviceForm.room_id),
|
|
service_type: this.serviceForm.type,
|
|
description: this.serviceForm.description,
|
|
priority: this.serviceForm.priority
|
|
});
|
|
alert('服务请求已提交,工作人员将尽快处理');
|
|
bootstrap.Modal.getInstance(document.getElementById('serviceModal')).hide();
|
|
await this.loadServices();
|
|
} catch (error) {
|
|
alert('提交失败: ' + (error.response?.data?.detail || error.message));
|
|
}
|
|
},
|
|
|
|
// 宾客管理
|
|
showGuestForm() {
|
|
this.editingGuest = false;
|
|
this.guestForm = { name: '', phone: '', id_card: '' };
|
|
new bootstrap.Modal(document.getElementById('guestModal')).show();
|
|
},
|
|
async saveGuest() {
|
|
if (!this.guestForm.name || !this.guestForm.phone) {
|
|
alert('请填写完整信息');
|
|
return;
|
|
}
|
|
try {
|
|
const method = this.editingGuest ? 'PUT' : 'POST';
|
|
const url = this.editingGuest ?
|
|
`http://127.0.0.1:8000/api/guests/${this.editingGuest}` :
|
|
'http://127.0.0.1:8000/api/guests';
|
|
await axios({method, url, data: this.guestForm});
|
|
await this.loadGuests();
|
|
bootstrap.Modal.getInstance(document.getElementById('guestModal')).hide();
|
|
alert('保存成功');
|
|
} catch (error) {
|
|
alert('保存失败: ' + error.message);
|
|
}
|
|
},
|
|
editGuest(guest) {
|
|
this.editingGuest = guest.guest_id;
|
|
this.guestForm = {...guest};
|
|
new bootstrap.Modal(document.getElementById('guestModal')).show();
|
|
},
|
|
async deleteGuest(id) {
|
|
if (!confirm('确定删除?')) return;
|
|
try {
|
|
await axios.delete(`http://127.0.0.1:8000/api/guests/${id}`);
|
|
await this.loadGuests();
|
|
alert('删除成功');
|
|
} catch (error) {
|
|
alert('删除失败: ' + error.message);
|
|
}
|
|
},
|
|
|
|
// 预订管理
|
|
showReservationForm() {
|
|
this.reservationForm = { guest_name: '', phone: '', checkin_date: '', checkout_date: '', room_type: '', channel: '' };
|
|
new bootstrap.Modal(document.getElementById('reservationModal')).show();
|
|
},
|
|
async saveReservation() {
|
|
if (!this.reservationForm.guest_name || !this.reservationForm.checkin_date || !this.reservationForm.checkout_date) {
|
|
alert('请完整填写必填字段');
|
|
return;
|
|
}
|
|
try {
|
|
const response = await axios.post('http://127.0.0.1:8000/api/reservations', {
|
|
guest_name: this.reservationForm.guest_name,
|
|
phone: this.reservationForm.phone,
|
|
checkin_date: this.reservationForm.checkin_date,
|
|
checkout_date: this.reservationForm.checkout_date,
|
|
room_type: this.reservationForm.room_type,
|
|
channel: this.reservationForm.channel
|
|
});
|
|
alert(`预订成功: ${this.reservationForm.guest_name} 房型 ${this.reservationForm.room_type}`);
|
|
bootstrap.Modal.getInstance(document.getElementById('reservationModal')).hide();
|
|
await this.loadReservations();
|
|
} catch (error) {
|
|
alert('创建预订失败: ' + (error.response?.data?.detail || error.message));
|
|
}
|
|
},
|
|
|
|
// 员工管理
|
|
showEmployeeForm() {
|
|
this.editingEmployee = false;
|
|
this.employeeForm = { name: '', role: '', permission: '', phone: '', account: '', password: '' };
|
|
new bootstrap.Modal(document.getElementById('employeeModal')).show();
|
|
},
|
|
async saveEmployee() {
|
|
if (!this.employeeForm.name || !this.employeeForm.role || !this.employeeForm.phone) {
|
|
alert('请完整填写必填字段');
|
|
return;
|
|
}
|
|
try {
|
|
const method = this.editingEmployee ? 'PUT' : 'POST';
|
|
const url = this.editingEmployee ?
|
|
`http://127.0.0.1:8000/api/employees/${this.editingEmployee}` :
|
|
'http://127.0.0.1:8000/api/employees';
|
|
await axios({method, url, data: this.employeeForm});
|
|
await this.loadEmployees();
|
|
bootstrap.Modal.getInstance(document.getElementById('employeeModal')).hide();
|
|
alert('保存成功');
|
|
} catch (error) {
|
|
alert('保存失败: ' + error.message);
|
|
}
|
|
},
|
|
editEmployee(e) {
|
|
this.editingEmployee = e.employee_id;
|
|
this.employeeForm = {...e};
|
|
new bootstrap.Modal(document.getElementById('employeeModal')).show();
|
|
},
|
|
async deleteEmployee(id) {
|
|
if (!confirm('确定删除?')) return;
|
|
try {
|
|
await axios.delete(`http://127.0.0.1:8000/api/employees/${id}`);
|
|
await this.loadEmployees();
|
|
alert('删除成功');
|
|
} catch (error) {
|
|
alert('删除失败: ' + error.message);
|
|
}
|
|
},
|
|
|
|
// 房间管理
|
|
async deleteRoom(id) {
|
|
if (!confirm('确定删除?')) return;
|
|
try {
|
|
await axios.delete(`http://127.0.0.1:8000/api/rooms/${id}`);
|
|
await this.loadRooms();
|
|
alert('删除成功');
|
|
} catch (error) {
|
|
alert('删除失败: ' + error.message);
|
|
}
|
|
},
|
|
filterRooms(status) {
|
|
this.roomStatusFilter = status;
|
|
},
|
|
async updateRoomStatus(room, newStatus) {
|
|
try {
|
|
const statusMap = {
|
|
'available': '空闲',
|
|
'cleaning': '清洁中',
|
|
'maintenance': '维修中',
|
|
'occupied': '已占用'
|
|
};
|
|
const statusText = statusMap[newStatus] || newStatus;
|
|
const response = await axios.put(`http://127.0.0.1:8000/api/rooms/${room.room_id}`, {
|
|
status: statusText
|
|
});
|
|
alert(`房间 ${room.room_id} 状态已更新为 ${statusText}`);
|
|
await this.loadRooms();
|
|
} catch (error) {
|
|
alert('更新失败: ' + error.message);
|
|
}
|
|
},
|
|
async markRoomCleaning(roomId) {
|
|
try {
|
|
const response = await axios.put(`http://127.0.0.1:8000/api/rooms/${roomId}`, {
|
|
status: '清洁中'
|
|
});
|
|
alert('已标记房间为清洁中');
|
|
await this.loadRooms();
|
|
} catch (error) {
|
|
alert('标记失败: ' + error.message);
|
|
}
|
|
},
|
|
|
|
// 入住签入
|
|
showCheckinModal() {
|
|
this.showCheckinForm();
|
|
},
|
|
|
|
// 退房签出
|
|
showCheckoutModal(guest) {
|
|
this.showCheckoutForm(guest);
|
|
},
|
|
|
|
// 宾客管理
|
|
showGuestModal() {
|
|
this.showGuestForm();
|
|
},
|
|
viewGuestDetails(guest) {
|
|
alert(`宾客信息:\n姓名: ${guest.name}\n电话: ${guest.phone}\n房间: ${guest.room}`);
|
|
},
|
|
|
|
// 员工管理
|
|
showEmployeeModal() {
|
|
this.showEmployeeForm();
|
|
},
|
|
|
|
// 预订管理
|
|
showReservationModal() {
|
|
this.showReservationForm();
|
|
},
|
|
async confirmReservation(orderId) {
|
|
try {
|
|
const response = await axios.put(`http://127.0.0.1:8000/api/orders/${orderId}`, {
|
|
status: '已确认'
|
|
});
|
|
alert('预订已确认');
|
|
await this.loadReservations();
|
|
} catch (error) {
|
|
alert('操作失败: ' + error.message);
|
|
}
|
|
},
|
|
async cancelReservation(orderId) {
|
|
if (!confirm('确定取消预订?')) return;
|
|
try {
|
|
const response = await axios.delete(`http://127.0.0.1:8000/api/orders/${orderId}`);
|
|
alert('预订已取消');
|
|
await this.loadReservations();
|
|
} catch (error) {
|
|
alert('取消失败: ' + error.message);
|
|
}
|
|
},
|
|
|
|
// 服务请求
|
|
showServiceModal() {
|
|
this.showServiceForm();
|
|
},
|
|
async completeService(serviceId) {
|
|
try {
|
|
// 此处可根据需要调用后端API
|
|
alert(`服务请求 ${serviceId} 已完成`);
|
|
await this.loadServices();
|
|
} catch (error) {
|
|
alert('操作失败: ' + error.message);
|
|
}
|
|
},
|
|
|
|
// 账单结算
|
|
async confirmPayment(billId) {
|
|
const bill = this.bills.find(b => b.bill_id === billId);
|
|
if (!bill) {
|
|
alert('账单不存在');
|
|
return;
|
|
}
|
|
try {
|
|
// 调用后端API确认支付
|
|
const response = await axios.post(`http://127.0.0.1:8000/api/bills/${billId}`, {
|
|
status: '已支付',
|
|
payment_method: bill.payment_method
|
|
});
|
|
alert(`账单 ${billId} 已确认支付,金额: ¥${bill.total_amount}`);
|
|
await this.loadBills();
|
|
} catch (error) {
|
|
alert('支付确认失败: ' + error.message);
|
|
}
|
|
},
|
|
|
|
// 工具方法
|
|
formatDate(dateStr) {
|
|
if (!dateStr) return '';
|
|
try {
|
|
const date = new Date(dateStr);
|
|
return date.toLocaleString('zh-CN', {year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit'});
|
|
} catch (e) {
|
|
return dateStr;
|
|
}
|
|
},
|
|
getRoomCardStyle(status) {
|
|
const styles = {
|
|
'空闲': { borderLeft: '4px solid #2ecc71' },
|
|
'已占用': { borderLeft: '4px solid #e74c3c' },
|
|
'清洁中': { borderLeft: '4px solid #f39c12' },
|
|
'维修中': { borderLeft: '4px solid #9b59b6' }
|
|
};
|
|
return styles[status] || { borderLeft: '4px solid #95a5a6' };
|
|
},
|
|
getStatusText(status) {
|
|
const statusMap = {
|
|
'空闲': '可用',
|
|
'已占用': '已占',
|
|
'清洁中': '清洁',
|
|
'维修中': '维修'
|
|
};
|
|
return statusMap[status] || status;
|
|
},
|
|
getRoomStatusBadge(status) {
|
|
const badges = {
|
|
'空闲': 'bg-success',
|
|
'已占用': 'bg-danger',
|
|
'清洁中': 'bg-info',
|
|
'维修中': 'bg-warning'
|
|
};
|
|
return badges[status] || 'bg-secondary';
|
|
},
|
|
getServiceStatusBadge(status) {
|
|
const badges = {
|
|
'待处理': 'bg-warning',
|
|
'处理中': 'bg-info',
|
|
'已完成': 'bg-success'
|
|
};
|
|
return badges[status] || 'bg-secondary';
|
|
},
|
|
getPriorityColor(priority) {
|
|
const colors = {
|
|
'低': 'primary',
|
|
'中': 'warning',
|
|
'高': 'danger',
|
|
'紧急': 'dark'
|
|
};
|
|
return colors[priority] || 'secondary';
|
|
}
|
|
},
|
|
mounted() {
|
|
this.loadAllData();
|
|
}
|
|
}).mount('#app');
|
|
</script>
|
|
</body>
|
|
</html> |