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

<!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>