no message

This commit is contained in:
2026-04-06 23:19:45 +08:00
parent 067c1189a2
commit 8a667533c1
146 changed files with 12710 additions and 0 deletions

View File

@@ -0,0 +1,418 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.util.List, com.hrs.model.entity.Collection, com.hrs.model.entity.House, com.hrs.model.entity.User" %>
<%
User loginUser = (User) session.getAttribute("loginUser");
if (loginUser == null || !"0".equals(loginUser.getRoleType())) {
response.sendRedirect(request.getContextPath() + "/user/login");
return;
}
List<Collection> collectionList = (List<Collection>) request.getAttribute("collectionList");
Integer totalCount = (Integer) request.getAttribute("totalCount");
String msg = request.getParameter("msg");
String error = request.getParameter("error");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>我的收藏 - 租房系统</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, "微软雅黑", sans-serif;
background-color: #f5f5f5;
}
.header {
background-color: #4CAF50;
color: white;
padding: 15px 0;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.container {
width: 1200px;
margin: 0 auto;
overflow: hidden;
}
.logo {
float: left;
font-size: 24px;
font-weight: bold;
}
.nav {
float: right;
margin-top: 5px;
}
.nav a {
color: white;
text-decoration: none;
margin-left: 20px;
padding: 5px 10px;
}
.nav a:hover, .nav a.active {
background-color: #45a049;
border-radius: 4px;
}
.user-info {
float: right;
margin-right: 20px;
font-size: 14px;
}
.breadcrumb {
background-color: white;
padding: 12px 0;
margin-bottom: 20px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.breadcrumb a {
color: #4CAF50;
text-decoration: none;
}
.stats {
background-color: white;
padding: 10px 15px;
border-radius: 4px;
margin-bottom: 20px;
color: #666;
}
.message {
background-color: #e8f5e9;
color: #2e7d32;
padding: 12px;
border-radius: 4px;
margin-bottom: 20px;
text-align: center;
animation: fadeOut 3s ease forwards;
}
.error-message {
background-color: #ffebee;
color: #c62828;
padding: 12px;
border-radius: 4px;
margin-bottom: 20px;
text-align: center;
animation: fadeOut 3s ease forwards;
}
@keyframes fadeOut {
0% { opacity: 1; }
70% { opacity: 1; }
100% { opacity: 0; display: none; }
}
.collection-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.collection-card {
background-color: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: transform 0.3s;
position: relative;
}
.collection-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.house-image {
height: 200px;
background-color: #e0e0e0;
background-size: cover;
background-position: center;
}
.house-image .no-image {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: #999;
font-size: 14px;
}
.house-info {
padding: 15px;
}
.house-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
}
.house-title a {
color: #333;
text-decoration: none;
}
.house-title a:hover {
color: #4CAF50;
}
.house-detail {
color: #666;
font-size: 14px;
margin-bottom: 8px;
}
.house-price {
color: #f44336;
font-size: 20px;
font-weight: bold;
margin: 10px 0;
}
.house-price span {
font-size: 14px;
font-weight: normal;
}
.collect-time {
font-size: 12px;
color: #999;
margin-top: 10px;
}
.delete-btn {
position: absolute;
top: 10px;
right: 10px;
background-color: rgba(0,0,0,0.6);
color: white;
border: none;
border-radius: 50%;
width: 30px;
height: 30px;
cursor: pointer;
font-size: 18px;
transition: background-color 0.3s;
z-index: 10;
}
.delete-btn:hover {
background-color: #f44336;
}
.empty-state {
text-align: center;
padding: 80px;
background-color: white;
border-radius: 8px;
color: #999;
}
.empty-state p {
margin-top: 20px;
font-size: 16px;
}
.empty-state a {
color: #4CAF50;
text-decoration: none;
}
.footer {
background-color: #333;
color: white;
text-align: center;
padding: 20px;
margin-top: 40px;
}
.toast {
position: fixed;
top: 80px;
right: 20px;
padding: 12px 24px;
border-radius: 4px;
color: white;
z-index: 1000;
animation: slideIn 0.3s ease;
}
.toast-success {
background-color: #4CAF50;
}
.toast-error {
background-color: #f44336;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.clearfix::after {
content: "";
clear: both;
display: table;
}
</style>
</head>
<body>
<div class="header">
<div class="container clearfix">
<div class="logo">🏠 租房系统</div>
<div class="user-info">
欢迎,<%= loginUser.getRealName() %> |
<a href="${pageContext.request.contextPath}/user/logout" style="color:white;">退出</a>
</div>
<div class="nav">
<a href="${pageContext.request.contextPath}/user/house/list">首页</a>
<a href="${pageContext.request.contextPath}/user/collection/list" class="active">我的收藏</a>
<a href="${pageContext.request.contextPath}/user/reservation/list">我的预约</a>
<a href="${pageContext.request.contextPath}/user/order/list">我的订单</a>
</div>
</div>
</div>
<div class="container">
<div class="breadcrumb">
<a href="${pageContext.request.contextPath}/user/house/list">首页</a> &gt;
<span>我的收藏</span>
</div>
<% if (msg != null) { %>
<div class="message"><%= msg %></div>
<% } %>
<% if (error != null) { %>
<div class="error-message"><%= error %></div>
<% } %>
<div class="stats">
📚 共收藏 <strong id="totalCount"><%= totalCount %></strong> 套房源
</div>
<div class="collection-list" id="collectionList">
<%
if (collectionList != null && !collectionList.isEmpty()) {
for (Collection collection : collectionList) {
House house = collection.getHouse();
if (house != null) {
%>
<div class="collection-card" data-id="<%= collection.getId() %>">
<div class="house-image" style="background-image: url('<%= house.getFirstImage() != null ? house.getFirstImage() : "" %>');">
<% if (house.getFirstImage() == null) { %>
<div class="no-image">🏠 暂无图片</div>
<% } %>
</div>
<button class="delete-btn" onclick="deleteCollection(<%= collection.getId() %>)" title="取消收藏">×</button>
<div class="house-info">
<div class="house-title">
<a href="${pageContext.request.contextPath}/user/house/detail?id=<%= house.getId() %>">
<%= house.getTitle() %>
</a>
</div>
<div class="house-detail">
📍 <%= house.getArea() %> | <%= house.getHouseType() %> | <%= house.getRentTypeText() %>
</div>
<div class="house-price">
¥<%= house.getRentPrice() %><span>/月</span>
</div>
<div class="collect-time">
收藏时间:<%= collection.getCollectTime() %>
</div>
</div>
</div>
<%
}
}
} else {
%>
<div class="empty-state">
<div style="font-size: 64px;">❤️</div>
<p>还没有收藏任何房源</p>
<p><a href="${pageContext.request.contextPath}/user/house/list">去浏览房源 &gt;&gt;</a></p>
</div>
<%
}
%>
</div>
</div>
<div class="footer">
<p>© 2026 租房系统 | 让租房更简单</p>
</div>
<script>
// AJAX删除收藏
function deleteCollection(id) {
if (confirm('确定要取消收藏这套房源吗?')) {
// 发送AJAX请求
var xhr = new XMLHttpRequest();
xhr.open('GET', '${pageContext.request.contextPath}/user/collection/delete?id=' + id + '&ajax=true&from=list', true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var result = JSON.parse(xhr.responseText);
if (result.success) {
// 移除DOM元素
var card = document.querySelector('.collection-card[data-id="' + id + '"]');
if (card) {
card.remove();
}
// 更新计数
var totalSpan = document.getElementById('totalCount');
var currentCount = parseInt(totalSpan.innerText);
totalSpan.innerText = currentCount - 1;
// 显示成功提示
showToast('取消收藏成功', 'success');
// 检查是否还有收藏
var remainingCards = document.querySelectorAll('.collection-card');
if (remainingCards.length == 0) {
location.reload(); // 刷新页面显示空状态
}
} else {
showToast(result.msg || '取消收藏失败', 'error');
}
}
};
xhr.send();
}
}
// 显示提示消息
function showToast(msg, type) {
var toast = document.createElement('div');
toast.className = 'toast toast-' + type;
toast.innerHTML = msg;
document.body.appendChild(toast);
setTimeout(function() {
toast.remove();
}, 2000);
}
</script>
</body>
</html>

