Thiết Kế Giao Diện Tìm Kiếm Đa Tiêu Chí
Hệ thống được phát triển bằng JSP với giao diện hỗ trợ tìm kiếm theo nhiều tiêu chí: tên chính sách, số văn bản, cơ quan ban hành và nội dung toàn văn. Mẫu hình thức sau đây sử dụng các trường nhập liệu được tối ưu hóa cho trải nghiệm người dùng:
<form method="post" action="policy-search">
<div class="search-group">
<label>Tên chính sách:</label>
<input type="text" name="policyTitle" value="${titleValue}" placeholder="Nhập tên chính sách">
<select name="titleMatch">
<option value="exact">Chính xác</option>
<option value="fuzzy" selected>Mơ hồ</option>
</select>
</div>
<div class="search-group">
<label>Số hiệu văn bản:</label>
<input type="text" name="docNumber" value="${docValue}" placeholder="Nhập số hiệu">
<select name="docMatch">
<option value="exact">Chính xác</option>
<option value="fuzzy" selected>Mơ hồ</option>
</select>
</div>
<button type="submit" class="search-btn">Tìm kiếm</button>
</form>
Cơ Chế Phân Trang Thông Minh
Triển khai phân trang hiệu quả với tính toán động dựa trên tổng số bản ghi. Đoạn mã sau xử lý logic phân trang và hiển thị kết quả:
int itemsPerPage = 10;
int currentPage = 1;
String pageParam = request.getParameter("page");
if (pageParam != null && !pageParam.isEmpty()) {
try {
currentPage = Integer.parseInt(pageParam);
if (currentPage < 1) currentPage = 1;
} catch (NumberFormatException e) {
currentPage = 1;
}
}
// Tính toán vị trí bắt đầu
int offset = (currentPage - 1) * itemsPerPage;
// Truy vấn cơ sở dữ liệu với LIMIT và OFFSET
String query = "SELECT id, title, agency, publish_date, category " +
"FROM policies " +
"ORDER BY publish_date DESC " +
"LIMIT ? OFFSET ?";
try (PreparedStatement stmt = conn.prepareStatement(query)) {
stmt.setInt(1, itemsPerPage);
stmt.setInt(2, offset);
ResultSet results = stmt.executeQuery();
while (results.next()) {
out.println("<tr>");
out.println("<td><a href='detail?id=" +
results.getInt("id") + "'>" +
results.getString("title") + "</a></td>");
// Hiển thị các cột khác...
out.println("</tr>");
}
}
Xử Lý Tìm Kiếm Thông Minh
Servlet xử lý yêu cầu tìm kiếm với cơ chế xây dựng truy vấn động an toàn, tránh lỗ hổng SQL Injection:
@WebServlet("/policy-search")
public class PolicySearchServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse res) {
String title = req.getParameter("policyTitle");
String docNum = req.getParameter("docNumber");
List<Policy> results = new ArrayList<>();
StringBuilder query = new StringBuilder("SELECT * FROM policies WHERE 1=1");
List<Object> params = new ArrayList<>();
if (title != null && !title.trim().isEmpty()) {
query.append(" AND title LIKE ?");
params.add("%" + title + "%");
}
if (docNum != null && !docNum.trim().isEmpty()) {
query.append(" AND document_number LIKE ?");
params.add("%" + docNum + "%");
}
try (Connection conn = Database.getConnection();
PreparedStatement stmt = conn.prepareStatement(query.toString())) {
for (int i = 0; i < params.size(); i++) {
stmt.setString(i + 1, (String) params.get(i));
}
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
results.add(new Policy(
rs.getInt("id"),
rs.getString("title"),
rs.getString("agency"),
rs.getDate("publish_date")
));
}
req.setAttribute("searchResults", results);
req.getRequestDispatcher("search-results.jsp").forward(req, res);
} catch (SQLException e) {
log("Database error", e);
req.setAttribute("error", "Lỗi truy vấn cơ sở dữ liệu");
}
}
}
Tối Ưu Hiển Thị Với CSS Động
CSS được thiết kế đáp ứng cho mọi kích thước màn hình, tập trung vào trải nghiệm người dùng:
.search-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.search-group {
display: flex;
gap: 10px;
margin-bottom: 15px;
align-items: center;
}
input[type="text"] {
flex: 1;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.results-table {
width: 100%;
border-collapse: collapse;
margin: 25px 0;
}
.results-table th {
background-color: #2c3e50;
color: white;
padding: 15px;
}
.results-table td {
padding: 12px 15px;
border-bottom: 1px solid #e0e0e0;
}
.pagination {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 20px;
}