上传完整架构
This commit is contained in:
3
HRS/WebContent/META-INF/MANIFEST.MF
Normal file
3
HRS/WebContent/META-INF/MANIFEST.MF
Normal file
@@ -0,0 +1,3 @@
|
||||
Manifest-Version: 1.0
|
||||
Class-Path:
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/dao/CollectionDao.class
Normal file
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/dao/CollectionDao.class
Normal file
Binary file not shown.
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/dao/HouseDao.class
Normal file
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/dao/HouseDao.class
Normal file
Binary file not shown.
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/dao/OrderDao.class
Normal file
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/dao/OrderDao.class
Normal file
Binary file not shown.
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/dao/ReservationDao.class
Normal file
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/dao/ReservationDao.class
Normal file
Binary file not shown.
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/dao/UserDao.class
Normal file
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/dao/UserDao.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/filter/LoginFilter.class
Normal file
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/filter/LoginFilter.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/model/entity/House.class
Normal file
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/model/entity/House.class
Normal file
Binary file not shown.
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/model/entity/Order.class
Normal file
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/model/entity/Order.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/model/entity/User.class
Normal file
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/model/entity/User.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/service/UserService.class
Normal file
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/service/UserService.class
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/util/DBUtil.class
Normal file
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/util/DBUtil.class
Normal file
Binary file not shown.
Binary file not shown.
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/util/MD5Util.class
Normal file
BIN
HRS/WebContent/WEB-INF/classes/com/hrs/util/MD5Util.class
Normal file
Binary file not shown.
BIN
HRS/WebContent/WEB-INF/lib/mysql-connector-java-5.1.49.jar
Normal file
BIN
HRS/WebContent/WEB-INF/lib/mysql-connector-java-5.1.49.jar
Normal file
Binary file not shown.
29
HRS/WebContent/WEB-INF/web.xml
Normal file
29
HRS/WebContent/WEB-INF/web.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
|
||||
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
|
||||
version="4.0">
|
||||
|
||||
<display-name>HRS</display-name>
|
||||
|
||||
<!-- 欢迎页面 -->
|
||||
<welcome-file-list>
|
||||
<welcome-file>index.jsp</welcome-file>
|
||||
</welcome-file-list>
|
||||
|
||||
<!-- 字符编码过滤器 -->
|
||||
<filter>
|
||||
<filter-name>encodingFilter</filter-name>
|
||||
<filter-class>com.hrs.filter.EncodingFilter</filter-class>
|
||||
<init-param>
|
||||
<param-name>encoding</param-name>
|
||||
<param-value>UTF-8</param-value>
|
||||
</init-param>
|
||||
</filter>
|
||||
<filter-mapping>
|
||||
<filter-name>encodingFilter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
|
||||
</web-app>
|
||||
5
HRS/WebContent/index.jsp
Normal file
5
HRS/WebContent/index.jsp
Normal file
@@ -0,0 +1,5 @@
|
||||
<%@ page contentType="text/html; charset=UTF-8" language="java" %>
|
||||
<%
|
||||
// 重定向到登录页面
|
||||
response.sendRedirect(request.getContextPath() + "/user/login");
|
||||
%>
|
||||
502
HRS/WebContent/jsp/admin/house_list.jsp
Normal file
502
HRS/WebContent/jsp/admin/house_list.jsp
Normal file
@@ -0,0 +1,502 @@
|
||||
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
||||
<%@ page import="java.util.List, java.text.SimpleDateFormat, com.hrs.model.entity.House, com.hrs.model.entity.User" %>
|
||||
<%
|
||||
User loginUser = (User) session.getAttribute("loginUser");
|
||||
if (loginUser == null || !"2".equals(loginUser.getRoleType())) {
|
||||
response.sendRedirect(request.getContextPath() + "/user/login");
|
||||
return;
|
||||
}
|
||||
|
||||
List<House> pendingList = (List<House>) request.getAttribute("pendingList");
|
||||
List<House> publishedList = (List<House>) request.getAttribute("publishedList");
|
||||
List<House> offlineList = (List<House>) request.getAttribute("offlineList");
|
||||
Integer pendingCount = (Integer) request.getAttribute("pendingCount");
|
||||
Integer publishedCount = (Integer) request.getAttribute("publishedCount");
|
||||
Integer offlineCount = (Integer) request.getAttribute("offlineCount");
|
||||
String msg = request.getParameter("msg");
|
||||
String error = request.getParameter("error");
|
||||
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
%>
|
||||
<!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: #2196F3;
|
||||
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: #1976D2;
|
||||
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);
|
||||
}
|
||||
|
||||
.stats-row {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background-color: white;
|
||||
padding: 15px 25px;
|
||||
border-radius: 8px;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.stat-pending .stat-number { color: #f39c12; }
|
||||
.stat-published .stat-number { color: #4CAF50; }
|
||||
.stat-offline .stat-number { color: #f44336; }
|
||||
|
||||
.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; }
|
||||
}
|
||||
|
||||
.house-section {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 30px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
background-color: #f5f5f5;
|
||||
padding: 15px 20px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid #2196F3;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 12px 15px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #fafafa;
|
||||
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-published {
|
||||
background-color: #e8f5e9;
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
.status-offline {
|
||||
background-color: #ffebee;
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 4px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
.btn-approve {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-reject {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-offline {
|
||||
background-color: #ff9800;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-online {
|
||||
background-color: #2196F3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-view {
|
||||
background-color: #999;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-approve:hover { background-color: #45a049; }
|
||||
.btn-reject:hover { background-color: #d32f2f; }
|
||||
.btn-offline:hover { background-color: #fb8c00; }
|
||||
.btn-online:hover { background-color: #1976D2; }
|
||||
.btn-view:hover { background-color: #777; }
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.price {
|
||||
color: #f44336;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.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}/jsp/admin/index.jsp" >首页</a>
|
||||
<a href="${pageContext.request.contextPath}/admin/user/list">用户管理</a>
|
||||
<a href="${pageContext.request.contextPath}/admin/house/list" class="active">房源审核</a>
|
||||
<a href="${pageContext.request.contextPath}/admin/order/list">订单管理</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="breadcrumb">
|
||||
<a href="${pageContext.request.contextPath}/admin/house/list">首页</a> >
|
||||
<span>房源审核</span>
|
||||
</div>
|
||||
|
||||
<% if (msg != null) { %>
|
||||
<div class="message"><%= msg %></div>
|
||||
<% } %>
|
||||
<% if (error != null) { %>
|
||||
<div class="error-message"><%= error %></div>
|
||||
<% } %>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="stats-row">
|
||||
<div class="stat-card stat-pending">
|
||||
<div class="stat-number"><%= pendingCount %></div>
|
||||
<div class="stat-label">待审核房源</div>
|
||||
</div>
|
||||
<div class="stat-card stat-published">
|
||||
<div class="stat-number"><%= publishedCount %></div>
|
||||
<div class="stat-label">已上架房源</div>
|
||||
</div>
|
||||
<div class="stat-card stat-offline">
|
||||
<div class="stat-number"><%= offlineCount %></div>
|
||||
<div class="stat-label">已下架/违规房源</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 待审核房源 -->
|
||||
<div class="house-section">
|
||||
<div class="section-title">
|
||||
⏳ 待审核房源
|
||||
<span style="font-size:14px; color:#999; margin-left:10px;">共 <%= pendingCount %> 条</span>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>房源图片</th>
|
||||
<th>房源信息</th>
|
||||
<th>房东信息</th>
|
||||
<th>租金</th>
|
||||
<th>发布时间</th>
|
||||
<th>操作</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%
|
||||
if (pendingList != null && !pendingList.isEmpty()) {
|
||||
for (House house : pendingList) {
|
||||
%>
|
||||
<tr>
|
||||
<td style="width:80px;">
|
||||
<div style="width:70px; height:50px; background-color:#e0e0e0; background-size:cover; background-position:center; border-radius:4px;
|
||||
<% if (house.getFirstImage() != null) { %>background-image:url('/HRS/<%= house.getFirstImage() %>');<% } %>">
|
||||
<% if (house.getFirstImage() == null) { %>
|
||||
<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:12px;">无图</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<strong><%= house.getTitle() %></strong><br>
|
||||
<span style="font-size:12px;color:#999;"><%= house.getArea() %> | <%= house.getHouseType() %></span>
|
||||
</td>
|
||||
<td>
|
||||
<%= house.getLandlordName() != null ? house.getLandlordName() : "未知" %><br>
|
||||
<span style="font-size:12px;color:#999;"><%= house.getLandlordPhone() != null ? house.getLandlordPhone() : "" %></span>
|
||||
</td>
|
||||
<td class="price">¥<%= house.getRentPrice() %>/月</td>
|
||||
<td><%= sdf.format(house.getCreateTime()) %></td>
|
||||
<td>
|
||||
<a href="${pageContext.request.contextPath}/admin/house/audit?id=<%= house.getId() %>&action=approve"
|
||||
class="btn btn-approve" onclick="return confirm('确定通过审核吗?房源将立即上架。')">通过</a>
|
||||
<a href="${pageContext.request.contextPath}/admin/house/audit?id=<%= house.getId() %>&action=reject"
|
||||
class="btn btn-reject" onclick="return confirm('确定拒绝该房源吗?')">拒绝</a>
|
||||
<a href="${pageContext.request.contextPath}/user/house/detail?id=<%= house.getId() %>"
|
||||
class="btn btn-view" target="_blank">预览</a>
|
||||
</tr>
|
||||
<%
|
||||
}
|
||||
} else {
|
||||
%>
|
||||
<tr><td colspan="6" class="empty-state">暂无待审核房源</td></tr>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 已上架房源 -->
|
||||
<div class="house-section">
|
||||
<div class="section-title">
|
||||
✅ 已上架房源
|
||||
<span style="font-size:14px; color:#999; margin-left:10px;">共 <%= publishedCount %> 条</span>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>房源图片</th>
|
||||
<th>房源信息</th>
|
||||
<th>房东信息</th>
|
||||
<th>租金</th>
|
||||
<th>上架时间</th>
|
||||
<th>操作</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%
|
||||
if (publishedList != null && !publishedList.isEmpty()) {
|
||||
for (House house : publishedList) {
|
||||
%>
|
||||
<tr>
|
||||
<td style="width:80px;">
|
||||
<div style="width:70px; height:50px; background-color:#e0e0e0; background-size:cover; background-position:center; border-radius:4px;
|
||||
<% if (house.getFirstImage() != null) { %>background-image:url('/HRS/<%= house.getFirstImage() %>');<% } %>">
|
||||
<% if (house.getFirstImage() == null) { %>
|
||||
<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:12px;">无图</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<strong><%= house.getTitle() %></strong><br>
|
||||
<span style="font-size:12px;color:#999;"><%= house.getArea() %> | <%= house.getHouseType() %></span>
|
||||
</td>
|
||||
<td>
|
||||
<%= house.getLandlordName() != null ? house.getLandlordName() : "未知" %>
|
||||
</td>
|
||||
<td class="price">¥<%= house.getRentPrice() %>/月</td>
|
||||
<td><%= sdf.format(house.getCreateTime()) %></td>
|
||||
<td>
|
||||
<a href="${pageContext.request.contextPath}/admin/house/offline?id=<%= house.getId() %>"
|
||||
class="btn btn-offline" onclick="return confirm('确定要下架该房源吗?')">下架</a>
|
||||
<a href="${pageContext.request.contextPath}/user/house/detail?id=<%= house.getId() %>"
|
||||
class="btn btn-view" target="_blank">预览</a>
|
||||
</td>
|
||||
</tr>
|
||||
<%
|
||||
}
|
||||
} else {
|
||||
%>
|
||||
<tr><td colspan="6" class="empty-state">暂无已上架房源</td></tr>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 已下架/违规房源 -->
|
||||
<div class="house-section">
|
||||
<div class="section-title">
|
||||
📥 已下架/违规房源
|
||||
<span style="font-size:14px; color:#999; margin-left:10px;">共 <%= offlineCount %> 条</span>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>房源图片</th>
|
||||
<th>房源信息</th>
|
||||
<th>房东信息</th>
|
||||
<th>租金</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%
|
||||
if (offlineList != null && !offlineList.isEmpty()) {
|
||||
for (House house : offlineList) {
|
||||
%>
|
||||
<tr>
|
||||
<td style="width:80px;">
|
||||
<div style="width:70px; height:50px; background-color:#e0e0e0; background-size:cover; background-position:center; border-radius:4px;
|
||||
<% if (house.getFirstImage() != null) { %>background-image:url('/HRS/<%= house.getFirstImage() %>');<% } %>">
|
||||
<% if (house.getFirstImage() == null) { %>
|
||||
<div style="display:flex;align-items:center;justify-content:center;height:100%;font-size:12px;">无图</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<strong><%= house.getTitle() %></strong><br>
|
||||
<span style="font-size:12px;color:#999;"><%= house.getArea() %> | <%= house.getHouseType() %></span>
|
||||
</td>
|
||||
<td>
|
||||
<%= house.getLandlordName() != null ? house.getLandlordName() : "未知" %>
|
||||
</td>
|
||||
<td class="price">¥<%= house.getRentPrice() %>/月</td>
|
||||
<td>
|
||||
<span class="status-badge status-offline"><%= house.getStatusText() %></span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="${pageContext.request.contextPath}/admin/house/online?id=<%= house.getId() %>"
|
||||
class="btn btn-online" onclick="return confirm('确定要重新上架该房源吗?')">重新上架</a>
|
||||
<a href="${pageContext.request.contextPath}/user/house/detail?id=<%= house.getId() %>"
|
||||
class="btn btn-view" target="_blank">预览</a>
|
||||
</td>
|
||||
</tr>
|
||||
<%
|
||||
}
|
||||
} else {
|
||||
%>
|
||||
<tr><td colspan="6" class="empty-state">暂无已下架房源</td></tr>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>© 2026 租房系统 | 让租房更简单</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
303
HRS/WebContent/jsp/admin/index.jsp
Normal file
303
HRS/WebContent/jsp/admin/index.jsp
Normal file
@@ -0,0 +1,303 @@
|
||||
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
||||
<%@ page import="com.hrs.model.entity.User" %>
|
||||
<%
|
||||
User loginUser = (User) session.getAttribute("loginUser");
|
||||
if (loginUser == null || !"2".equals(loginUser.getRoleType())) {
|
||||
response.sendRedirect(request.getContextPath() + "/user/login");
|
||||
return;
|
||||
}
|
||||
%>
|
||||
<!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: #2196F3;
|
||||
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;
|
||||
}
|
||||
|
||||
.logo span {
|
||||
font-size: 14px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.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: #1976D2;
|
||||
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: #2196F3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.welcome-card {
|
||||
background: linear-gradient(135deg, #2196F3 0%, #1976D2 100%);
|
||||
color: white;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.welcome-card h1 {
|
||||
font-size: 28px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.welcome-card p {
|
||||
font-size: 16px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.stats-row {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background-color: white;
|
||||
padding: 25px;
|
||||
border-radius: 8px;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.stat-user .stat-number { color: #2196F3; }
|
||||
.stat-house .stat-number { color: #4CAF50; }
|
||||
.stat-order .stat-number { color: #ff9800; }
|
||||
.stat-pending .stat-number { color: #f44336; }
|
||||
|
||||
.quick-actions {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.quick-actions h3 {
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
border-left: 4px solid #2196F3;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
background-color: #f5f5f5;
|
||||
padding: 15px 25px;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
transition: all 0.3s;
|
||||
text-align: center;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background-color: #2196F3;
|
||||
color: white;
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.action-btn .icon {
|
||||
font-size: 24px;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.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">👑 租房系统管理后台 <span>Administrator</span></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}/admin/index.jsp" class="active">首页</a>
|
||||
<a href="${pageContext.request.contextPath}/admin/user/list">用户管理</a>
|
||||
<a href="${pageContext.request.contextPath}/admin/house/list">房源审核</a>
|
||||
<a href="${pageContext.request.contextPath}/admin/order/list">订单管理</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="breadcrumb">
|
||||
<a href="${pageContext.request.contextPath}/admin/index.jsp">首页</a> >
|
||||
<span>控制台</span>
|
||||
</div>
|
||||
|
||||
<div class="welcome-card">
|
||||
<h1>欢迎回来,<%= loginUser.getRealName() %>!</h1>
|
||||
<p>今天是 <%= new java.text.SimpleDateFormat("yyyy年MM月dd日 EEEE").format(new java.util.Date()) %></p>
|
||||
<p style="margin-top: 10px;">您可以通过下方面板管理系统数据</p>
|
||||
</div>
|
||||
|
||||
<div class="stats-row" id="statsRow">
|
||||
<div class="stat-card stat-user">
|
||||
<div class="stat-icon">👥</div>
|
||||
<div class="stat-number" id="userCount">-</div>
|
||||
<div class="stat-label">注册用户</div>
|
||||
</div>
|
||||
<div class="stat-card stat-house">
|
||||
<div class="stat-icon">🏠</div>
|
||||
<div class="stat-number" id="houseCount">-</div>
|
||||
<div class="stat-label">房源总数</div>
|
||||
</div>
|
||||
<div class="stat-card stat-order">
|
||||
<div class="stat-icon">📦</div>
|
||||
<div class="stat-number" id="orderCount">-</div>
|
||||
<div class="stat-label">订单总数</div>
|
||||
</div>
|
||||
<div class="stat-card stat-pending">
|
||||
<div class="stat-icon">⏳</div>
|
||||
<div class="stat-number" id="pendingCount">-</div>
|
||||
<div class="stat-label">待审核房源</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quick-actions">
|
||||
<h3>快捷操作</h3>
|
||||
<div class="action-buttons">
|
||||
<a href="${pageContext.request.contextPath}/admin/user/list" class="action-btn">
|
||||
<span class="icon">👤</span>
|
||||
用户管理
|
||||
</a>
|
||||
<a href="${pageContext.request.contextPath}/admin/house/list" class="action-btn">
|
||||
<span class="icon">🏠</span>
|
||||
房源审核
|
||||
</a>
|
||||
<a href="${pageContext.request.contextPath}/admin/order/list" class="action-btn">
|
||||
<span class="icon">📦</span>
|
||||
订单管理
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>© 2026 租房系统 | 管理员后台 | 让租房更简单</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 加载统计数据
|
||||
function loadStats() {
|
||||
fetch('${pageContext.request.contextPath}/admin/statistics')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('userCount').innerText = data.userCount || 0;
|
||||
document.getElementById('houseCount').innerText = data.houseCount || 0;
|
||||
document.getElementById('orderCount').innerText = data.orderCount || 0;
|
||||
document.getElementById('pendingCount').innerText = data.pendingCount || 0;
|
||||
})
|
||||
.catch(err => {
|
||||
console.log('加载统计数据失败,使用默认值');
|
||||
document.getElementById('userCount').innerText = '-';
|
||||
document.getElementById('houseCount').innerText = '-';
|
||||
document.getElementById('orderCount').innerText = '-';
|
||||
document.getElementById('pendingCount').innerText = '-';
|
||||
});
|
||||
}
|
||||
|
||||
loadStats();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
280
HRS/WebContent/jsp/admin/order_list.jsp
Normal file
280
HRS/WebContent/jsp/admin/order_list.jsp
Normal file
@@ -0,0 +1,280 @@
|
||||
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
||||
<%@ page import="java.util.List, java.text.SimpleDateFormat, com.hrs.model.entity.Order, com.hrs.model.entity.House, com.hrs.model.entity.User" %>
|
||||
<%
|
||||
User loginUser = (User) session.getAttribute("loginUser");
|
||||
if (loginUser == null || !"2".equals(loginUser.getRoleType())) {
|
||||
response.sendRedirect(request.getContextPath() + "/user/login");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Order> orderList = (List<Order>) request.getAttribute("orderList");
|
||||
Integer totalCount = (Integer) request.getAttribute("totalCount");
|
||||
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
%>
|
||||
<!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: #2196F3;
|
||||
color: white;
|
||||
padding: 15px 0;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 1300px;
|
||||
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: #1976D2;
|
||||
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: #2196F3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.stats {
|
||||
background-color: white;
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.order-table {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
min-width: 1000px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
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-paid {
|
||||
background-color: #e8f5e9;
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
.status-cancelled {
|
||||
background-color: #ffebee;
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.price {
|
||||
color: #f44336;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.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}/jsp/admin/index.jsp" >首页</a>
|
||||
<a href="${pageContext.request.contextPath}/admin/user/list">用户管理</a>
|
||||
<a href="${pageContext.request.contextPath}/admin/house/list">房源审核</a>
|
||||
<a href="${pageContext.request.contextPath}/admin/order/list" class="active">订单管理</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="breadcrumb">
|
||||
<a href="${pageContext.request.contextPath}/admin/order/list">首页</a> >
|
||||
<span>订单管理</span>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
📋 共有 <strong><%= totalCount %></strong> 个订单
|
||||
</div>
|
||||
|
||||
<div class="order-table">
|
||||
<%
|
||||
if (orderList != null && !orderList.isEmpty()) {
|
||||
%>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>订单编号</th>
|
||||
<th>租客信息</th>
|
||||
<th>房东信息</th>
|
||||
<th>房源信息</th>
|
||||
<th>租赁时间</th>
|
||||
<th>总租金</th>
|
||||
<th>支付状态</th>
|
||||
<th>订单状态</th>
|
||||
<th>下单时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%
|
||||
for (Order order : orderList) {
|
||||
House house = order.getHouse();
|
||||
User tenant = order.getTenant();
|
||||
User landlord = order.getLandlord();
|
||||
%>
|
||||
<tr>
|
||||
<td><%= order.getOrderNo() %></td>
|
||||
<td>
|
||||
<%= tenant != null ? (tenant.getRealName() != null ? tenant.getRealName() : tenant.getUserName()) : "未知" %><br>
|
||||
<span style="font-size:12px;color:#999;"><%= tenant != null ? tenant.getPhone() : "" %></span>
|
||||
</td>
|
||||
<td>
|
||||
<%= landlord != null ? (landlord.getRealName() != null ? landlord.getRealName() : landlord.getUserName()) : "未知" %>
|
||||
</td>
|
||||
<td>
|
||||
<strong><%= house != null ? house.getTitle() : "未知" %></strong><br>
|
||||
<span style="font-size:12px;color:#999;"><%= house != null ? house.getArea() : "" %></span>
|
||||
</td>
|
||||
<td>
|
||||
<%= sdf.format(order.getRentStartTime()) %> ~ <%= sdf.format(order.getRentEndTime()) %><br>
|
||||
<span style="font-size:12px;color:#999;">共<%= order.getRentDays() %>天</span>
|
||||
</td>
|
||||
<td class="price">¥<%= order.getTotalRent() %></td>
|
||||
<td>
|
||||
<span class="status-badge status-<%= "0".equals(order.getPayStatus()) ? "pending" : "paid" %>">
|
||||
<%= order.getPayStatusText() %>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="status-badge status-pending">
|
||||
<%= order.getStatusText() %>
|
||||
</span>
|
||||
</td>
|
||||
<td><%= sdf.format(order.getCreateTime()) %></td>
|
||||
</tr>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</tbody>
|
||||
20
|
||||
<%
|
||||
} else {
|
||||
%>
|
||||
<div class="empty-state">
|
||||
<div style="font-size: 64px;">📦</div>
|
||||
<p>暂无订单</p>
|
||||
</div>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>© 2026 租房系统 | 让租房更简单</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
453
HRS/WebContent/jsp/admin/user_list.jsp
Normal file
453
HRS/WebContent/jsp/admin/user_list.jsp
Normal file
@@ -0,0 +1,453 @@
|
||||
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
||||
<%@ page import="java.util.List, com.hrs.model.entity.User" %>
|
||||
<%
|
||||
User loginUser = (User) session.getAttribute("loginUser");
|
||||
if (loginUser == null || !"2".equals(loginUser.getRoleType())) {
|
||||
response.sendRedirect(request.getContextPath() + "/user/login");
|
||||
return;
|
||||
}
|
||||
|
||||
List<User> tenants = (List<User>) request.getAttribute("tenants");
|
||||
List<User> landlords = (List<User>) request.getAttribute("landlords");
|
||||
List<User> admins = (List<User>) request.getAttribute("admins");
|
||||
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: #2196F3;
|
||||
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: #1976D2;
|
||||
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);
|
||||
}
|
||||
|
||||
.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; }
|
||||
}
|
||||
|
||||
.user-section {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 30px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
background-color: #f5f5f5;
|
||||
padding: 15px 20px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid #2196F3;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 12px 15px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #fafafa;
|
||||
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-active {
|
||||
background-color: #e8f5e9;
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
.status-disabled {
|
||||
background-color: #ffebee;
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background-color: #fff3e0;
|
||||
color: #f39c12;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 4px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
.btn-enable {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-disable {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-delete {
|
||||
background-color: #999;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-enable:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.btn-disable:hover {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
|
||||
.btn-delete:hover {
|
||||
background-color: #777;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #333;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.clearfix::after {
|
||||
content: "";
|
||||
clear: both;
|
||||
display: table;
|
||||
}
|
||||
|
||||
.role-icon {
|
||||
font-size: 18px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
</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}/jsp/admin/index.jsp" >首页</a>
|
||||
<a href="${pageContext.request.contextPath}/admin/user/list" class="active">用户管理</a>
|
||||
<a href="${pageContext.request.contextPath}/admin/house/list">房源审核</a>
|
||||
<a href="${pageContext.request.contextPath}/admin/order/list">订单管理</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="breadcrumb">
|
||||
<a href="${pageContext.request.contextPath}/admin/user/list">首页</a> >
|
||||
<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="user-section">
|
||||
<div class="section-title">
|
||||
<span class="role-icon">👤</span> 租客列表
|
||||
<span style="font-size:14px; color:#999; margin-left:10px;">共 <%= tenants != null ? tenants.size() : 0 %> 人</span>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>用户名</th>
|
||||
<th>真实姓名</th>
|
||||
<th>手机号</th>
|
||||
<th>状态</th>
|
||||
<th>注册时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%
|
||||
if (tenants != null && !tenants.isEmpty()) {
|
||||
for (User user : tenants) {
|
||||
%>
|
||||
<tr>
|
||||
<td><%= user.getId() %></td>
|
||||
<td><%= user.getUserName() %></td>
|
||||
<td><%= user.getRealName() != null ? user.getRealName() : "-" %></td>
|
||||
<td><%= user.getPhone() != null ? user.getPhone() : "-" %></td>
|
||||
<td>
|
||||
<span class="status-badge <%= "1".equals(user.getStatus()) ? "status-active" : "status-disabled" %>">
|
||||
<%= "1".equals(user.getStatus()) ? "已认证" : "未认证/禁用" %>
|
||||
</span>
|
||||
</td>
|
||||
<td><%= new java.text.SimpleDateFormat("yyyy-MM-dd").format(user.getCreateTime()) %></td>
|
||||
<td>
|
||||
<% if ("1".equals(user.getStatus())) { %>
|
||||
<a href="${pageContext.request.contextPath}/admin/user/status?id=<%= user.getId() %>&action=disable"
|
||||
class="btn btn-disable" onclick="return confirm('确定要禁用该用户吗?')">禁用</a>
|
||||
<% } else { %>
|
||||
<a href="${pageContext.request.contextPath}/admin/user/status?id=<%= user.getId() %>&action=enable"
|
||||
class="btn btn-enable" onclick="return confirm('确定要启用该用户吗?')">启用</a>
|
||||
<% } %>
|
||||
<a href="${pageContext.request.contextPath}/admin/user/delete?id=<%= user.getId() %>"
|
||||
class="btn btn-delete"
|
||||
onclick="return confirm('确定要删除该用户吗?此操作不可恢复!')">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
<%
|
||||
}
|
||||
} else {
|
||||
%>
|
||||
<tr>
|
||||
<td colspan="7" class="empty-state">暂无租客数据</td>
|
||||
</tr>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 房东列表 -->
|
||||
<div class="user-section">
|
||||
<div class="section-title">
|
||||
<span class="role-icon">🏠</span> 房东列表
|
||||
<span style="font-size:14px; color:#999; margin-left:10px;">共 <%= landlords != null ? landlords.size() : 0 %> 人</span>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>用户名</th>
|
||||
<th>真实姓名</th>
|
||||
<th>手机号</th>
|
||||
<th>状态</th>
|
||||
<th>注册时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%
|
||||
if (landlords != null && !landlords.isEmpty()) {
|
||||
for (User user : landlords) {
|
||||
%>
|
||||
<tr>
|
||||
<td><%= user.getId() %></td>
|
||||
<td><%= user.getUserName() %></td>
|
||||
<td><%= user.getRealName() != null ? user.getRealName() : "-" %></td>
|
||||
<td><%= user.getPhone() != null ? user.getPhone() : "-" %></td>
|
||||
<td>
|
||||
<span class="status-badge <%= "1".equals(user.getStatus()) ? "status-active" : "status-disabled" %>">
|
||||
<%= "1".equals(user.getStatus()) ? "已认证" : "未认证/禁用" %>
|
||||
</span>
|
||||
</td>
|
||||
<td><%= new java.text.SimpleDateFormat("yyyy-MM-dd").format(user.getCreateTime()) %></td>
|
||||
<td>
|
||||
<% if ("1".equals(user.getStatus())) { %>
|
||||
<a href="${pageContext.request.contextPath}/admin/user/status?id=<%= user.getId() %>&action=disable"
|
||||
class="btn btn-disable" onclick="return confirm('确定要禁用该用户吗?')">禁用</a>
|
||||
<% } else { %>
|
||||
<a href="${pageContext.request.contextPath}/admin/user/status?id=<%= user.getId() %>&action=enable"
|
||||
class="btn btn-enable" onclick="return confirm('确定要启用该用户吗?')">启用</a>
|
||||
<% } %>
|
||||
<a href="${pageContext.request.contextPath}/admin/user/delete?id=<%= user.getId() %>"
|
||||
class="btn btn-delete"
|
||||
onclick="return confirm('确定要删除该用户吗?此操作不可恢复!')">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
<%
|
||||
}
|
||||
} else {
|
||||
%>
|
||||
<tr>
|
||||
<td colspan="7" class="empty-state">暂无房东数据</td>
|
||||
</tr>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 管理员列表 -->
|
||||
<div class="user-section">
|
||||
<div class="section-title">
|
||||
<span class="role-icon">👑</span> 管理员列表
|
||||
<span style="font-size:14px; color:#999; margin-left:10px;">共 <%= admins != null ? admins.size() : 0 %> 人</span>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>用户名</th>
|
||||
<th>真实姓名</th>
|
||||
<th>手机号</th>
|
||||
<th>状态</th>
|
||||
<th>注册时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%
|
||||
if (admins != null && !admins.isEmpty()) {
|
||||
for (User user : admins) {
|
||||
%>
|
||||
<tr>
|
||||
<td><%= user.getId() %></td>
|
||||
<td><%= user.getUserName() %></td>
|
||||
<td><%= user.getRealName() != null ? user.getRealName() : "-" %></td>
|
||||
<td><%= user.getPhone() != null ? user.getPhone() : "-" %></td>
|
||||
<td>
|
||||
<span class="status-badge status-active">管理员</span>
|
||||
</td>
|
||||
<td><%= new java.text.SimpleDateFormat("yyyy-MM-dd").format(user.getCreateTime()) %></td>
|
||||
<td>
|
||||
<span style="color:#999;">不可操作</span>
|
||||
</td>
|
||||
</tr>
|
||||
<%
|
||||
}
|
||||
} else {
|
||||
%>
|
||||
<tr>
|
||||
<td colspan="7" class="empty-state">暂无管理员数据</td>
|
||||
</tr>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>© 2026 租房系统 | 让租房更简单</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
348
HRS/WebContent/jsp/landlord/house_add.jsp
Normal file
348
HRS/WebContent/jsp/landlord/house_add.jsp
Normal file
@@ -0,0 +1,348 @@
|
||||
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
||||
<%@ page import="com.hrs.model.entity.User" %>
|
||||
<%
|
||||
User loginUser = (User) session.getAttribute("loginUser");
|
||||
if (loginUser == null || !"1".equals(loginUser.getRoleType())) {
|
||||
response.sendRedirect(request.getContextPath() + "/user/login");
|
||||
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: #ff9800;
|
||||
color: white;
|
||||
padding: 15px 0;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 900px;
|
||||
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: #fb8c00;
|
||||
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: #ff9800;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.main-card {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="number"],
|
||||
select,
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.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: #ff9800;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #fb8c00;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.row .form-group {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.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}/landlord/house/list">房源管理</a>
|
||||
<a href="${pageContext.request.contextPath}/landlord/reservation/list">预约管理</a>
|
||||
<a href="${pageContext.request.contextPath}/landlord/order/list">订单管理</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="breadcrumb">
|
||||
<a href="${pageContext.request.contextPath}/landlord/house/list">房源管理</a> >
|
||||
<span>发布房源</span>
|
||||
</div>
|
||||
|
||||
<div class="main-card">
|
||||
<h2>📝 发布新房源</h2>
|
||||
|
||||
<% if (error != null) { %>
|
||||
<div class="error"><%= error %></div>
|
||||
<% } %>
|
||||
|
||||
<form action="${pageContext.request.contextPath}/landlord/house/add" method="post" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<label>房源标题 *</label>
|
||||
<input type="text" name="title" placeholder="例如:朝阳区温馨一居室" required>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<label>所在区域</label>
|
||||
<select name="area">
|
||||
<option value="朝阳区">朝阳区</option>
|
||||
<option value="海淀区">海淀区</option>
|
||||
<option value="东城区">东城区</option>
|
||||
<option value="西城区">西城区</option>
|
||||
<option value="丰台区">丰台区</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>户型</label>
|
||||
<select name="houseType">
|
||||
<option value="一室一厅">一室一厅</option>
|
||||
<option value="两室一厅">两室一厅</option>
|
||||
<option value="两室两厅">两室两厅</option>
|
||||
<option value="三室一厅">三室一厅</option>
|
||||
<option value="三室两厅">三室两厅</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>租赁类型</label>
|
||||
<select name="rentType">
|
||||
<option value="0">整租</option>
|
||||
<option value="1">合租</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>详细地址</label>
|
||||
<input type="text" name="address" placeholder="请输入详细地址">
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<label>月租金(元)*</label>
|
||||
<input type="number" name="rentPrice" placeholder="例如:2500" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>配套设施</label>
|
||||
<input type="text" name="facility" placeholder="例如:空调,洗衣机,热水器,WiFi">
|
||||
<div class="hint">多个设施用英文逗号分隔</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 房源图片上传字段 -->
|
||||
<div class="form-group">
|
||||
<label>房源图片</label>
|
||||
<input type="file" name="images" multiple accept="image/jpeg,image/jpg,image/png,image/gif" id="imageInput">
|
||||
<div class="hint">支持jpg、png、gif格式,可多选,单张不超过5MB</div>
|
||||
</div>
|
||||
|
||||
<!-- 图片预览区域 -->
|
||||
<div class="form-group" id="previewArea" style="display:none;">
|
||||
<label>图片预览</label>
|
||||
<div id="imagePreview" style="display:flex; gap:10px; flex-wrap:wrap;"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>房源描述</label>
|
||||
<textarea name="description" placeholder="请输入房源描述信息..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary">提交审核</button>
|
||||
<a href="${pageContext.request.contextPath}/landlord/house/list" class="btn btn-secondary">返回</a>
|
||||
</div>
|
||||
|
||||
<div class="hint" style="margin-top: 15px; text-align: center;">
|
||||
房源提交后将进入审核状态,审核通过后即可在租客端展示
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>© 2026 租房系统 | 让租房更简单</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 图片预览
|
||||
document.getElementById('imageInput').addEventListener('change', function(e) {
|
||||
var previewArea = document.getElementById('previewArea');
|
||||
var previewContainer = document.getElementById('imagePreview');
|
||||
previewContainer.innerHTML = '';
|
||||
|
||||
if (this.files.length > 0) {
|
||||
previewArea.style.display = 'block';
|
||||
|
||||
for (var i = 0; i < this.files.length; i++) {
|
||||
var file = this.files[i];
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onload = function(e) {
|
||||
var img = document.createElement('img');
|
||||
img.src = e.target.result;
|
||||
img.style.width = '100px';
|
||||
img.style.height = '80px';
|
||||
img.style.objectFit = 'cover';
|
||||
img.style.borderRadius = '4px';
|
||||
img.style.border = '1px solid #ddd';
|
||||
previewContainer.appendChild(img);
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
} else {
|
||||
previewArea.style.display = 'none';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
316
HRS/WebContent/jsp/landlord/house_edit.jsp
Normal file
316
HRS/WebContent/jsp/landlord/house_edit.jsp
Normal file
@@ -0,0 +1,316 @@
|
||||
<%@ 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 || !"1".equals(loginUser.getRoleType())) {
|
||||
response.sendRedirect(request.getContextPath() + "/user/login");
|
||||
return;
|
||||
}
|
||||
|
||||
House house = (House) request.getAttribute("house");
|
||||
if (house == null) {
|
||||
response.sendRedirect(request.getContextPath() + "/landlord/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: #ff9800;
|
||||
color: white;
|
||||
padding: 15px 0;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 900px;
|
||||
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: #fb8c00;
|
||||
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: #ff9800;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.main-card {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="number"],
|
||||
select,
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.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: #ff9800;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #fb8c00;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.row .form-group {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.status-info {
|
||||
background-color: #f9f9f9;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.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}/landlord/house/list">房源管理</a>
|
||||
<a href="${pageContext.request.contextPath}/landlord/reservation/list">预约管理</a>
|
||||
<a href="${pageContext.request.contextPath}/landlord/order/list">订单管理</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="breadcrumb">
|
||||
<a href="${pageContext.request.contextPath}/landlord/house/list">房源管理</a> >
|
||||
<span>编辑房源</span>
|
||||
</div>
|
||||
|
||||
<div class="main-card">
|
||||
<h2>✏️ 编辑房源</h2>
|
||||
|
||||
<% if (error != null) { %>
|
||||
<div class="error"><%= error %></div>
|
||||
<% } %>
|
||||
|
||||
<div class="status-info">
|
||||
<strong>房源状态:</strong> <%= house.getStatusText() %>
|
||||
<span style="margin-left: 20px;"><strong>房源编号:</strong> <%= house.getHouseNo() %></span>
|
||||
</div>
|
||||
|
||||
<form action="${pageContext.request.contextPath}/landlord/house/edit" method="post">
|
||||
<input type="hidden" name="id" value="<%= house.getId() %>">
|
||||
|
||||
<div class="form-group">
|
||||
<label>房源标题 *</label>
|
||||
<input type="text" name="title" value="<%= house.getTitle() %>" required>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<label>所在区域</label>
|
||||
<select name="area">
|
||||
<option value="朝阳区" <%= "朝阳区".equals(house.getArea()) ? "selected" : "" %>>朝阳区</option>
|
||||
<option value="海淀区" <%= "海淀区".equals(house.getArea()) ? "selected" : "" %>>海淀区</option>
|
||||
<option value="东城区" <%= "东城区".equals(house.getArea()) ? "selected" : "" %>>东城区</option>
|
||||
<option value="西城区" <%= "西城区".equals(house.getArea()) ? "selected" : "" %>>西城区</option>
|
||||
<option value="丰台区" <%= "丰台区".equals(house.getArea()) ? "selected" : "" %>>丰台区</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>户型</label>
|
||||
<select name="houseType">
|
||||
<option value="一室一厅" <%= "一室一厅".equals(house.getHouseType()) ? "selected" : "" %>>一室一厅</option>
|
||||
<option value="两室一厅" <%= "两室一厅".equals(house.getHouseType()) ? "selected" : "" %>>两室一厅</option>
|
||||
<option value="两室两厅" <%= "两室两厅".equals(house.getHouseType()) ? "selected" : "" %>>两室两厅</option>
|
||||
<option value="三室一厅" <%= "三室一厅".equals(house.getHouseType()) ? "selected" : "" %>>三室一厅</option>
|
||||
<option value="三室两厅" <%= "三室两厅".equals(house.getHouseType()) ? "selected" : "" %>>三室两厅</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>租赁类型</label>
|
||||
<select name="rentType">
|
||||
<option value="0" <%= "0".equals(house.getRentType()) ? "selected" : "" %>>整租</option>
|
||||
<option value="1" <%= "1".equals(house.getRentType()) ? "selected" : "" %>>合租</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>详细地址</label>
|
||||
<input type="text" name="address" value="<%= house.getAddress() != null ? house.getAddress() : "" %>">
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<label>月租金(元)*</label>
|
||||
<input type="number" name="rentPrice" value="<%= house.getRentPrice() %>" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>配套设施</label>
|
||||
<input type="text" name="facility" value="<%= house.getFacility() != null ? house.getFacility() : "" %>" placeholder="例如:空调,洗衣机,热水器,WiFi">
|
||||
<div class="hint">多个设施用英文逗号分隔</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>房源图片地址</label>
|
||||
<input type="text" name="imgUrl" value="<%= house.getImgUrl() != null ? house.getImgUrl() : "" %>" placeholder="例如:/images/house1.jpg,/images/house2.jpg">
|
||||
<div class="hint">支持多个图片地址,用英文逗号分隔</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>房源描述</label>
|
||||
<textarea name="description" placeholder="请输入房源描述信息..."><%= house.getDescription() != null ? house.getDescription() : "" %></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary">保存修改</button>
|
||||
<a href="${pageContext.request.contextPath}/landlord/house/list" class="btn btn-secondary">返回</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>© 2026 租房系统 | 让租房更简单</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
369
HRS/WebContent/jsp/landlord/house_list.jsp
Normal file
369
HRS/WebContent/jsp/landlord/house_list.jsp
Normal file
@@ -0,0 +1,369 @@
|
||||
<%@ 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 || !"1".equals(loginUser.getRoleType())) {
|
||||
response.sendRedirect(request.getContextPath() + "/user/login");
|
||||
return;
|
||||
}
|
||||
|
||||
List<House> houseList = (List<House>) request.getAttribute("houseList");
|
||||
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: #ff9800;
|
||||
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: #fb8c00;
|
||||
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: #ff9800;
|
||||
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; }
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.add-btn:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.house-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-published {
|
||||
background-color: #e8f5e9;
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
.status-offline {
|
||||
background-color: #ffebee;
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 4px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background-color: #2196F3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-edit:hover {
|
||||
background-color: #0b7dda;
|
||||
}
|
||||
|
||||
.btn-delete {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-delete:hover {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #333;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.clearfix::after {
|
||||
content: "";
|
||||
clear: both;
|
||||
display: table;
|
||||
}
|
||||
|
||||
.house-image {
|
||||
width: 80px;
|
||||
height: 60px;
|
||||
background-color: #e0e0e0;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.no-image {
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.price {
|
||||
color: #f44336;
|
||||
font-weight: bold;
|
||||
}
|
||||
</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}/landlord/house/list" class="active">房源管理</a>
|
||||
<a href="${pageContext.request.contextPath}/landlord/reservation/list">预约管理</a>
|
||||
<a href="${pageContext.request.contextPath}/landlord/order/list">订单管理</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="breadcrumb">
|
||||
<a href="${pageContext.request.contextPath}/landlord/house/list">首页</a> >
|
||||
<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> 套房源
|
||||
<a href="${pageContext.request.contextPath}/landlord/house/add" class="add-btn" style="float: right;">+ 发布新房源</a>
|
||||
</div>
|
||||
|
||||
<div class="house-table">
|
||||
<%
|
||||
if (houseList != null && !houseList.isEmpty()) {
|
||||
%>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>房源图片</th>
|
||||
<th>房源信息</th>
|
||||
<th>租金</th>
|
||||
<th>状态</th>
|
||||
<th>发布时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%
|
||||
for (House house : houseList) {
|
||||
%>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="house-image"
|
||||
style="background-image: url('${pageContext.request.contextPath}/<%= house.getFirstImage() != null ? house.getFirstImage() : "" %>');">
|
||||
<% if (house.getFirstImage() == null) { %>
|
||||
<div class="no-image">🏠 暂无图片</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<strong><%= house.getTitle() %></strong><br>
|
||||
<span style="font-size:12px;color:#999;"><%= house.getArea() %> | <%= house.getHouseType() %></span>
|
||||
</td>
|
||||
<td class="price">¥<%= house.getRentPrice() %>/月</td>
|
||||
<td>
|
||||
<span class="status-badge status-<%= house.getStatus().equals("0") ? "pending" : house.getStatus().equals("1") ? "published" : "offline" %>">
|
||||
<%= house.getStatusText() %>
|
||||
</span>
|
||||
</td>
|
||||
<td><%= new java.text.SimpleDateFormat("yyyy-MM-dd").format(house.getCreateTime()) %></td>
|
||||
<td>
|
||||
<a href="${pageContext.request.contextPath}/landlord/house/edit?id=<%= house.getId() %>" class="btn btn-edit">编辑</a>
|
||||
<a href="${pageContext.request.contextPath}/landlord/house/delete?id=<%= house.getId() %>"
|
||||
class="btn btn-delete"
|
||||
onclick="return confirm('确定要删除这套房源吗?删除后不可恢复!')">删除</a>
|
||||
</td>
|
||||
</tr>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</tbody>
|
||||
</table>
|
||||
<%
|
||||
} else {
|
||||
%>
|
||||
<div class="empty-state">
|
||||
<div style="font-size: 64px;">🏠</div>
|
||||
<p>还没有发布任何房源</p>
|
||||
<p><a href="${pageContext.request.contextPath}/landlord/house/add">立即发布新房源 >></a></p>
|
||||
</div>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>© 2026 租房系统 | 让租房更简单</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
196
HRS/WebContent/jsp/landlord/index.jsp
Normal file
196
HRS/WebContent/jsp/landlord/index.jsp
Normal file
@@ -0,0 +1,196 @@
|
||||
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
||||
<%@ page import="com.hrs.model.entity.User" %>
|
||||
<%
|
||||
User loginUser = (User) session.getAttribute("loginUser");
|
||||
if (loginUser == null || !"1".equals(loginUser.getRoleType())) {
|
||||
response.sendRedirect(request.getContextPath() + "/user/login");
|
||||
return;
|
||||
}
|
||||
%>
|
||||
<!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: #ff9800;
|
||||
color: white;
|
||||
padding: 15px 0;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.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: #fb8c00;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
float: right;
|
||||
margin-right: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.clearfix::after {
|
||||
content: "";
|
||||
clear: both;
|
||||
display: table;
|
||||
}
|
||||
|
||||
.welcome-card {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
margin-top: 20px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.welcome-card h1 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.welcome-card p {
|
||||
color: #666;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background-color: #f9f9f9;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
width: 200px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: #666;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background-color: #ff9800;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #fb8c00;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #333;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
</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}/landlord/house/list">房源管理</a>
|
||||
<a href="${pageContext.request.contextPath}/landlord/reservation/list">预约管理</a>
|
||||
<a href="${pageContext.request.contextPath}/landlord/order/list">订单管理</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="welcome-card">
|
||||
<h1>欢迎房东,<%= loginUser.getRealName() %>!</h1>
|
||||
<p>欢迎来到租房系统房东后台,您可以管理房源、查看预约、处理订单。</p>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-number" id="houseCount">-</div>
|
||||
<div class="stat-label">我的房源</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number" id="reservationCount">-</div>
|
||||
<div class="stat-label">待处理预约</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 30px;">
|
||||
<a href="${pageContext.request.contextPath}/landlord/house/list" class="btn">管理房源</a>
|
||||
<a href="${pageContext.request.contextPath}/landlord/reservation/list" class="btn">查看预约</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>© 2026 租房系统 | 让租房更简单</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 加载统计数据
|
||||
fetch('${pageContext.request.contextPath}/landlord/statistics')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('houseCount').innerText = data.houseCount || 0;
|
||||
document.getElementById('reservationCount').innerText = data.reservationCount || 0;
|
||||
})
|
||||
.catch(err => console.log('加载统计数据失败', err));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
408
HRS/WebContent/jsp/landlord/order_list.jsp
Normal file
408
HRS/WebContent/jsp/landlord/order_list.jsp
Normal file
@@ -0,0 +1,408 @@
|
||||
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
||||
<%@ page import="java.util.List, java.text.SimpleDateFormat, com.hrs.model.entity.Order, com.hrs.model.entity.House, com.hrs.model.entity.User" %>
|
||||
<%
|
||||
User loginUser = (User) session.getAttribute("loginUser");
|
||||
if (loginUser == null || !"1".equals(loginUser.getRoleType())) {
|
||||
response.sendRedirect(request.getContextPath() + "/user/login");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Order> orderList = (List<Order>) request.getAttribute("orderList");
|
||||
Integer totalCount = (Integer) request.getAttribute("totalCount");
|
||||
String msg = request.getParameter("msg");
|
||||
String error = request.getParameter("error");
|
||||
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
%>
|
||||
<!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: #ff9800;
|
||||
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: #fb8c00;
|
||||
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: #ff9800;
|
||||
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; }
|
||||
}
|
||||
|
||||
.order-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-pay {
|
||||
background-color: #fff3e0;
|
||||
color: #f39c12;
|
||||
}
|
||||
|
||||
.status-paid {
|
||||
background-color: #e8f5e9;
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
.status-cancelled {
|
||||
background-color: #ffebee;
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.status-pending-sign {
|
||||
background-color: #e3f2fd;
|
||||
color: #2196F3;
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
background-color: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.price {
|
||||
color: #f44336;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 5px 15px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-confirm:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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}/landlord/house/list">房源管理</a>
|
||||
<a href="${pageContext.request.contextPath}/landlord/reservation/list">预约管理</a>
|
||||
<a href="${pageContext.request.contextPath}/landlord/order/list" class="active">订单管理</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="breadcrumb">
|
||||
<a href="${pageContext.request.contextPath}/landlord/house/list">首页</a> >
|
||||
<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="order-table">
|
||||
<%
|
||||
if (orderList != null && !orderList.isEmpty()) {
|
||||
%>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>订单编号</th>
|
||||
<th>租客信息</th>
|
||||
<th>房源信息</th>
|
||||
<th>租赁时间</th>
|
||||
<th>总租金</th>
|
||||
<th>支付状态</th>
|
||||
<th>订单状态</th>
|
||||
<th>下单时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%
|
||||
for (Order order : orderList) {
|
||||
House house = order.getHouse();
|
||||
User tenant = order.getTenant();
|
||||
%>
|
||||
<tr>
|
||||
<td style="font-size:12px;"><%= order.getOrderNo() %></td>
|
||||
<td>
|
||||
<%= tenant != null ? (tenant.getRealName() != null ? tenant.getRealName() : tenant.getUserName()) : "未知" %><br>
|
||||
<span style="font-size:12px;color:#999;"><%= tenant != null ? tenant.getPhone() : "" %></span>
|
||||
</td>
|
||||
<td>
|
||||
<strong><%= house.getTitle() %></strong><br>
|
||||
<span style="font-size:12px;color:#999;"><%= house.getArea() %> | <%= house.getHouseType() %></span>
|
||||
</td>
|
||||
<td>
|
||||
<%= sdf.format(order.getRentStartTime()) %> ~ <%= sdf.format(order.getRentEndTime()) %><br>
|
||||
<span style="font-size:12px;color:#999;">共<%= order.getRentDays() %>天</span>
|
||||
</td>
|
||||
<td class="price">¥<%= order.getTotalRent() %></td>
|
||||
<td>
|
||||
<%
|
||||
String payStatusClass = "";
|
||||
String payStatusText = "";
|
||||
if ("0".equals(order.getPayStatus())) {
|
||||
payStatusClass = "pending-pay";
|
||||
payStatusText = "待支付";
|
||||
} else if ("1".equals(order.getPayStatus())) {
|
||||
payStatusClass = "paid";
|
||||
payStatusText = "已支付";
|
||||
} else if ("2".equals(order.getPayStatus())) {
|
||||
payStatusClass = "cancelled";
|
||||
payStatusText = "已退款";
|
||||
} else {
|
||||
payStatusClass = "pending-pay";
|
||||
payStatusText = "未知";
|
||||
}
|
||||
%>
|
||||
<span class="status-badge status-<%= payStatusClass %>"><%= payStatusText %></span>
|
||||
</td>
|
||||
<td>
|
||||
<%
|
||||
String orderStatusClass = "";
|
||||
String orderStatusText = "";
|
||||
if ("0".equals(order.getStatus())) {
|
||||
orderStatusClass = "pending-sign";
|
||||
orderStatusText = "待签约";
|
||||
} else if ("1".equals(order.getStatus())) {
|
||||
orderStatusClass = "paid";
|
||||
orderStatusText = "已签约";
|
||||
} else if ("2".equals(order.getStatus())) {
|
||||
orderStatusClass = "completed";
|
||||
orderStatusText = "已结束";
|
||||
} else if ("3".equals(order.getStatus())) {
|
||||
orderStatusClass = "cancelled";
|
||||
orderStatusText = "已取消";
|
||||
} else {
|
||||
orderStatusClass = "pending-sign";
|
||||
orderStatusText = "未知";
|
||||
}
|
||||
%>
|
||||
<span class="status-badge status-<%= orderStatusClass %>"><%= orderStatusText %></span>
|
||||
</td>
|
||||
<td><%= sdf.format(order.getCreateTime()) %></td>
|
||||
<td>
|
||||
<%
|
||||
// 已支付且待签约状态:显示确认签约按钮
|
||||
if ("1".equals(order.getPayStatus()) && "0".equals(order.getStatus())) {
|
||||
%>
|
||||
<a href="${pageContext.request.contextPath}/landlord/order/confirm?id=<%= order.getId() %>"
|
||||
class="btn btn-confirm" onclick="return confirm('确认与租客签约吗?确认后将生成正式合同。')">确认签约</a>
|
||||
<%
|
||||
} else if ("1".equals(order.getStatus())) {
|
||||
// 已签约
|
||||
%>
|
||||
<span class="btn btn-disabled">已签约</span>
|
||||
<%
|
||||
} else if ("2".equals(order.getStatus())) {
|
||||
// 已结束
|
||||
%>
|
||||
<span class="btn btn-disabled">已完成</span>
|
||||
<%
|
||||
} else if ("3".equals(order.getStatus())) {
|
||||
// 已取消
|
||||
%>
|
||||
<span class="btn btn-disabled">已取消</span>
|
||||
<%
|
||||
} else if ("0".equals(order.getPayStatus())) {
|
||||
// 待支付
|
||||
%>
|
||||
<span class="btn btn-disabled">等待租客支付</span>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</td>
|
||||
</tr>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</tbody>
|
||||
</table>
|
||||
<%
|
||||
} else {
|
||||
%>
|
||||
<div class="empty-state">
|
||||
<div style="font-size: 64px;">📦</div>
|
||||
<p>暂无订单</p>
|
||||
</div>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>© 2026 租房系统 | 让租房更简单</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
324
HRS/WebContent/jsp/landlord/reservation_list.jsp
Normal file
324
HRS/WebContent/jsp/landlord/reservation_list.jsp
Normal file
@@ -0,0 +1,324 @@
|
||||
<%@ 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 || !"1".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: #ff9800;
|
||||
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: #fb8c00;
|
||||
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);
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #ffebee;
|
||||
color: #c62828;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 5px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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}/landlord/house/list">房源管理</a>
|
||||
<a href="${pageContext.request.contextPath}/landlord/reservation/list" class="active">预约管理</a>
|
||||
<a href="${pageContext.request.contextPath}/landlord/order/list">订单管理</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="breadcrumb">
|
||||
<a href="${pageContext.request.contextPath}/landlord/house/list">首页</a> >
|
||||
<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>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%
|
||||
for (Reservation reservation : reservationList) {
|
||||
House house = reservation.getHouse();
|
||||
User tenant = reservation.getTenant();
|
||||
%>
|
||||
<tr>
|
||||
<td>
|
||||
<%= tenant.getRealName() != null ? tenant.getRealName() : tenant.getUserName() %><br>
|
||||
<span style="font-size:12px;color:#999;"><%= tenant.getPhone() %></span>
|
||||
</td>
|
||||
<td>
|
||||
<strong><%= house.getTitle() %></strong><br>
|
||||
<span style="font-size:12px;color:#999;"><%= house.getArea() %></span>
|
||||
</td>
|
||||
<td><%= sdf.format(reservation.getReserveTime()) %></td>
|
||||
<td>
|
||||
<span class="status-badge status-<%= reservation.getStatus().equals("0") ? "pending" : "confirmed" %>">
|
||||
<%= reservation.getStatusText() %>
|
||||
</span>
|
||||
</td>
|
||||
<td style="max-width:150px;">
|
||||
<%= reservation.getRemark() != null ? reservation.getRemark() : "-" %>
|
||||
</td>
|
||||
<td>
|
||||
<% if ("0".equals(reservation.getStatus())) { %>
|
||||
<a href="${pageContext.request.contextPath}/landlord/reservation/handle?id=<%= reservation.getId() %>&action=confirm"
|
||||
class="btn btn-success">确认</a>
|
||||
<a href="${pageContext.request.contextPath}/landlord/reservation/handle?id=<%= reservation.getId() %>&action=reject"
|
||||
class="btn btn-danger">拒绝</a>
|
||||
<% } else { %>
|
||||
<span class="btn-disabled">已处理</span>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</tbody>
|
||||
</table>
|
||||
<%
|
||||
} else {
|
||||
%>
|
||||
<div class="empty-state">
|
||||
<div style="font-size: 64px;">📅</div>
|
||||
<p>暂无预约记录</p>
|
||||
</div>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>© 2026 租房系统 | 让租房更简单</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
418
HRS/WebContent/jsp/user/collection_list.jsp
Normal file
418
HRS/WebContent/jsp/user/collection_list.jsp
Normal 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> >
|
||||
<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('/HRS/<%= 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">去浏览房源 >></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>
|
||||
619
HRS/WebContent/jsp/user/house_detail.jsp
Normal file
619
HRS/WebContent/jsp/user/house_detail.jsp
Normal file
@@ -0,0 +1,619 @@
|
||||
<%@ 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;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.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> >
|
||||
<span><%= house.getTitle() %></span>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<!-- 图片区域 -->
|
||||
<div class="house-images">
|
||||
<!-- 主图 -->
|
||||
<div class="main-image" id="mainImage"
|
||||
style="background-image: url('${pageContext.request.contextPath}/<%= 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('${pageContext.request.contextPath}/<%= images[i] %>');"
|
||||
onclick="changeImage('${pageContext.request.contextPath}/<%= 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">
|
||||
<a href="${pageContext.request.contextPath}/user/order/create?houseId=<%= house.getId() %>"
|
||||
class="btn btn-primary">💰 立即下单</a>
|
||||
<button class="btn btn-secondary" onclick="makeReservation()">📅 预约看房</button>
|
||||
<%
|
||||
com.hrs.service.CollectionService collectionService = new com.hrs.service.impl.CollectionServiceImpl();
|
||||
boolean isCollected = collectionService.isCollected(loginUser.getId(), house.getId());
|
||||
if (isCollected) {
|
||||
%>
|
||||
<button class="btn btn-outline" onclick="cancelCollection(<%= house.getId() %>)">❤️ 已收藏</button>
|
||||
<%
|
||||
} else {
|
||||
%>
|
||||
<button class="btn btn-outline" 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;
|
||||
}
|
||||
|
||||
// 收藏房源
|
||||
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('确定要取消收藏这套房源吗?')) {
|
||||
// 发送AJAX请求取消收藏
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '${pageContext.request.contextPath}/user/collection/delete?houseId=' + houseId, true);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 4 && xhr.status == 200) {
|
||||
showMessage('取消收藏成功!', 'success');
|
||||
// 刷新页面更新按钮状态
|
||||
setTimeout(function() {
|
||||
location.reload();
|
||||
}, 1000);
|
||||
} else if (xhr.readyState == 4) {
|
||||
showMessage('取消收藏失败,请稍后重试', 'error');
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
}
|
||||
|
||||
// 显示提示消息
|
||||
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>
|
||||
412
HRS/WebContent/jsp/user/house_list.jsp
Normal file
412
HRS/WebContent/jsp/user/house_list.jsp
Normal 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="${pageContext.request.contextPath}/user/reservation/list">我的预约</a>
|
||||
<a href="${pageContext.request.contextPath}/user/order/list">我的订单</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('/HRS/<%= 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>
|
||||
5
HRS/WebContent/jsp/user/index.jsp
Normal file
5
HRS/WebContent/jsp/user/index.jsp
Normal file
@@ -0,0 +1,5 @@
|
||||
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
||||
<%
|
||||
// 重定向到房源列表页
|
||||
response.sendRedirect(request.getContextPath() + "/user/house/list");
|
||||
%>
|
||||
151
HRS/WebContent/jsp/user/login.jsp
Normal file
151
HRS/WebContent/jsp/user/login.jsp
Normal 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>
|
||||
366
HRS/WebContent/jsp/user/order_create.jsp
Normal file
366
HRS/WebContent/jsp/user/order_create.jsp
Normal file
@@ -0,0 +1,366 @@
|
||||
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
||||
<%@ page import="com.hrs.model.entity.House, com.hrs.model.entity.User, java.text.SimpleDateFormat, java.util.Date" %>
|
||||
<%
|
||||
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");
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
String today = sdf.format(new Date());
|
||||
%>
|
||||
<!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: 1000px;
|
||||
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);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.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: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
input[type="date"] {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.price-preview {
|
||||
background-color: #fff3e0;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.total-price {
|
||||
font-size: 28px;
|
||||
color: #f44336;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.row .form-group {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.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> >
|
||||
<a href="${pageContext.request.contextPath}/user/house/detail?id=<%= house.getId() %>">房源详情</a> >
|
||||
<span>创建订单</span>
|
||||
</div>
|
||||
|
||||
<div class="main-card">
|
||||
<h2>📝 创建租赁订单</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/order/create" method="post" id="orderForm">
|
||||
<input type="hidden" name="houseId" value="<%= house.getId() %>">
|
||||
|
||||
<div class="row">
|
||||
<div class="form-group">
|
||||
<label>租赁开始日期 *</label>
|
||||
<input type="date" name="startDate" id="startDate" required min="<%= today %>" onchange="calculateTotal()">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>租赁结束日期 *</label>
|
||||
<input type="date" name="endDate" id="endDate" required min="<%= today %>" onchange="calculateTotal()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="price-preview">
|
||||
<div style="margin-bottom: 10px;">预计总租金</div>
|
||||
<div class="total-price" id="totalPrice">¥0.00</div>
|
||||
<div class="hint">* 租金按月租比例计算,实际金额以合同为准</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary" id="submitBtn" disabled>确认下单</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 monthlyRent = <%= house.getRentPrice() %>;
|
||||
|
||||
function calculateTotal() {
|
||||
var startDate = document.getElementById('startDate').value;
|
||||
var endDate = document.getElementById('endDate').value;
|
||||
var submitBtn = document.getElementById('submitBtn');
|
||||
var totalPriceSpan = document.getElementById('totalPrice');
|
||||
|
||||
if (startDate && endDate) {
|
||||
var start = new Date(startDate);
|
||||
var end = new Date(endDate);
|
||||
|
||||
if (end < start) {
|
||||
totalPriceSpan.innerHTML = '¥0.00';
|
||||
submitBtn.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算天数
|
||||
var diffTime = Math.abs(end - start);
|
||||
var diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
// 计算租金(按比例)
|
||||
var total = (monthlyRent / 30) * diffDays;
|
||||
totalPriceSpan.innerHTML = '¥' + total.toFixed(2);
|
||||
submitBtn.disabled = false;
|
||||
} else {
|
||||
totalPriceSpan.innerHTML = '¥0.00';
|
||||
submitBtn.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置结束日期最小值跟随开始日期
|
||||
document.getElementById('startDate').addEventListener('change', function() {
|
||||
var endDateInput = document.getElementById('endDate');
|
||||
endDateInput.min = this.value;
|
||||
if (endDateInput.value && endDateInput.value < this.value) {
|
||||
endDateInput.value = '';
|
||||
}
|
||||
calculateTotal();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
435
HRS/WebContent/jsp/user/order_list.jsp
Normal file
435
HRS/WebContent/jsp/user/order_list.jsp
Normal file
@@ -0,0 +1,435 @@
|
||||
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
||||
<%@ page import="java.util.List, java.text.SimpleDateFormat, com.hrs.model.entity.Order, 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<Order> orderList = (List<Order>) request.getAttribute("orderList");
|
||||
Integer totalCount = (Integer) request.getAttribute("totalCount");
|
||||
String msg = request.getParameter("msg");
|
||||
String error = request.getParameter("error");
|
||||
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
||||
%>
|
||||
<!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; }
|
||||
}
|
||||
|
||||
.order-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-pay {
|
||||
background-color: #fff3e0;
|
||||
color: #f39c12;
|
||||
}
|
||||
|
||||
.status-paid {
|
||||
background-color: #e8f5e9;
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
.status-cancelled {
|
||||
background-color: #ffebee;
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.status-pending-sign {
|
||||
background-color: #e3f2fd;
|
||||
color: #2196F3;
|
||||
}
|
||||
|
||||
.status-completed {
|
||||
background-color: #e8f5e9;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.price {
|
||||
color: #f44336;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 5px 15px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin: 0 3px;
|
||||
}
|
||||
|
||||
.btn-pay {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-pay:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
|
||||
.btn-disabled {
|
||||
background-color: #ccc;
|
||||
color: #666;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.house-link {
|
||||
color: #4CAF50;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.house-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.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">我的预约</a>
|
||||
<a href="${pageContext.request.contextPath}/user/order/list" class="active">我的订单</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="breadcrumb">
|
||||
<a href="${pageContext.request.contextPath}/user/house/list">首页</a> >
|
||||
<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="order-table">
|
||||
<%
|
||||
if (orderList != null && !orderList.isEmpty()) {
|
||||
%>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>订单编号</th>
|
||||
<th>房源信息</th>
|
||||
<th>租赁时间</th>
|
||||
<th>总租金</th>
|
||||
<th>支付状态</th>
|
||||
<th>订单状态</th>
|
||||
<th>下单时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%
|
||||
for (Order order : orderList) {
|
||||
House house = order.getHouse();
|
||||
%>
|
||||
<tr>
|
||||
<td style="font-size:12px;"><%= order.getOrderNo() %></td>
|
||||
<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.getHouseType() %></span>
|
||||
</td>
|
||||
<td>
|
||||
<%= sdf.format(order.getRentStartTime()) %> ~ <%= sdf.format(order.getRentEndTime()) %><br>
|
||||
<span style="font-size:12px;color:#999;">共<%= order.getRentDays() %>天</span>
|
||||
</td>
|
||||
<td class="price">¥<%= order.getTotalRent() %></td>
|
||||
<td>
|
||||
<%
|
||||
String payStatusClass = "";
|
||||
String payStatusText = "";
|
||||
if ("0".equals(order.getPayStatus())) {
|
||||
payStatusClass = "pending-pay";
|
||||
payStatusText = "待支付";
|
||||
} else if ("1".equals(order.getPayStatus())) {
|
||||
payStatusClass = "paid";
|
||||
payStatusText = "已支付";
|
||||
} else if ("2".equals(order.getPayStatus())) {
|
||||
payStatusClass = "cancelled";
|
||||
payStatusText = "已退款";
|
||||
} else {
|
||||
payStatusClass = "pending-pay";
|
||||
payStatusText = "未知";
|
||||
}
|
||||
%>
|
||||
<span class="status-badge status-<%= payStatusClass %>"><%= payStatusText %></span>
|
||||
</td>
|
||||
<td>
|
||||
<%
|
||||
String orderStatusClass = "";
|
||||
String orderStatusText = "";
|
||||
if ("0".equals(order.getStatus())) {
|
||||
orderStatusClass = "pending-sign";
|
||||
orderStatusText = "待签约";
|
||||
} else if ("1".equals(order.getStatus())) {
|
||||
orderStatusClass = "paid";
|
||||
orderStatusText = "已签约";
|
||||
} else if ("2".equals(order.getStatus())) {
|
||||
orderStatusClass = "completed";
|
||||
orderStatusText = "已结束";
|
||||
} else if ("3".equals(order.getStatus())) {
|
||||
orderStatusClass = "cancelled";
|
||||
orderStatusText = "已取消";
|
||||
} else {
|
||||
orderStatusClass = "pending-sign";
|
||||
orderStatusText = "未知";
|
||||
}
|
||||
%>
|
||||
<span class="status-badge status-<%= orderStatusClass %>"><%= orderStatusText %></span>
|
||||
</td>
|
||||
<td><%= sdf.format(order.getCreateTime()) %></td>
|
||||
<td>
|
||||
<%
|
||||
// 待支付状态:显示支付和取消按钮
|
||||
if ("0".equals(order.getPayStatus()) && "0".equals(order.getStatus())) {
|
||||
%>
|
||||
<a href="${pageContext.request.contextPath}/user/order/pay?id=<%= order.getId() %>"
|
||||
class="btn btn-pay" onclick="return confirm('确认支付 ¥<%= order.getTotalRent() %> 吗?')">立即支付</a>
|
||||
<a href="${pageContext.request.contextPath}/user/order/cancel?id=<%= order.getId() %>"
|
||||
class="btn btn-cancel" onclick="return confirm('确定要取消该订单吗?')">取消订单</a>
|
||||
<%
|
||||
} else if ("1".equals(order.getPayStatus()) && "0".equals(order.getStatus())) {
|
||||
// 已支付待签约状态
|
||||
%>
|
||||
<span class="btn btn-disabled">等待房东确认</span>
|
||||
<%
|
||||
} else if ("1".equals(order.getStatus())) {
|
||||
// 已签约
|
||||
%>
|
||||
<span class="btn btn-disabled">已签约</span>
|
||||
<%
|
||||
} else if ("2".equals(order.getStatus())) {
|
||||
// 已结束
|
||||
%>
|
||||
<span class="btn btn-disabled">已完成</span>
|
||||
<%
|
||||
} else if ("3".equals(order.getStatus())) {
|
||||
// 已取消
|
||||
%>
|
||||
<span class="btn btn-disabled">已取消</span>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</td>
|
||||
</tr>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</tbody>
|
||||
20
|
||||
<%
|
||||
} else {
|
||||
%>
|
||||
<div class="empty-state">
|
||||
<div style="font-size: 64px;">📦</div>
|
||||
<p>还没有任何订单</p>
|
||||
<p><a href="${pageContext.request.contextPath}/user/house/list">去浏览房源 >></a></p>
|
||||
</div>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>© 2026 租房系统 | 让租房更简单</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
218
HRS/WebContent/jsp/user/register.jsp
Normal file
218
HRS/WebContent/jsp/user/register.jsp
Normal 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>
|
||||
313
HRS/WebContent/jsp/user/reservation_add.jsp
Normal file
313
HRS/WebContent/jsp/user/reservation_add.jsp
Normal 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> >
|
||||
<a href="${pageContext.request.contextPath}/user/house/detail?id=<%= house.getId() %>">房源详情</a> >
|
||||
<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('/HRS/<%= 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>
|
||||
376
HRS/WebContent/jsp/user/reservation_list.jsp
Normal file
376
HRS/WebContent/jsp/user/reservation_list.jsp
Normal file
@@ -0,0 +1,376 @@
|
||||
<%@ 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> >
|
||||
<span>我的预约</span>
|
||||
</div>
|
||||
|
||||
<% if (msg != null && !msg.isEmpty()) { %>
|
||||
<div class="message"><%= msg %></div>
|
||||
<% } %>
|
||||
<% if (error != null && !error.isEmpty()) { %>
|
||||
<div class="error-message"><%= error %></div>
|
||||
<% } %>
|
||||
|
||||
<div class="stats">
|
||||
📋 共有 <strong><%= totalCount != null ? totalCount : 0 %></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();
|
||||
if (house == null) continue;
|
||||
|
||||
// 安全获取状态值,避免空指针
|
||||
String status = reservation.getStatus();
|
||||
String statusClass = "";
|
||||
String statusText = "";
|
||||
if ("0".equals(status)) {
|
||||
statusClass = "pending";
|
||||
statusText = "待确认";
|
||||
} else if ("1".equals(status)) {
|
||||
statusClass = "confirmed";
|
||||
statusText = "已确认";
|
||||
} else if ("2".equals(status)) {
|
||||
statusClass = "cancelled";
|
||||
statusText = "已取消";
|
||||
} else if ("3".equals(status)) {
|
||||
statusClass = "completed";
|
||||
statusText = "已完成";
|
||||
} else {
|
||||
statusClass = "pending";
|
||||
statusText = "未知";
|
||||
}
|
||||
|
||||
String remark = reservation.getRemark();
|
||||
String remarkDisplay = (remark != null && !remark.trim().isEmpty()) ? remark : "-";
|
||||
|
||||
java.util.Date reserveTime = reservation.getReserveTime();
|
||||
String reserveTimeStr = reserveTime != null ? sdf.format(reserveTime) : "-";
|
||||
%>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="${pageContext.request.contextPath}/user/house/detail?id=<%= house.getId() %>" class="house-link">
|
||||
<strong><%= house.getTitle() != null ? house.getTitle() : "未知房源" %></strong>
|
||||
</a><br>
|
||||
<span style="font-size:12px;color:#999;">
|
||||
<%= house.getArea() != null ? house.getArea() : "" %>
|
||||
<%= house.getRentPrice() != null ? "| ¥" + house.getRentPrice() + "/月" : "" %>
|
||||
</span>
|
||||
</td>
|
||||
<td><%= reserveTimeStr %></td>
|
||||
<td>
|
||||
<span class="status-badge status-<%= statusClass %>"><%= statusText %></span>
|
||||
</td>
|
||||
<td style="max-width:200px; overflow:hidden; text-overflow:ellipsis;">
|
||||
<%= remarkDisplay %>
|
||||
</td>
|
||||
<td>
|
||||
<% if ("0".equals(status)) { %>
|
||||
<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">去浏览房源 >></a></p>
|
||||
</div>
|
||||
<%
|
||||
}
|
||||
%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>© 2026 租房系统 | 让租房更简单</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
BIN
HRS/WebContent/upload/house/2026-03-31163207.png
Normal file
BIN
HRS/WebContent/upload/house/2026-03-31163207.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 696 KiB |
Reference in New Issue
Block a user