View File

@@ -0,0 +1,597 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.hrs.model.entity.House, com.hrs.model.entity.User" %>
<%
User loginUser = (User) session.getAttribute("loginUser");
if (loginUser == null || !"0".equals(loginUser.getRoleType())) {
response.sendRedirect(request.getContextPath() + "/user/login");
return;
}
House house = (House) request.getAttribute("house");
if (house == null) {
response.sendRedirect(request.getContextPath() + "/user/house/list");
return;
}
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><%= house.getTitle() %> - 租房系统</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, "微软雅黑", sans-serif;
background-color: #f5f5f5;
}
/* 头部导航 */
.header {
background-color: #4CAF50;
color: white;
padding: 15px 0;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.container {
width: 1200px;
margin: 0 auto;
overflow: hidden;
}
.logo {
float: left;
font-size: 24px;
font-weight: bold;
}
.nav {
float: right;
margin-top: 5px;
}
.nav a {
color: white;
text-decoration: none;
margin-left: 20px;
padding: 5px 10px;
}
.nav a:hover {
background-color: #45a049;
border-radius: 4px;
}
.user-info {
float: right;
margin-right: 20px;
font-size: 14px;
}
/* 面包屑导航 */
.breadcrumb {
background-color: white;
padding: 12px 0;
margin-bottom: 20px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.breadcrumb a {
color: #4CAF50;
text-decoration: none;
}
.breadcrumb a:hover {
text-decoration: underline;
}
/* 主要内容区 */
.main-content {
background-color: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
/* 图片区域 */
.house-images {
padding: 20px;
border-bottom: 1px solid #eee;
}
.main-image {
width: 100%;
height: 400px;
background-color: #f0f0f0;
background-size: cover;
background-position: center;
border-radius: 8px;
margin-bottom: 15px;
}
.thumb-images {
display: flex;
gap: 10px;
overflow-x: auto;
}
.thumb {
width: 80px;
height: 60px;
background-color: #e0e0e0;
background-size: cover;
background-position: center;
border-radius: 4px;
cursor: pointer;
border: 2px solid transparent;
}
.thumb:hover {
border-color: #4CAF50;
}
.thumb.active {
border-color: #4CAF50;
}
.no-image {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: #999;
font-size: 24px;
}
/* 房源信息 */
.house-info {
padding: 20px;
}
.house-title {
font-size: 28px;
font-weight: bold;
color: #333;
margin-bottom: 15px;
}
.price-box {
background-color: #fff3e0;
padding: 15px 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.price {
font-size: 32px;
color: #f44336;
font-weight: bold;
}
.price span {
font-size: 16px;
font-weight: normal;
}
.info-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
margin-bottom: 20px;
padding: 15px 0;
border-bottom: 1px solid #eee;
}
.info-item {
text-align: center;
}
.info-label {
font-size: 12px;
color: #999;
margin-bottom: 5px;
}
.info-value {
font-size: 18px;
font-weight: bold;
color: #333;
}
.section {
margin-bottom: 25px;
}
.section-title {
font-size: 18px;
font-weight: bold;
padding-bottom: 10px;
margin-bottom: 15px;
border-bottom: 2px solid #4CAF50;
}
.facility-list {
display: flex;
flex-wrap: wrap;
gap: 15px;
}
.facility-item {
background-color: #f5f5f5;
padding: 5px 15px;
border-radius: 20px;
font-size: 14px;
color: #666;
}
.description {
line-height: 1.8;
color: #666;
background-color: #f9f9f9;
padding: 15px;
border-radius: 8px;
}
/* 房东信息 */
.landlord-card {
background-color: #f9f9f9;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.landlord-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
}
.landlord-info {
display: flex;
align-items: center;
gap: 20px;
}
.landlord-avatar {
width: 60px;
height: 60px;
background-color: #4CAF50;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 24px;
}
.landlord-details p {
margin: 5px 0;
color: #666;
}
/* 操作按钮 */
.action-buttons {
display: flex;
gap: 15px;
margin-top: 20px;
}
.btn {
padding: 12px 30px;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: all 0.3s;
text-decoration: none;
display: inline-block;
text-align: center;
}
.btn-primary {
background-color: #4CAF50;
color: white;
}
.btn-primary:hover {
background-color: #45a049;
}
.btn-secondary {
background-color: #ff9800;
color: white;
}
.btn-secondary:hover {
background-color: #fb8c00;
}
.btn-outline {
background-color: transparent;
border: 1px solid #4CAF50;
color: #4CAF50;
}
.btn-outline:hover {
background-color: #4CAF50;
color: white;
}
/* 页脚 */
.footer {
background-color: #333;
color: white;
text-align: center;
padding: 20px;
margin-top: 40px;
}
/* 消息提示 */
.message {
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
border-radius: 4px;
color: white;
z-index: 1000;
animation: slideIn 0.3s ease;
}
.message-success {
background-color: #4CAF50;
}
.message-error {
background-color: #f44336;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.clearfix::after {
content: "";
clear: both;
display: table;
}
</style>
</head>
<body>
<!-- 头部导航 -->
<div class="header">
<div class="container clearfix">
<div class="logo">🏠 租房系统</div>
<div class="user-info">
欢迎,<%= loginUser.getRealName() %> |
<a href="${pageContext.request.contextPath}/user/logout" style="color:white;">退出</a>
</div>
<div class="nav">
<a href="${pageContext.request.contextPath}/user/house/list">首页</a>
<a href="${pageContext.request.contextPath}/user/collection/list">我的收藏</a>
<a href="${pageContext.request.contextPath}/user/reservation/list">我的预约</a>
<a href="${pageContext.request.contextPath}/user/order/list">我的订单</a>
</div>
</div>
</div>
<div class="container">
<!-- 面包屑导航 -->
<div class="breadcrumb">
<a href="${pageContext.request.contextPath}/user/house/list">首页</a> &gt;
<span><%= house.getTitle() %></span>
</div>
<div class="main-content">
<!-- 图片区域 -->
<div class="house-images">
<div class="main-image" id="mainImage" style="background-image: url('<%= house.getFirstImage() != null ? house.getFirstImage() : "" %>');">
<% if (house.getFirstImage() == null) { %>
<div class="no-image">🏠 暂无图片</div>
<% } %>
</div>
<div class="thumb-images" id="thumbImages">
<%
String[] images = null;
if (house.getImgUrl() != null && !house.getImgUrl().isEmpty()) {
images = house.getImgUrl().split(",");
for (int i = 0; i < images.length; i++) {
%>
<div class="thumb" style="background-image: url('<%= images[i] %>');" onclick="changeImage('<%= images[i] %>', this)"></div>
<%
}
}
%>
</div>
</div>
<!-- 房源信息 -->
<div class="house-info">
<div class="house-title"><%= house.getTitle() %></div>
<div class="price-box">
<span class="price">¥<%= house.getRentPrice() %><span>/月</span></span>
</div>
<div class="info-grid">
<div class="info-item">
<div class="info-label">户型</div>
<div class="info-value"><%= house.getHouseType() != null ? house.getHouseType() : "待定" %></div>
</div>
<div class="info-item">
<div class="info-label">面积</div>
<div class="info-value"><%= house.getArea() != null ? house.getArea() : "待定" %></div>
</div>
<div class="info-item">
<div class="info-label">租赁类型</div>
<div class="info-value"><%= house.getRentTypeText() %></div>
</div>
<div class="info-item">
<div class="info-label">房源编号</div>
<div class="info-value"><%= house.getHouseNo() %></div>
</div>
</div>
<div class="section">
<div class="section-title">📍 房源地址</div>
<p style="color: #666;"><%= house.getAddress() != null ? house.getAddress() : "暂无地址信息" %></p>
</div>
<div class="section">
<div class="section-title">🛋️ 配套设施</div>
<div class="facility-list">
<%
String facility = house.getFacility();
if (facility != null && !facility.isEmpty()) {
String[] facilities = facility.split(",");
for (String f : facilities) {
%>
<span class="facility-item"><%= f.trim() %></span>
<%
}
} else {
%>
<span class="facility-item">暂无设施信息</span>
<%
}
%>
</div>
</div>
<div class="section">
<div class="section-title">📝 房源描述</div>
<div class="description">
<%= house.getDescription() != null ? house.getDescription() : "暂无描述信息" %>
</div>
</div>
</div>
</div>
<!-- 房东信息 -->
<div class="landlord-card">
<div class="landlord-title">👤 房东信息</div>
<div class="landlord-info">
<div class="landlord-avatar">👨</div>
<div class="landlord-details">
<p><strong><%= house.getLandlordName() != null ? house.getLandlordName() : "房东" %></strong></p>
<p>📞 <%= house.getLandlordPhone() != null ? house.getLandlordPhone() : "暂无联系方式" %></p>
<p>⭐ 认证房东 · 已发布房源</p>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="action-buttons">
<button class="btn btn-primary" onclick="makeReservation()">📅 预约看房</button>
<%
// 检查是否已收藏
com.hrs.model.dao.CollectionDAO collectionDAO = new com.hrs.model.dao.CollectionDAO();
boolean isCollected = collectionDAO.isCollected(loginUser.getId(), house.getId());
if (isCollected) {
%>
<button class="btn btn-outline" onclick="cancelCollection(<%= house.getId() %>)">❤️ 已收藏</button>
<%
} else {
%>
<button class="btn btn-secondary" onclick="addToCollection()">❤️ 收藏房源</button>
<%
}
%>
<a href="${pageContext.request.contextPath}/user/house/list" class="btn btn-outline">🔙 返回列表</a>
</div>
</div>
<!-- 页脚 -->
<div class="footer">
<p>© 2026 租房系统 | 让租房更简单</p>
</div>
<script>
// 切换主图
function changeImage(url, element) {
document.getElementById('mainImage').style.backgroundImage = 'url(' + url + ')';
// 移除所有active类
var thumbs = document.querySelectorAll('.thumb');
thumbs.forEach(function(thumb) {
thumb.classList.remove('active');
});
// 添加active类到当前
element.classList.add('active');
}
// 预约看房
function makeReservation() {
var houseId = <%= house.getId() %>;
// 跳转到预约页面
window.location.href = '${pageContext.request.contextPath}/user/reservation/add?houseId=' + houseId;
}
// 收藏房源暂时用alert后面实现真正的收藏功能
function addToCollection() {
var houseId = <%= house.getId() %>;
// 发送AJAX请求收藏
var xhr = new XMLHttpRequest();
xhr.open('POST', '${pageContext.request.contextPath}/user/collection/add', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var result = xhr.responseText;
if (result == 'success') {
showMessage('收藏成功!', 'success');
// 刷新页面更新按钮状态
setTimeout(function() {
location.reload();
}, 1000);
} else if (result == 'exists') {
showMessage('您已经收藏过这套房源了', 'error');
} else {
showMessage('收藏失败,请稍后重试', 'error');
}
}
};
xhr.send('houseId=' + houseId);
}
// 取消收藏
function cancelCollection(houseId) {
if (confirm('确定要取消收藏这套房源吗?')) {
window.location.href = '${pageContext.request.contextPath}/user/collection/deleteByHouse?houseId=' + houseId;
}
}
// 显示提示消息
function showMessage(msg, type) {
var messageDiv = document.createElement('div');
messageDiv.className = 'message message-' + type;
messageDiv.innerHTML = msg;
document.body.appendChild(messageDiv);
setTimeout(function() {
messageDiv.remove();
}, 3000);
}
// 如果有缩略图,默认第一个高亮
var firstThumb = document.querySelector('.thumb');
if (firstThumb) {
firstThumb.classList.add('active');
}
</script>
</body>
</html>

View File

@@ -0,0 +1,412 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.util.List, com.hrs.model.entity.House, com.hrs.model.entity.User" %>
<%
User loginUser = (User) session.getAttribute("loginUser");
if (loginUser == null || !"0".equals(loginUser.getRoleType())) {
response.sendRedirect(request.getContextPath() + "/user/login");
return;
}
List<House> houseList = (List<House>) request.getAttribute("houseList");
Integer totalCount = (Integer) request.getAttribute("totalCount");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>房源列表 - 租房系统</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, "微软雅黑", sans-serif;
background-color: #f5f5f5;
}
/* 头部导航 */
.header {
background-color: #4CAF50;
color: white;
padding: 15px 0;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.container {
width: 1200px;
margin: 0 auto;
overflow: hidden;
}
.logo {
float: left;
font-size: 24px;
font-weight: bold;
}
.nav {
float: right;
margin-top: 5px;
}
.nav a {
color: white;
text-decoration: none;
margin-left: 20px;
padding: 5px 10px;
}
.nav a:hover {
background-color: #45a049;
border-radius: 4px;
}
.user-info {
float: right;
margin-right: 20px;
font-size: 14px;
}
/* 搜索栏 */
.search-bar {
background-color: white;
padding: 20px 0;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.search-form {
display: flex;
gap: 15px;
align-items: flex-end;
flex-wrap: wrap;
}
.search-group {
display: flex;
flex-direction: column;
}
.search-group label {
font-size: 12px;
color: #666;
margin-bottom: 5px;
}
.search-group input, .search-group select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
width: 150px;
}
.search-btn {
background-color: #4CAF50;
color: white;
border: none;
padding: 8px 20px;
border-radius: 4px;
cursor: pointer;
}
.search-btn:hover {
background-color: #45a049;
}
.reset-btn {
background-color: #999;
color: white;
border: none;
padding: 8px 20px;
border-radius: 4px;
cursor: pointer;
text-decoration: none;
display: inline-block;
}
/* 房源列表 */
.house-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.house-card {
background-color: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: transform 0.3s, box-shadow 0.3s;
}
.house-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.house-image {
height: 200px;
background-color: #e0e0e0;
background-size: cover;
background-position: center;
position: relative;
}
.house-image .no-image {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: #999;
font-size: 14px;
}
.house-info {
padding: 15px;
}
.house-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
color: #333;
}
.house-title a {
color: #333;
text-decoration: none;
}
.house-title a:hover {
color: #4CAF50;
}
.house-detail {
color: #666;
font-size: 14px;
margin-bottom: 8px;
}
.house-price {
color: #f44336;
font-size: 22px;
font-weight: bold;
margin: 10px 0;
}
.house-price span {
font-size: 14px;
font-weight: normal;
}
.house-tags {
margin: 10px 0;
}
.tag {
display: inline-block;
background-color: #f0f0f0;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
margin-right: 5px;
color: #666;
}
.house-footer {
border-top: 1px solid #eee;
padding-top: 10px;
margin-top: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.landlord-info {
font-size: 12px;
color: #999;
}
.btn {
padding: 5px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
text-decoration: none;
display: inline-block;
}
.btn-primary {
background-color: #4CAF50;
color: white;
}
.btn-primary:hover {
background-color: #45a049;
}
.btn-outline {
background-color: transparent;
border: 1px solid #4CAF50;
color: #4CAF50;
}
.btn-outline:hover {
background-color: #4CAF50;
color: white;
}
/* 统计信息 */
.stats {
background-color: white;
padding: 10px 15px;
border-radius: 4px;
margin-bottom: 20px;
color: #666;
}
/* 空状态 */
.empty-state {
text-align: center;
padding: 60px;
background-color: white;
border-radius: 8px;
color: #999;
}
.empty-state p {
margin-top: 10px;
}
/* 页脚 */
.footer {
background-color: #333;
color: white;
text-align: center;
padding: 20px;
margin-top: 40px;
}
.clearfix::after {
content: "";
clear: both;
display: table;
}
</style>
</head>
<body>
<!-- 头部导航 -->
<div class="header">
<div class="container clearfix">
<div class="logo">🏠 租房系统</div>
<div class="user-info">
欢迎,<%= loginUser.getRealName() %> |
<a href="${pageContext.request.contextPath}/user/logout" style="color:white;">退出</a>
</div>
<div class="nav">
<a href="${pageContext.request.contextPath}/user/house/list">首页</a>
<a href="${pageContext.request.contextPath}/user/collection/list">我的收藏</a>
<a href="#">我的预约</a>
<a href="#">我的订单</a>
</div>
</div>
</div>
<!-- 搜索栏 -->
<div class="search-bar">
<div class="container">
<form class="search-form" action="${pageContext.request.contextPath}/user/house/list" method="get">
<div class="search-group">
<label>区域</label>
<select name="area">
<option value="">全部区域</option>
<option value="朝阳区" <%= "朝阳区".equals(request.getAttribute("searchArea")) ? "selected" : "" %>>朝阳区</option>
<option value="海淀区" <%= "海淀区".equals(request.getAttribute("searchArea")) ? "selected" : "" %>>海淀区</option>
<option value="东城区" <%= "东城区".equals(request.getAttribute("searchArea")) ? "selected" : "" %>>东城区</option>
<option value="西城区" <%= "西城区".equals(request.getAttribute("searchArea")) ? "selected" : "" %>>西城区</option>
<option value="丰台区" <%= "丰台区".equals(request.getAttribute("searchArea")) ? "selected" : "" %>>丰台区</option>
</select>
</div>
<div class="search-group">
<label>最低租金(元)</label>
<input type="number" name="minPrice" placeholder="不限" value="<%= request.getAttribute("minPrice") != null ? request.getAttribute("minPrice") : "" %>">
</div>
<div class="search-group">
<label>最高租金(元)</label>
<input type="number" name="maxPrice" placeholder="不限" value="<%= request.getAttribute("maxPrice") != null ? request.getAttribute("maxPrice") : "" %>">
</div>
<button type="submit" class="search-btn">搜索</button>
<a href="${pageContext.request.contextPath}/user/house/list" class="reset-btn">重置</a>
</form>
</div>
</div>
<div class="container">
<!-- 统计信息 -->
<div class="stats">
📊 共找到 <strong><%= totalCount %></strong> 套房源
</div>
<!-- 房源列表 -->
<div class="house-list">
<%
if (houseList != null && !houseList.isEmpty()) {
for (House house : houseList) {
%>
<div class="house-card">
<div class="house-image" style="background-image: url('<%= house.getFirstImage() != null ? house.getFirstImage() : "" %>');">
<% if (house.getFirstImage() == null) { %>
<div class="no-image">🏠 暂无图片</div>
<% } %>
</div>
<div class="house-info">
<div class="house-title">
<a href="${pageContext.request.contextPath}/user/house/detail?id=<%= house.getId() %>">
<%= house.getTitle() %>
</a>
</div>
<div class="house-detail">
📍 <%= house.getArea() %> | <%= house.getHouseType() %> | <%= house.getRentTypeText() %>
</div>
<div class="house-price">
¥<%= house.getRentPrice() %><span>/月</span>
</div>
<div class="house-tags">
<span class="tag">#<%= house.getHouseType() %></span>
<span class="tag">#<%= house.getArea() %></span>
</div>
<div class="house-footer">
<div class="landlord-info">
👤 <%= house.getLandlordName() != null ? house.getLandlordName() : "房东" %>
</div>
<a href="${pageContext.request.contextPath}/user/house/detail?id=<%= house.getId() %>" class="btn btn-primary">
查看详情
</a>
</div>
</div>
</div>
<%
}
} else {
%>
<div class="empty-state">
<div style="font-size: 48px;">🏠</div>
<p>暂无房源,请稍后再来</p>
</div>
<%
}
%>
</div>
</div>
<!-- 页脚 -->
<div class="footer">
<p>© 2026 租房系统 | 让租房更简单</p>
</div>
</body>
</html>

View File

@@ -0,0 +1,5 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
// 重定向到房源列表页
response.sendRedirect(request.getContextPath() + "/user/house/list");
%>

View File

@@ -0,0 +1,151 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>租房系统登录</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.login-container {
background-color: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
width: 360px;
}
h2 {
text-align: center;
color: #333;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
color: #666;
font-weight: bold;
}
input[type="text"],
input[type="password"] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
font-size: 14px;
}
.role-group {
display: flex;
justify-content: space-around;
margin: 10px 0;
}
.role-group label {
display: inline;
font-weight: normal;
}
button {
width: 100%;
padding: 12px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #45a049;
}
.error {
background-color: #ffebee;
color: #c62828;
padding: 10px;
border-radius: 4px;
margin-bottom: 20px;
text-align: center;
}
.register-link {
text-align: center;
margin-top: 20px;
color: #666;
}
.register-link a {
color: #4CAF50;
text-decoration: none;
}
.register-link a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="login-container">
<h2>租房系统登录</h2>
<%-- 显示错误信息 --%>
<%
String error = (String)request.getAttribute("error");
if(error != null) {
%>
<div class="error"><%= error %></div>
<%
}
%>
<%-- 显示成功信息(如注册成功) --%>
<%
String message = (String)request.getAttribute("message");
if(message != null) {
%>
<div class="message" style="background-color:#e8f5e9; color:#2e7d32; padding:10px; border-radius:4px; margin-bottom:20px; text-align:center;">
<%= message %>
</div>
<%
}
%>
<form action="${pageContext.request.contextPath}/user/login" method="post">
<div class="form-group">
<label>用户名:</label>
<input type="text" name="username" required>
</div>
<div class="form-group">
<label>密码:</label>
<input type="password" name="password" required>
</div>
<div class="form-group">
<label>登录角色:</label>
<div class="role-group">
<label>
<input type="radio" name="roleType" value="0" checked> 租客
</label>
<label>
<input type="radio" name="roleType" value="1"> 房东
</label>
<label>
<input type="radio" name="roleType" value="2"> 管理员
</label>
</div>
</div>
<button type="submit">登录</button>
</form>
<div class="register-link">
还没有账号? <a href="${pageContext.request.contextPath}/user/register">立即注册</a>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,218 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>租房系统注册</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
}
.register-container {
background-color: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
width: 450px;
}
h2 {
text-align: center;
color: #333;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
color: #666;
font-weight: bold;
}
input[type="text"],
input[type="password"] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
font-size: 14px;
}
.role-group {
display: flex;
gap: 20px;
margin: 10px 0;
}
.role-group label {
display: inline;
font-weight: normal;
}
button {
width: 100%;
padding: 12px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #45a049;
}
.error {
background-color: #ffebee;
color: #c62828;
padding: 10px;
border-radius: 4px;
margin-bottom: 20px;
text-align: center;
}
.message {
background-color: #e8f5e9;
color: #2e7d32;
padding: 10px;
border-radius: 4px;
margin-bottom: 20px;
text-align: center;
}
.login-link {
text-align: center;
margin-top: 20px;
color: #666;
}
.login-link a {
color: #4CAF50;
text-decoration: none;
}
.login-link a:hover {
text-decoration: underline;
}
.hint {
font-size: 12px;
color: #999;
margin-top: 5px;
}
.required {
color: #f44336;
}
</style>
</head>
<body>
<div class="register-container">
<h2>租房系统注册</h2>
<%-- 显示错误信息 --%>
<%
String error = (String)request.getAttribute("error");
if(error != null) {
%>
<div class="error"><%= error %></div>
<%
}
%>
<%-- 显示成功信息 --%>
<%
String message = (String)request.getAttribute("message");
if(message != null) {
%>
<div class="message"><%= message %></div>
<%
}
%>
<form action="${pageContext.request.contextPath}/user/register" method="post">
<div class="form-group">
<label>用户名 <span class="required">*</span></label>
<input type="text" name="username" placeholder="请输入用户名" required>
</div>
<div class="form-group">
<label>密码 <span class="required">*</span></label>
<input type="password" name="password" placeholder="请输入密码" required>
<div class="hint">密码长度6-20位建议使用字母+数字组合</div>
</div>
<div class="form-group">
<label>确认密码 <span class="required">*</span></label>
<input type="password" name="confirmPassword" placeholder="请再次输入密码" required>
</div>
<div class="form-group">
<label>真实姓名</label>
<input type="text" name="realName" placeholder="请输入真实姓名">
</div>
<div class="form-group">
<label>手机号 <span class="required">*</span></label>
<input type="text" name="phone" placeholder="请输入11位手机号" required>
</div>
<div class="form-group">
<label>身份证号</label>
<input type="text" name="idCard" placeholder="请输入身份证号(可选)">
<div class="hint">房东认证需要提供身份证号</div>
</div>
<div class="form-group">
<label>注册角色 <span class="required">*</span></label>
<div class="role-group">
<label>
<input type="radio" name="roleType" value="0" checked> 租客
</label>
<label>
<input type="radio" name="roleType" value="1"> 房东
</label>
</div>
<div class="hint">房东需要管理员审核认证后才能发布房源</div>
</div>
<button type="submit">立即注册</button>
</form>
<div class="login-link">
已有账号? <a href="${pageContext.request.contextPath}/user/login">返回登录</a>
</div>
</div>
<script>
// 简单的前端验证
document.querySelector('form').addEventListener('submit', function(e) {
var password = document.querySelector('input[name="password"]').value;
var confirm = document.querySelector('input[name="confirmPassword"]').value;
var phone = document.querySelector('input[name="phone"]').value;
// 验证密码长度
if(password.length < 6 || password.length > 20) {
alert('密码长度应为6-20位');
e.preventDefault();
return false;
}
// 验证两次密码
if(password !== confirm) {
alert('两次输入的密码不一致');
e.preventDefault();
return false;
}
// 验证手机号
var phoneReg = /^1[3-9]\d{9}$/;
if(!phoneReg.test(phone)) {
alert('请输入正确的11位手机号');
e.preventDefault();
return false;
}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,313 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.hrs.model.entity.House, com.hrs.model.entity.User" %>
<%
User loginUser = (User) session.getAttribute("loginUser");
if (loginUser == null || !"0".equals(loginUser.getRoleType())) {
response.sendRedirect(request.getContextPath() + "/user/login");
return;
}
House house = (House) request.getAttribute("house");
if (house == null) {
response.sendRedirect(request.getContextPath() + "/user/house/list");
return;
}
String error = (String) request.getAttribute("error");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>预约看房 - 租房系统</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, "微软雅黑", sans-serif;
background-color: #f5f5f5;
}
.header {
background-color: #4CAF50;
color: white;
padding: 15px 0;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.container {
width: 800px;
margin: 0 auto;
overflow: hidden;
}
.logo {
float: left;
font-size: 24px;
font-weight: bold;
}
.nav {
float: right;
margin-top: 5px;
}
.nav a {
color: white;
text-decoration: none;
margin-left: 20px;
padding: 5px 10px;
}
.nav a:hover {
background-color: #45a049;
border-radius: 4px;
}
.user-info {
float: right;
margin-right: 20px;
font-size: 14px;
}
.breadcrumb {
background-color: white;
padding: 12px 0;
margin-bottom: 20px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.breadcrumb a {
color: #4CAF50;
text-decoration: none;
}
.main-card {
background-color: white;
border-radius: 8px;
padding: 30px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.house-summary {
background-color: #f9f9f9;
padding: 15px;
border-radius: 8px;
margin-bottom: 25px;
display: flex;
gap: 15px;
}
.house-img {
width: 100px;
height: 100px;
background-color: #e0e0e0;
background-size: cover;
background-position: center;
border-radius: 8px;
}
.house-info {
flex: 1;
}
.house-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 8px;
}
.house-detail {
color: #666;
font-size: 14px;
margin-bottom: 5px;
}
.house-price {
color: #f44336;
font-size: 18px;
font-weight: bold;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: bold;
color: #333;
}
input[type="date"],
input[type="time"],
textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
textarea {
resize: vertical;
min-height: 80px;
}
.error {
background-color: #ffebee;
color: #c62828;
padding: 12px;
border-radius: 4px;
margin-bottom: 20px;
text-align: center;
}
.btn {
padding: 12px 30px;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
text-decoration: none;
display: inline-block;
}
.btn-primary {
background-color: #4CAF50;
color: white;
}
.btn-primary:hover {
background-color: #45a049;
}
.btn-secondary {
background-color: #999;
color: white;
margin-left: 10px;
}
.btn-secondary:hover {
background-color: #777;
}
.hint {
font-size: 12px;
color: #999;
margin-top: 5px;
}
.footer {
background-color: #333;
color: white;
text-align: center;
padding: 20px;
margin-top: 40px;
}
.clearfix::after {
content: "";
clear: both;
display: table;
}
</style>
</head>
<body>
<div class="header">
<div class="container clearfix">
<div class="logo">🏠 租房系统</div>
<div class="user-info">
欢迎,<%= loginUser.getRealName() %> |
<a href="${pageContext.request.contextPath}/user/logout" style="color:white;">退出</a>
</div>
<div class="nav">
<a href="${pageContext.request.contextPath}/user/house/list">首页</a>
<a href="${pageContext.request.contextPath}/user/collection/list">我的收藏</a>
<a href="${pageContext.request.contextPath}/user/reservation/list">我的预约</a>
<a href="${pageContext.request.contextPath}/user/order/list">我的订单</a>
</div>
</div>
</div>
<div class="container">
<div class="breadcrumb">
<a href="${pageContext.request.contextPath}/user/house/list">首页</a> &gt;
<a href="${pageContext.request.contextPath}/user/house/detail?id=<%= house.getId() %>">房源详情</a> &gt;
<span>预约看房</span>
</div>
<div class="main-card">
<h2 style="margin-bottom: 20px;">📅 预约看房</h2>
<% if (error != null) { %>
<div class="error"><%= error %></div>
<% } %>
<!-- 房源信息摘要 -->
<div class="house-summary">
<div class="house-img" style="background-image: url('<%= house.getFirstImage() != null ? house.getFirstImage() : "" %>');">
<% if (house.getFirstImage() == null) { %>
<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#999;">🏠</div>
<% } %>
</div>
<div class="house-info">
<div class="house-title"><%= house.getTitle() %></div>
<div class="house-detail">📍 <%= house.getArea() %> | <%= house.getHouseType() %> | <%= house.getRentTypeText() %></div>
<div class="house-price">¥<%= house.getRentPrice() %>/月</div>
</div>
</div>
<form action="${pageContext.request.contextPath}/user/reservation/add" method="post">
<input type="hidden" name="houseId" value="<%= house.getId() %>">
<div class="form-group">
<label>选择看房日期 *</label>
<input type="date" name="reserveDate" required min="<%= new java.text.SimpleDateFormat("yyyy-MM-dd").format(new java.util.Date()) %>">
<div class="hint">请选择您方便的看房日期</div>
</div>
<div class="form-group">
<label>选择看房时间 *</label>
<input type="time" name="reserveTime" required>
<div class="hint">建议选择 9:00-18:00 之间的时间</div>
</div>
<div class="form-group">
<label>备注信息(可选)</label>
<textarea name="remark" placeholder="请输入您的特殊需求或问题..."></textarea>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">提交预约</button>
<a href="${pageContext.request.contextPath}/user/house/detail?id=<%= house.getId() %>" class="btn btn-secondary">返回</a>
</div>
</form>
</div>
</div>
<div class="footer">
<p>© 2026 租房系统 | 让租房更简单</p>
</div>
<script>
// 设置日期最小值
var dateInput = document.querySelector('input[type="date"]');
var today = new Date();
var yyyy = today.getFullYear();
var mm = String(today.getMonth() + 1).padStart(2, '0');
var dd = String(today.getDate()).padStart(2, '0');
dateInput.min = yyyy + '-' + mm + '-' + dd;
// 设置时间默认值
var timeInput = document.querySelector('input[type="time"]');
timeInput.value = "10:00";
</script>
</body>
</html>

View File

@@ -0,0 +1,347 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.util.List, java.text.SimpleDateFormat, com.hrs.model.entity.Reservation, com.hrs.model.entity.House, com.hrs.model.entity.User" %>
<%
User loginUser = (User) session.getAttribute("loginUser");
if (loginUser == null || !"0".equals(loginUser.getRoleType())) {
response.sendRedirect(request.getContextPath() + "/user/login");
return;
}
List<Reservation> reservationList = (List<Reservation>) request.getAttribute("reservationList");
Integer totalCount = (Integer) request.getAttribute("totalCount");
String msg = request.getParameter("msg");
String error = request.getParameter("error");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>我的预约 - 租房系统</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, "微软雅黑", sans-serif;
background-color: #f5f5f5;
}
.header {
background-color: #4CAF50;
color: white;
padding: 15px 0;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.container {
width: 1200px;
margin: 0 auto;
overflow: hidden;
}
.logo {
float: left;
font-size: 24px;
font-weight: bold;
}
.nav {
float: right;
margin-top: 5px;
}
.nav a {
color: white;
text-decoration: none;
margin-left: 20px;
padding: 5px 10px;
}
.nav a:hover, .nav a.active {
background-color: #45a049;
border-radius: 4px;
}
.user-info {
float: right;
margin-right: 20px;
font-size: 14px;
}
.breadcrumb {
background-color: white;
padding: 12px 0;
margin-bottom: 20px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.breadcrumb a {
color: #4CAF50;
text-decoration: none;
}
.stats {
background-color: white;
padding: 10px 15px;
border-radius: 4px;
margin-bottom: 20px;
color: #666;
}
.message {
background-color: #e8f5e9;
color: #2e7d32;
padding: 12px;
border-radius: 4px;
margin-bottom: 20px;
text-align: center;
animation: fadeOut 3s ease forwards;
}
.error-message {
background-color: #ffebee;
color: #c62828;
padding: 12px;
border-radius: 4px;
margin-bottom: 20px;
text-align: center;
animation: fadeOut 3s ease forwards;
}
@keyframes fadeOut {
0% { opacity: 1; }
70% { opacity: 1; }
100% { opacity: 0; display: none; }
}
.reservation-table {
background-color: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 15px;
text-align: left;
border-bottom: 1px solid #eee;
}
th {
background-color: #f5f5f5;
font-weight: bold;
color: #333;
}
tr:hover {
background-color: #f9f9f9;
}
.status-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: bold;
}
.status-pending {
background-color: #fff3e0;
color: #f39c12;
}
.status-confirmed {
background-color: #e8f5e9;
color: #4CAF50;
}
.status-cancelled {
background-color: #ffebee;
color: #f44336;
}
.status-completed {
background-color: #e3f2fd;
color: #2196F3;
}
.house-link {
color: #4CAF50;
text-decoration: none;
}
.house-link:hover {
text-decoration: underline;
}
.btn {
padding: 4px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
text-decoration: none;
display: inline-block;
}
.btn-danger {
background-color: #f44336;
color: white;
}
.btn-danger:hover {
background-color: #d32f2f;
}
.btn-disabled {
background-color: #ccc;
color: #666;
cursor: not-allowed;
}
.empty-state {
text-align: center;
padding: 60px;
background-color: white;
border-radius: 8px;
color: #999;
}
.empty-state p {
margin-top: 20px;
}
.empty-state a {
color: #4CAF50;
text-decoration: none;
}
.footer {
background-color: #333;
color: white;
text-align: center;
padding: 20px;
margin-top: 40px;
}
.clearfix::after {
content: "";
clear: both;
display: table;
}
</style>
</head>
<body>
<div class="header">
<div class="container clearfix">
<div class="logo">🏠 租房系统</div>
<div class="user-info">
欢迎,<%= loginUser.getRealName() %> |
<a href="${pageContext.request.contextPath}/user/logout" style="color:white;">退出</a>
</div>
<div class="nav">
<a href="${pageContext.request.contextPath}/user/house/list">首页</a>
<a href="${pageContext.request.contextPath}/user/collection/list">我的收藏</a>
<a href="${pageContext.request.contextPath}/user/reservation/list" class="active">我的预约</a>
<a href="${pageContext.request.contextPath}/user/order/list">我的订单</a>
</div>
</div>
</div>
<div class="container">
<div class="breadcrumb">
<a href="${pageContext.request.contextPath}/user/house/list">首页</a> &gt;
<span>我的预约</span>
</div>
<% if (msg != null) { %>
<div class="message"><%= msg %></div>
<% } %>
<% if (error != null) { %>
<div class="error-message"><%= error %></div>
<% } %>
<div class="stats">
📋 共有 <strong><%= totalCount %></strong> 条预约记录
</div>
<div class="reservation-table">
<%
if (reservationList != null && !reservationList.isEmpty()) {
%>
<table>
<thead>
<tr>
<th>房源信息</th>
<th>预约时间</th>
<th>状态</th>
<th>备注</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<%
for (Reservation reservation : reservationList) {
House house = reservation.getHouse();
%>
<tr>
<td>
<a href="${pageContext.request.contextPath}/user/house/detail?id=<%= house.getId() %>" class="house-link">
<strong><%= house.getTitle() %></strong>
</a><br>
<span style="font-size:12px;color:#999;"><%= house.getArea() %> | ¥<%= house.getRentPrice() %>/月</span>
</td>
<td><%= sdf.format(reservation.getReserveTime()) %></td>
<td>
<span class="status-badge status-<%= reservation.getStatus().equals("0") ? "pending" : reservation.getStatus().equals("1") ? "confirmed" : reservation.getStatus().equals("2") ? "cancelled" : "completed" %>">
<%= reservation.getStatusText() %>
</span>
</td>
<td style="max-width:200px; overflow:hidden; text-overflow:ellipsis;">
<%= reservation.getRemark() != null ? reservation.getRemark() : "-" %>
</td>
<td>
<% if ("0".equals(reservation.getStatus())) { %>
<a href="${pageContext.request.contextPath}/user/reservation/cancel?id=<%= reservation.getId() %>"
class="btn btn-danger"
onclick="return confirm('确定要取消这个预约吗?')">取消</a>
<% } else { %>
<span class="btn btn-disabled">已处理</span>
<% } %>
</td>
</tr>
<%
}
%>
</tbody>
</table>
<%
} else {
%>
<div class="empty-state">
<div style="font-size: 64px;">📅</div>
<p>还没有任何预约记录</p>
<p><a href="${pageContext.request.contextPath}/user/house/list">去浏览房源 &gt;&gt;</a></p>
</div>
<%
}
%>
</div>
</div>
<div class="footer">
<p>© 2026 租房系统 | 让租房更简单</p>
</div>
</body>
</html>