This commit is contained in:
Zzzz
2026-04-28 10:53:09 +08:00
parent 5dc91a4e8e
commit ff044e6aab
58 changed files with 793 additions and 635 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ project_doc_fallback_filenames = ["AGENTS.md"]
# Without this flag, hooks.json is ignored and Trellis context won't # Without this flag, hooks.json is ignored and Trellis context won't
# be injected into Codex sessions. # be injected into Codex sessions.
sandbox_mode = "workspace-write" sandbox_mode = "danger-full-access"
[sandbox_workspace_write] [sandbox_workspace_write]
network_access = true network_access = true
+7
View File
@@ -52,3 +52,10 @@ DAOs report database failures without leaking SQL details to JSP pages.
Use concise messages suitable for JSP rendering. For protected operations, Use concise messages suitable for JSP rendering. For protected operations,
prefer generic denial messages over exposing permission internals. prefer generic denial messages over exposing permission internals.
For this application, messages rendered into JSP pages should be Simplified
Chinese. This includes `ServiceResult.message`, field-level validation errors,
flash messages set by Servlet controllers, and display names returned by entity
helpers. Keep log-only diagnostics, exception types, stored enum codes, request
parameter names, and database values unchanged unless a separate contract change
requires it.
@@ -23,6 +23,19 @@ the reusable UI units.
--- ---
## Interface Copy
- Render user-visible JSP copy in Simplified Chinese, including navigation,
headings, form labels, buttons, table headers, empty states, and accessible
labels.
- Keep machine-readable values unchanged: URLs, request parameter names, CSS
classes, Java identifiers, enum codes, database values, and servlet names stay
in their existing code form.
- Translate display helper output and controller/service messages when they are
rendered into JSP pages.
---
## Forms ## Forms
- Forms should post to Servlet controller endpoints, not directly to DAOs or - Forms should post to Servlet controller endpoints, not directly to DAOs or
@@ -0,0 +1,6 @@
{"file": ".trellis/spec/frontend/index.md", "reason": "Check translated UI against frontend conventions."}
{"file": ".trellis/spec/frontend/component-guidelines.md", "reason": "Verify forms, tables, fragments, and UI copy remain presentation-focused."}
{"file": ".trellis/spec/frontend/type-safety.md", "reason": "Verify JSP/Servlet display contracts and escaping were preserved."}
{"file": ".trellis/spec/frontend/quality-guidelines.md", "reason": "Verify accessibility labels, layout preservation, and JSP/CSS quality."}
{"file": ".trellis/spec/backend/error-handling.md", "reason": "Review translated backend messages displayed in the UI."}
{"file": ".trellis/spec/backend/quality-guidelines.md", "reason": "Review backend layer boundaries for message-only changes."}
@@ -0,0 +1,7 @@
{"file": ".trellis/spec/frontend/index.md", "reason": "Frontend JSP/CSS conventions and pre-development checklist for UI changes."}
{"file": ".trellis/spec/frontend/directory-structure.md", "reason": "Confirms JSP/static asset layout and forbidden SPA conventions."}
{"file": ".trellis/spec/frontend/component-guidelines.md", "reason": "JSP fragments, forms, tables, and reusable UI conventions affected by translation."}
{"file": ".trellis/spec/frontend/type-safety.md", "reason": "JSP/Servlet display contracts and safe rendering guidance while changing visible text."}
{"file": ".trellis/spec/frontend/quality-guidelines.md", "reason": "Frontend quality bar for preserving JSP/CSS behavior and accessibility basics."}
{"file": ".trellis/spec/backend/error-handling.md", "reason": "Server-generated UI messages must remain safe and user-facing."}
{"file": ".trellis/spec/backend/quality-guidelines.md", "reason": "Layer boundary constraints for any controller/service message changes."}
@@ -0,0 +1,61 @@
# brainstorm: Frontend Chinese UI
## Goal
Make the existing JSP/Servlet frontend interface display in Simplified Chinese so users see Chinese page titles, navigation, labels, action buttons, empty states, accessibility labels, and server-rendered feedback messages.
## What I already know
* The user requested: "前端界面需要中文".
* The project uses JSP/CSS rendered by Servlet/Tomcat, not a SPA framework.
* Visible English text is concentrated in `src/main/webapp/WEB-INF/jsp/**`.
* Some user-facing messages are set by Java controllers/services and rendered by JSP pages.
* The application already uses UTF-8 JSP page encoding and a UTF-8 character encoding filter.
## Assumptions
* Use Simplified Chinese for all user-visible frontend copy.
* Keep URLs, form field names, Java identifiers, enum codes, database values, CSS class names, and servlet names unchanged.
* Translate dynamic display names exposed by Java enums/role helpers where they are shown in the UI.
* Do not add a full i18n framework in this task.
## Open Questions
* None blocking; proceed with the Simplified Chinese assumption.
## Requirements
* Translate all hardcoded visible English text in JSP pages to Simplified Chinese.
* Change HTML language attributes from English to Simplified Chinese where applicable.
* Translate visible document titles and the web app display name.
* Translate server-generated messages that are displayed to users on the frontend.
* Preserve existing page structure, routing, permissions, form submission behavior, JSTL/EL bindings, and backend workflows.
* Leave stored data and machine-readable codes unchanged.
## Acceptance Criteria
* [x] Navigation, page headings, labels, buttons, table headers, empty states, option labels, and accessible labels appear in Simplified Chinese.
* [x] Login, unauthorized, dashboard, role home, catalog, management, reader, borrowing, report, system log, and admin user pages no longer show English UI copy except product/brand names and technical/user data.
* [x] Server-rendered success/error messages shown in the UI are Simplified Chinese.
* [x] Maven build succeeds.
## Definition of Done
* Tests or build verification run where practical.
* Lint/typecheck/build green for the Java webapp.
* Spec update considered after implementation.
## Out of Scope
* Runtime language switching.
* Browser locale detection.
* New translation resource bundles.
* Database data migration.
* Visual redesign beyond any minor layout-preserving text fit adjustments.
## Technical Notes
* Relevant spec index: `.trellis/spec/frontend/index.md`.
* Backend messages may require `.trellis/spec/backend/error-handling.md` and `.trellis/spec/backend/quality-guidelines.md`.
* Likely affected frontend files include `src/main/webapp/WEB-INF/jsp/**`, `src/main/webapp/WEB-INF/web.xml`, and possibly `src/main/webapp/static/images/library-login.svg` for accessible text.
* Likely affected Java files include controllers/services/entities that expose user-facing display names or request messages.
@@ -0,0 +1,26 @@
{
"id": "frontend-chinese-ui",
"name": "frontend-chinese-ui",
"title": "brainstorm: 前端界面中文化",
"description": "",
"status": "in_progress",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "Zzzz",
"assignee": "Zzzz",
"createdAt": "2026-04-28",
"completedAt": null,
"branch": null,
"base_branch": "master",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}
@@ -44,7 +44,7 @@ public class BookManagementServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String path = request.getServletPath(); String path = request.getServletPath();
if ("/books/new".equals(path)) { if ("/books/new".equals(path)) {
renderForm(request, response, "Create book", "/books", new Book(), Collections.emptyMap(), renderForm(request, response, "创建图书", "/books", new Book(), Collections.emptyMap(),
Collections.emptyMap(), null); Collections.emptyMap(), null);
return; return;
} }
@@ -57,7 +57,7 @@ public class BookManagementServlet extends HttpServlet {
return; return;
} }
if ("/book-categories/new".equals(path)) { if ("/book-categories/new".equals(path)) {
renderCategoryForm(request, response, "Create category", "/book-categories", new BookCategory(), renderCategoryForm(request, response, "创建分类", "/book-categories", new BookCategory(),
Collections.emptyMap(), Collections.emptyMap(), null); Collections.emptyMap(), Collections.emptyMap(), null);
return; return;
} }
@@ -134,12 +134,12 @@ public class BookManagementServlet extends HttpServlet {
long id = requiredLong(request.getParameter("id"), -1L); long id = requiredLong(request.getParameter("id"), -1L);
ServiceResult<Optional<Book>> result = bookService.findBook(id); ServiceResult<Optional<Book>> result = bookService.findBook(id);
if (!result.isSuccessful() || !result.getData().isPresent()) { if (!result.isSuccessful() || !result.getData().isPresent()) {
flashError(request, result.isSuccessful() ? "Book was not found." : result.getMessage()); flashError(request, result.isSuccessful() ? "未找到图书。" : result.getMessage());
response.sendRedirect(request.getContextPath() + "/books"); response.sendRedirect(request.getContextPath() + "/books");
return; return;
} }
renderForm(request, response, "Edit book", "/books/update", result.getData().get(), renderForm(request, response, "编辑图书", "/books/update", result.getData().get(),
Collections.emptyMap(), Collections.emptyMap(), null); Collections.emptyMap(), Collections.emptyMap(), null);
} }
@@ -160,26 +160,26 @@ public class BookManagementServlet extends HttpServlet {
long id = requiredLong(request.getParameter("id"), -1L); long id = requiredLong(request.getParameter("id"), -1L);
ServiceResult<Optional<BookCategory>> result = bookService.findCategory(id); ServiceResult<Optional<BookCategory>> result = bookService.findCategory(id);
if (!result.isSuccessful() || !result.getData().isPresent()) { if (!result.isSuccessful() || !result.getData().isPresent()) {
flashError(request, result.isSuccessful() ? "Category was not found." : result.getMessage()); flashError(request, result.isSuccessful() ? "未找到分类。" : result.getMessage());
response.sendRedirect(request.getContextPath() + "/book-categories"); response.sendRedirect(request.getContextPath() + "/book-categories");
return; return;
} }
renderCategoryForm(request, response, "Edit category", "/book-categories/update", result.getData().get(), renderCategoryForm(request, response, "编辑分类", "/book-categories/update", result.getData().get(),
Collections.emptyMap(), Collections.emptyMap(), null); Collections.emptyMap(), Collections.emptyMap(), null);
} }
private void createBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { private void createBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
BookForm form = readBookForm(request, false); BookForm form = readBookForm(request, false);
if (!form.getErrors().isEmpty()) { if (!form.getErrors().isEmpty()) {
renderForm(request, response, "Create book", "/books", form.getBook(), form.getValues(), renderForm(request, response, "创建图书", "/books", form.getBook(), form.getValues(),
form.getErrors(), "Please correct the highlighted book fields."); form.getErrors(), "请修正高亮的图书字段。");
return; return;
} }
ServiceResult<Long> result = bookService.createBook(currentUser(request), form.getBook()); ServiceResult<Long> result = bookService.createBook(currentUser(request), form.getBook());
if (!result.isSuccessful()) { if (!result.isSuccessful()) {
handleFormFailure(request, response, "Create book", "/books", form, result); handleFormFailure(request, response, "创建图书", "/books", form, result);
return; return;
} }
@@ -190,14 +190,14 @@ public class BookManagementServlet extends HttpServlet {
private void updateBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { private void updateBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
BookForm form = readBookForm(request, true); BookForm form = readBookForm(request, true);
if (!form.getErrors().isEmpty()) { if (!form.getErrors().isEmpty()) {
renderForm(request, response, "Edit book", "/books/update", form.getBook(), form.getValues(), renderForm(request, response, "编辑图书", "/books/update", form.getBook(), form.getValues(),
form.getErrors(), "Please correct the highlighted book fields."); form.getErrors(), "请修正高亮的图书字段。");
return; return;
} }
ServiceResult<Void> result = bookService.updateBook(currentUser(request), form.getBook()); ServiceResult<Void> result = bookService.updateBook(currentUser(request), form.getBook());
if (!result.isSuccessful()) { if (!result.isSuccessful()) {
handleFormFailure(request, response, "Edit book", "/books/update", form, result); handleFormFailure(request, response, "编辑图书", "/books/update", form, result);
return; return;
} }
@@ -224,14 +224,14 @@ public class BookManagementServlet extends HttpServlet {
throws ServletException, IOException { throws ServletException, IOException {
CategoryForm form = readCategoryForm(request, false); CategoryForm form = readCategoryForm(request, false);
if (!form.getErrors().isEmpty()) { if (!form.getErrors().isEmpty()) {
renderCategoryForm(request, response, "Create category", "/book-categories", form.getCategory(), renderCategoryForm(request, response, "创建分类", "/book-categories", form.getCategory(),
form.getValues(), form.getErrors(), "Please correct the highlighted category fields."); form.getValues(), form.getErrors(), "请修正高亮的分类字段。");
return; return;
} }
ServiceResult<Long> result = bookService.createCategory(currentUser(request), form.getCategory()); ServiceResult<Long> result = bookService.createCategory(currentUser(request), form.getCategory());
if (!result.isSuccessful()) { if (!result.isSuccessful()) {
handleCategoryFormFailure(request, response, "Create category", "/book-categories", form, result); handleCategoryFormFailure(request, response, "创建分类", "/book-categories", form, result);
return; return;
} }
@@ -243,14 +243,14 @@ public class BookManagementServlet extends HttpServlet {
throws ServletException, IOException { throws ServletException, IOException {
CategoryForm form = readCategoryForm(request, true); CategoryForm form = readCategoryForm(request, true);
if (!form.getErrors().isEmpty()) { if (!form.getErrors().isEmpty()) {
renderCategoryForm(request, response, "Edit category", "/book-categories/update", form.getCategory(), renderCategoryForm(request, response, "编辑分类", "/book-categories/update", form.getCategory(),
form.getValues(), form.getErrors(), "Please correct the highlighted category fields."); form.getValues(), form.getErrors(), "请修正高亮的分类字段。");
return; return;
} }
ServiceResult<Void> result = bookService.updateCategory(currentUser(request), form.getCategory()); ServiceResult<Void> result = bookService.updateCategory(currentUser(request), form.getCategory());
if (!result.isSuccessful()) { if (!result.isSuccessful()) {
handleCategoryFormFailure(request, response, "Edit category", "/book-categories/update", form, result); handleCategoryFormFailure(request, response, "编辑分类", "/book-categories/update", form, result);
return; return;
} }
@@ -338,20 +338,20 @@ public class BookManagementServlet extends HttpServlet {
Book book = new Book(); Book book = new Book();
if (requireId) { if (requireId) {
book.setId(parseLong(values.get("id"), "id", "Select a valid book.", errors)); book.setId(parseLong(values.get("id"), "id", "请选择有效的图书。", errors));
} }
book.setIdentifier(values.get("identifier")); book.setIdentifier(values.get("identifier"));
book.setTitle(values.get("title")); book.setTitle(values.get("title"));
book.setAuthor(values.get("author")); book.setAuthor(values.get("author"));
book.setCategoryId(parseLong(values.get("categoryId"), "categoryId", "Select a category.", errors)); book.setCategoryId(parseLong(values.get("categoryId"), "categoryId", "请选择分类。", errors));
book.setTotalCopies(parseInt(values.get("totalCopies"), "totalCopies", "Enter a valid total copy count.", errors)); book.setTotalCopies(parseInt(values.get("totalCopies"), "totalCopies", "请输入有效的馆藏总数。", errors));
book.setAvailableCopies(parseInt(values.get("availableCopies"), "availableCopies", book.setAvailableCopies(parseInt(values.get("availableCopies"), "availableCopies",
"Enter a valid available copy count.", errors)); "请输入有效的可借数量。", errors));
try { try {
book.setStatus(BookStatus.fromCode(values.get("status"))); book.setStatus(BookStatus.fromCode(values.get("status")));
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
errors.put("status", "Select a status."); errors.put("status", "请选择状态。");
} }
return new BookForm(book, values, errors); return new BookForm(book, values, errors);
@@ -376,7 +376,7 @@ public class BookManagementServlet extends HttpServlet {
BookCategory category = new BookCategory(); BookCategory category = new BookCategory();
if (requireId) { if (requireId) {
category.setId(parseLong(values.get("id"), "id", "Select a valid category.", errors)); category.setId(parseLong(values.get("id"), "id", "请选择有效的分类。", errors));
} }
category.setName(values.get("name")); category.setName(values.get("name"));
category.setDescription(values.get("description")); category.setDescription(values.get("description"));
@@ -454,7 +454,7 @@ public class BookManagementServlet extends HttpServlet {
} }
private boolean isPermissionDenied(ServiceResult<?> result) { private boolean isPermissionDenied(ServiceResult<?> result) {
return !result.isSuccessful() && "You do not have permission to manage books.".equals(result.getMessage()); return !result.isSuccessful() && "您无权管理图书。".equals(result.getMessage());
} }
private void forwardDenied(HttpServletRequest request, HttpServletResponse response, String message) private void forwardDenied(HttpServletRequest request, HttpServletResponse response, String message)
@@ -115,7 +115,7 @@ public class BorrowingManagementServlet extends HttpServlet {
throws IOException, ServletException { throws IOException, ServletException {
long id = requiredLong(request.getParameter("id"), -1L); long id = requiredLong(request.getParameter("id"), -1L);
ServiceResult<Void> result = id <= 0 ServiceResult<Void> result = id <= 0
? ServiceResult.failure("Select a valid borrowing record.") ? ServiceResult.failure("请选择有效的借阅记录。")
: borrowingService.returnBook(currentUser(request), id); : borrowingService.returnBook(currentUser(request), id);
redirectWithResult(request, response, result); redirectWithResult(request, response, result);
} }
@@ -124,7 +124,7 @@ public class BorrowingManagementServlet extends HttpServlet {
throws IOException, ServletException { throws IOException, ServletException {
long id = requiredLong(request.getParameter("id"), -1L); long id = requiredLong(request.getParameter("id"), -1L);
ServiceResult<Void> result = id <= 0 ServiceResult<Void> result = id <= 0
? ServiceResult.failure("Select a valid borrowing record.") ? ServiceResult.failure("请选择有效的借阅记录。")
: borrowingService.renewLoan(currentUser(request), id); : borrowingService.renewLoan(currentUser(request), id);
redirectWithResult(request, response, result); redirectWithResult(request, response, result);
} }
@@ -185,12 +185,12 @@ public class BorrowingManagementServlet extends HttpServlet {
if (result.hasErrors()) { if (result.hasErrors()) {
return result.getErrors().values().iterator().next(); return result.getErrors().values().iterator().next();
} }
return "Borrowing action failed."; return "借阅操作失败。";
} }
private boolean isPermissionDenied(ServiceResult<?> result) { private boolean isPermissionDenied(ServiceResult<?> result) {
return !result.isSuccessful() return !result.isSuccessful()
&& "You do not have permission to manage borrowing.".equals(result.getMessage()); && "您无权管理借阅。".equals(result.getMessage());
} }
private void forwardDenied(HttpServletRequest request, HttpServletResponse response, String message) private void forwardDenied(HttpServletRequest request, HttpServletResponse response, String message)
@@ -20,7 +20,7 @@ import javax.servlet.http.HttpSession;
public class ReaderLoanHistoryServlet extends HttpServlet { public class ReaderLoanHistoryServlet extends HttpServlet {
private static final String HISTORY_JSP = "/WEB-INF/jsp/reader/loans.jsp"; private static final String HISTORY_JSP = "/WEB-INF/jsp/reader/loans.jsp";
private static final String UNAUTHORIZED_JSP = "/WEB-INF/jsp/auth/unauthorized.jsp"; private static final String UNAUTHORIZED_JSP = "/WEB-INF/jsp/auth/unauthorized.jsp";
private static final String HISTORY_DENIED_MESSAGE = "You do not have permission to view loan history."; private static final String HISTORY_DENIED_MESSAGE = "您无权查看借阅历史。";
private BorrowingServiceImpl borrowingService; private BorrowingServiceImpl borrowingService;
@@ -41,7 +41,7 @@ public class ReaderManagementServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String path = request.getServletPath(); String path = request.getServletPath();
if ("/readers/new".equals(path)) { if ("/readers/new".equals(path)) {
renderForm(request, response, "Create reader", "/readers", defaultReader(), Collections.emptyMap(), renderForm(request, response, "创建读者", "/readers", defaultReader(), Collections.emptyMap(),
Collections.emptyMap(), null); Collections.emptyMap(), null);
return; return;
} }
@@ -99,12 +99,12 @@ public class ReaderManagementServlet extends HttpServlet {
long id = requiredLong(request.getParameter("id"), -1L); long id = requiredLong(request.getParameter("id"), -1L);
ServiceResult<Optional<Reader>> result = readerService.findReader(id); ServiceResult<Optional<Reader>> result = readerService.findReader(id);
if (!result.isSuccessful() || !result.getData().isPresent()) { if (!result.isSuccessful() || !result.getData().isPresent()) {
flashError(request, result.isSuccessful() ? "Reader profile was not found." : result.getMessage()); flashError(request, result.isSuccessful() ? "未找到读者档案。" : result.getMessage());
response.sendRedirect(request.getContextPath() + "/readers"); response.sendRedirect(request.getContextPath() + "/readers");
return; return;
} }
renderForm(request, response, "Edit reader", "/readers/update", result.getData().get(), renderForm(request, response, "编辑读者", "/readers/update", result.getData().get(),
Collections.emptyMap(), Collections.emptyMap(), null); Collections.emptyMap(), Collections.emptyMap(), null);
} }
@@ -112,14 +112,14 @@ public class ReaderManagementServlet extends HttpServlet {
throws ServletException, IOException { throws ServletException, IOException {
ReaderForm form = readReaderForm(request, false); ReaderForm form = readReaderForm(request, false);
if (!form.getErrors().isEmpty()) { if (!form.getErrors().isEmpty()) {
renderForm(request, response, "Create reader", "/readers", form.getReader(), form.getValues(), renderForm(request, response, "创建读者", "/readers", form.getReader(), form.getValues(),
form.getErrors(), "Please correct the highlighted reader fields."); form.getErrors(), "请修正高亮的读者字段。");
return; return;
} }
ServiceResult<Long> result = readerService.createReader(currentUser(request), form.getReader()); ServiceResult<Long> result = readerService.createReader(currentUser(request), form.getReader());
if (!result.isSuccessful()) { if (!result.isSuccessful()) {
handleFormFailure(request, response, "Create reader", "/readers", form, result); handleFormFailure(request, response, "创建读者", "/readers", form, result);
return; return;
} }
@@ -131,14 +131,14 @@ public class ReaderManagementServlet extends HttpServlet {
throws ServletException, IOException { throws ServletException, IOException {
ReaderForm form = readReaderForm(request, true); ReaderForm form = readReaderForm(request, true);
if (!form.getErrors().isEmpty()) { if (!form.getErrors().isEmpty()) {
renderForm(request, response, "Edit reader", "/readers/update", form.getReader(), form.getValues(), renderForm(request, response, "编辑读者", "/readers/update", form.getReader(), form.getValues(),
form.getErrors(), "Please correct the highlighted reader fields."); form.getErrors(), "请修正高亮的读者字段。");
return; return;
} }
ServiceResult<Void> result = readerService.updateReader(currentUser(request), form.getReader()); ServiceResult<Void> result = readerService.updateReader(currentUser(request), form.getReader());
if (!result.isSuccessful()) { if (!result.isSuccessful()) {
handleFormFailure(request, response, "Edit reader", "/readers/update", form, result); handleFormFailure(request, response, "编辑读者", "/readers/update", form, result);
return; return;
} }
@@ -195,21 +195,21 @@ public class ReaderManagementServlet extends HttpServlet {
Reader reader = new Reader(); Reader reader = new Reader();
if (requireId) { if (requireId) {
reader.setId(parseLong(values.get("id"), "id", "Select a valid reader.", errors)); reader.setId(parseLong(values.get("id"), "id", "请选择有效的读者。", errors));
} }
reader.setIdentifier(values.get("identifier")); reader.setIdentifier(values.get("identifier"));
reader.setUserId(optionalPositiveLong(values.get("userId"), "userId", reader.setUserId(optionalPositiveLong(values.get("userId"), "userId",
"Enter a valid linked account ID.", errors)); "请输入有效的关联账户 ID", errors));
reader.setFullName(values.get("fullName")); reader.setFullName(values.get("fullName"));
reader.setPhone(values.get("phone")); reader.setPhone(values.get("phone"));
reader.setEmail(values.get("email")); reader.setEmail(values.get("email"));
reader.setMaxBorrowCount(parseInt(values.get("maxBorrowCount"), "maxBorrowCount", reader.setMaxBorrowCount(parseInt(values.get("maxBorrowCount"), "maxBorrowCount",
"Enter a valid max borrow count.", errors)); "请输入有效的最大借阅数量。", errors));
try { try {
reader.setStatus(ReaderStatus.fromCode(values.get("status"))); reader.setStatus(ReaderStatus.fromCode(values.get("status")));
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
errors.put("status", "Select a status."); errors.put("status", "请选择状态。");
} }
return new ReaderForm(reader, values, errors); return new ReaderForm(reader, values, errors);
@@ -304,7 +304,7 @@ public class ReaderManagementServlet extends HttpServlet {
} }
private boolean isPermissionDenied(ServiceResult<?> result) { private boolean isPermissionDenied(ServiceResult<?> result) {
return !result.isSuccessful() && "You do not have permission to manage readers.".equals(result.getMessage()); return !result.isSuccessful() && "您无权管理读者。".equals(result.getMessage());
} }
private void forwardDenied(HttpServletRequest request, HttpServletResponse response, String message) private void forwardDenied(HttpServletRequest request, HttpServletResponse response, String message)
@@ -46,7 +46,7 @@ public class ReportServlet extends HttpServlet {
private boolean isPermissionDenied(ServiceResult<?> result) { private boolean isPermissionDenied(ServiceResult<?> result) {
return !result.isSuccessful() return !result.isSuccessful()
&& "You do not have permission to view reports.".equals(result.getMessage()); && "您无权查看报表。".equals(result.getMessage());
} }
private void forwardDenied(HttpServletRequest request, HttpServletResponse response, String message) private void forwardDenied(HttpServletRequest request, HttpServletResponse response, String message)
@@ -14,14 +14,14 @@ public class RoleAreaServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String servletPath = request.getServletPath(); String servletPath = request.getServletPath();
if (servletPath.startsWith("/admin")) { if (servletPath.startsWith("/admin")) {
request.setAttribute("areaName", "Administration"); request.setAttribute("areaName", "系统管理");
request.setAttribute("areaSummary", "Account, role, permission, and system-maintenance entry point."); request.setAttribute("areaSummary", "账户、角色、权限和系统维护入口。");
} else if (servletPath.startsWith("/librarian")) { } else if (servletPath.startsWith("/librarian")) {
request.setAttribute("areaName", "Librarian Workspace"); request.setAttribute("areaName", "馆员工作台");
request.setAttribute("areaSummary", "Book, reader, borrowing, return, renewal, and overdue entry point."); request.setAttribute("areaSummary", "图书、读者、借阅、归还、续借和逾期处理入口。");
} else { } else {
request.setAttribute("areaName", "Reader Center"); request.setAttribute("areaName", "读者中心");
request.setAttribute("areaSummary", "Catalog search and reader self-service entry point."); request.setAttribute("areaSummary", "馆藏检索和读者自助服务入口。");
} }
request.getRequestDispatcher(ROLE_HOME_JSP).forward(request, response); request.getRequestDispatcher(ROLE_HOME_JSP).forward(request, response);
@@ -21,7 +21,7 @@ import javax.servlet.http.HttpSession;
public class SystemLogServlet extends HttpServlet { public class SystemLogServlet extends HttpServlet {
private static final String LOGS_JSP = "/WEB-INF/jsp/maintenance/system-logs.jsp"; private static final String LOGS_JSP = "/WEB-INF/jsp/maintenance/system-logs.jsp";
private static final String UNAUTHORIZED_JSP = "/WEB-INF/jsp/auth/unauthorized.jsp"; private static final String UNAUTHORIZED_JSP = "/WEB-INF/jsp/auth/unauthorized.jsp";
private static final String DENIED_MESSAGE = "You do not have permission to view system logs."; private static final String DENIED_MESSAGE = "您无权查看系统日志。";
private SystemLogService systemLogService; private SystemLogService systemLogService;
@@ -30,7 +30,7 @@ public class UserManagementServlet extends HttpServlet {
private static final String UNAUTHORIZED_JSP = "/WEB-INF/jsp/auth/unauthorized.jsp"; private static final String UNAUTHORIZED_JSP = "/WEB-INF/jsp/auth/unauthorized.jsp";
private static final String FLASH_SUCCESS = "flashSuccess"; private static final String FLASH_SUCCESS = "flashSuccess";
private static final String FLASH_ERROR = "flashError"; private static final String FLASH_ERROR = "flashError";
private static final String DENIED_MESSAGE = "You do not have permission to manage users."; private static final String DENIED_MESSAGE = "您无权管理用户。";
private UserAccountService userAccountService; private UserAccountService userAccountService;
@@ -44,7 +44,7 @@ public class UserManagementServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String path = request.getServletPath(); String path = request.getServletPath();
if ("/admin/users/new".equals(path)) { if ("/admin/users/new".equals(path)) {
renderForm(request, response, "Create user account", "/admin/users", defaultUser(), renderForm(request, response, "创建用户账户", "/admin/users", defaultUser(),
Collections.emptyMap(), Collections.emptyMap(), null); Collections.emptyMap(), Collections.emptyMap(), null);
return; return;
} }
@@ -108,12 +108,12 @@ public class UserManagementServlet extends HttpServlet {
return; return;
} }
if (!result.isSuccessful() || !result.getData().isPresent()) { if (!result.isSuccessful() || !result.getData().isPresent()) {
flashError(request, result.isSuccessful() ? "User account was not found." : result.getMessage()); flashError(request, result.isSuccessful() ? "未找到用户账户。" : result.getMessage());
response.sendRedirect(request.getContextPath() + "/admin/users"); response.sendRedirect(request.getContextPath() + "/admin/users");
return; return;
} }
renderForm(request, response, "Edit user account", "/admin/users/update", result.getData().get(), renderForm(request, response, "编辑用户账户", "/admin/users/update", result.getData().get(),
Collections.emptyMap(), Collections.emptyMap(), null); Collections.emptyMap(), Collections.emptyMap(), null);
} }
@@ -121,15 +121,15 @@ public class UserManagementServlet extends HttpServlet {
throws ServletException, IOException { throws ServletException, IOException {
UserForm form = readUserForm(request, false); UserForm form = readUserForm(request, false);
if (!form.getErrors().isEmpty()) { if (!form.getErrors().isEmpty()) {
renderForm(request, response, "Create user account", "/admin/users", form.getUser(), form.getValues(), renderForm(request, response, "创建用户账户", "/admin/users", form.getUser(), form.getValues(),
form.getErrors(), "Please correct the highlighted account fields."); form.getErrors(), "请修正高亮的账户字段。");
return; return;
} }
ServiceResult<Long> result = userAccountService.createUser(currentUser(request), form.getUser(), ServiceResult<Long> result = userAccountService.createUser(currentUser(request), form.getUser(),
form.getPassword(), clientIp(request)); form.getPassword(), clientIp(request));
if (!result.isSuccessful()) { if (!result.isSuccessful()) {
handleFormFailure(request, response, "Create user account", "/admin/users", form, result); handleFormFailure(request, response, "创建用户账户", "/admin/users", form, result);
return; return;
} }
@@ -141,15 +141,15 @@ public class UserManagementServlet extends HttpServlet {
throws ServletException, IOException { throws ServletException, IOException {
UserForm form = readUserForm(request, true); UserForm form = readUserForm(request, true);
if (!form.getErrors().isEmpty()) { if (!form.getErrors().isEmpty()) {
renderForm(request, response, "Edit user account", "/admin/users/update", form.getUser(), form.getValues(), renderForm(request, response, "编辑用户账户", "/admin/users/update", form.getUser(), form.getValues(),
form.getErrors(), "Please correct the highlighted account fields."); form.getErrors(), "请修正高亮的账户字段。");
return; return;
} }
ServiceResult<Void> result = userAccountService.updateUser(currentUser(request), form.getUser(), ServiceResult<Void> result = userAccountService.updateUser(currentUser(request), form.getUser(),
form.getPassword(), clientIp(request)); form.getPassword(), clientIp(request));
if (!result.isSuccessful()) { if (!result.isSuccessful()) {
handleFormFailure(request, response, "Edit user account", "/admin/users/update", form, result); handleFormFailure(request, response, "编辑用户账户", "/admin/users/update", form, result);
return; return;
} }
@@ -206,7 +206,7 @@ public class UserManagementServlet extends HttpServlet {
User user = new User(); User user = new User();
if (requireId) { if (requireId) {
user.setId(parseLong(values.get("id"), "id", "Select a valid user account.", errors)); user.setId(parseLong(values.get("id"), "id", "请选择有效的用户账户。", errors));
} }
user.setUsername(values.get("username")); user.setUsername(values.get("username"));
user.setDisplayName(values.get("displayName")); user.setDisplayName(values.get("displayName"));
@@ -214,7 +214,7 @@ public class UserManagementServlet extends HttpServlet {
try { try {
user.setRole(Role.fromCode(values.get("role"))); user.setRole(Role.fromCode(values.get("role")));
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
errors.put("role", "Select a role."); errors.put("role", "请选择角色。");
} }
return new UserForm(user, values, errors, request.getParameter("password")); return new UserForm(user, values, errors, request.getParameter("password"));
@@ -253,7 +253,7 @@ public class UserManagementServlet extends HttpServlet {
if ("false".equals(normalized) || UserSearchCriteria.INACTIVE_STATUS.equals(normalized)) { if ("false".equals(normalized) || UserSearchCriteria.INACTIVE_STATUS.equals(normalized)) {
return false; return false;
} }
errors.put("active", "Select an active state."); errors.put("active", "请选择启用状态。");
return false; return false;
} }
@@ -3,9 +3,9 @@ package com.mzh.library.entity;
import java.util.Locale; import java.util.Locale;
public enum BookStatus { public enum BookStatus {
AVAILABLE("available", "Available"), AVAILABLE("available", "可借"),
UNAVAILABLE("unavailable", "Unavailable"), UNAVAILABLE("unavailable", "不可借"),
ARCHIVED("archived", "Archived"); ARCHIVED("archived", "已归档");
private final String code; private final String code;
private final String displayName; private final String displayName;
@@ -145,7 +145,7 @@ public class BorrowRecord {
} }
public String getDisplayStatusName() { public String getDisplayStatusName() {
return isOverdue() ? "Overdue" : status.getDisplayName(); return isOverdue() ? "逾期" : status.getDisplayName();
} }
public String getBorrowedAtText() { public String getBorrowedAtText() {
@@ -3,8 +3,8 @@ package com.mzh.library.entity;
import java.util.Locale; import java.util.Locale;
public enum BorrowRecordStatus { public enum BorrowRecordStatus {
ACTIVE("active", "Active"), ACTIVE("active", "借阅中"),
RETURNED("returned", "Returned"); RETURNED("returned", "已归还");
private final String code; private final String code;
private final String displayName; private final String displayName;
@@ -3,9 +3,9 @@ package com.mzh.library.entity;
import java.util.Locale; import java.util.Locale;
public enum ReaderStatus { public enum ReaderStatus {
ACTIVE("active", "Active"), ACTIVE("active", "正常"),
SUSPENDED("suspended", "Suspended"), SUSPENDED("suspended", "暂停"),
INACTIVE("inactive", "Inactive"); INACTIVE("inactive", "停用");
private final String code; private final String code;
private final String displayName; private final String displayName;
@@ -3,9 +3,9 @@ package com.mzh.library.entity;
import java.util.Locale; import java.util.Locale;
public enum Role { public enum Role {
ADMINISTRATOR("administrator", "Administrator"), ADMINISTRATOR("administrator", "管理员"),
LIBRARIAN("librarian", "Librarian"), LIBRARIAN("librarian", "馆员"),
READER("reader", "Reader"); READER("reader", "读者");
private final String code; private final String code;
private final String displayName; private final String displayName;
@@ -131,7 +131,7 @@ public class SystemLog {
return username; return username;
} }
return operatorId == null ? "System" : "User #" + operatorId; return operatorId == null ? "系统" : "用户 #" + operatorId;
} }
public String getOperatorMetaText() { public String getOperatorMetaText() {
@@ -144,7 +144,7 @@ public class SystemLog {
if (operatorId != null && (!displayName.isEmpty() || !username.isEmpty())) { if (operatorId != null && (!displayName.isEmpty() || !username.isEmpty())) {
appendMeta(meta, "#" + operatorId); appendMeta(meta, "#" + operatorId);
} }
appendMeta(meta, trim(operatorRole)); appendMeta(meta, displayRole(operatorRole));
return meta.toString(); return meta.toString();
} }
@@ -157,8 +157,22 @@ public class SystemLog {
} }
public String getResultStatusName() { public String getResultStatusName() {
String trimmed = trim(resultStatus); String normalized = trim(resultStatus).toLowerCase(Locale.ROOT);
return trimmed.isEmpty() ? "Unknown" : trimmed; if ("success".equals(normalized)) {
return "成功";
}
if ("failure".equals(normalized)) {
return "失败";
}
return normalized.isEmpty() ? "未知" : trim(resultStatus);
}
public String getTargetTableName() {
String normalized = trim(targetTable).toLowerCase(Locale.ROOT);
if ("users".equals(normalized)) {
return "用户";
}
return trim(targetTable);
} }
private void appendMeta(StringBuilder meta, String value) { private void appendMeta(StringBuilder meta, String value) {
@@ -174,4 +188,16 @@ public class SystemLog {
private String trim(String value) { private String trim(String value) {
return value == null ? "" : value.trim(); return value == null ? "" : value.trim();
} }
private String displayRole(String roleCode) {
String normalized = trim(roleCode);
if (normalized.isEmpty()) {
return "";
}
try {
return Role.fromCode(normalized).getDisplayName();
} catch (IllegalArgumentException ex) {
return normalized;
}
}
} }
@@ -84,7 +84,7 @@ public class User {
} }
public String getActiveStatusName() { public String getActiveStatusName() {
return active ? "Active" : "Inactive"; return active ? "启用" : "停用";
} }
public String getCreatedAtText() { public String getCreatedAtText() {
@@ -62,7 +62,7 @@ public class AuthorizationFilter implements Filter {
logDeniedAccess(user, requiredRule, path); logDeniedAccess(user, requiredRule, path);
httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); httpResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
request.setAttribute("errorMessage", "You do not have permission to access this page."); request.setAttribute("errorMessage", "您无权访问此页面。");
request.getRequestDispatcher(UNAUTHORIZED_JSP).forward(request, response); request.getRequestDispatcher(UNAUTHORIZED_JSP).forward(request, response);
} }
@@ -17,9 +17,9 @@ import java.util.logging.Logger;
public class AuthServiceImpl implements AuthService { public class AuthServiceImpl implements AuthService {
private static final Logger LOGGER = Logger.getLogger(AuthServiceImpl.class.getName()); private static final Logger LOGGER = Logger.getLogger(AuthServiceImpl.class.getName());
private static final String REQUIRED_MESSAGE = "Username and password are required."; private static final String REQUIRED_MESSAGE = "请输入用户名和密码。";
private static final String INVALID_MESSAGE = "Invalid username or password."; private static final String INVALID_MESSAGE = "用户名或密码不正确。";
private static final String UNAVAILABLE_MESSAGE = "Login service is temporarily unavailable. Please try again later."; private static final String UNAVAILABLE_MESSAGE = "登录服务暂时不可用,请稍后重试。";
private final UserDao userDao; private final UserDao userDao;
private final PermissionPolicy permissionPolicy; private final PermissionPolicy permissionPolicy;
@@ -21,10 +21,10 @@ import java.util.logging.Logger;
public class BookServiceImpl implements BookService { public class BookServiceImpl implements BookService {
private static final Logger LOGGER = Logger.getLogger(BookServiceImpl.class.getName()); private static final Logger LOGGER = Logger.getLogger(BookServiceImpl.class.getName());
private static final String UNAVAILABLE_MESSAGE = private static final String UNAVAILABLE_MESSAGE =
"Book service is temporarily unavailable. Please try again later."; "图书服务暂时不可用,请稍后重试。";
private static final String VALIDATION_MESSAGE = "Please correct the highlighted book fields."; private static final String VALIDATION_MESSAGE = "请修正高亮的图书字段。";
private static final String CATEGORY_VALIDATION_MESSAGE = "Please correct the highlighted category fields."; private static final String CATEGORY_VALIDATION_MESSAGE = "请修正高亮的分类字段。";
private static final String DENIED_MESSAGE = "You do not have permission to manage books."; private static final String DENIED_MESSAGE = "您无权管理图书。";
private final BookDao bookDao; private final BookDao bookDao;
private final PermissionPolicy permissionPolicy; private final PermissionPolicy permissionPolicy;
@@ -51,7 +51,7 @@ public class BookServiceImpl implements BookService {
@Override @Override
public ServiceResult<Optional<BookCategory>> findCategory(long id) { public ServiceResult<Optional<BookCategory>> findCategory(long id) {
if (id <= 0) { if (id <= 0) {
return ServiceResult.failure("Select a valid category."); return ServiceResult.failure("请选择有效的分类。");
} }
try { try {
@@ -76,13 +76,13 @@ public class BookServiceImpl implements BookService {
try { try {
if (bookDao.findCategoryByName(category.getName()).isPresent()) { if (bookDao.findCategoryByName(category.getName()).isPresent()) {
errors.put("name", "Category name is already in use."); errors.put("name", "分类名称已被使用。");
return ServiceResult.validationFailure(CATEGORY_VALIDATION_MESSAGE, errors); return ServiceResult.validationFailure(CATEGORY_VALIDATION_MESSAGE, errors);
} }
long id = bookDao.createCategory(category); long id = bookDao.createCategory(category);
LOGGER.info("Created book category id=" + id + " actorId=" + actor.getId()); LOGGER.info("Created book category id=" + id + " actorId=" + actor.getId());
return ServiceResult.success(id, "Category created."); return ServiceResult.success(id, "分类已创建。");
} catch (DaoException ex) { } catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to create book category actorId=" + actor.getId() LOGGER.log(Level.SEVERE, "Unable to create book category actorId=" + actor.getId()
+ " name=" + safeCategoryName(category), ex); + " name=" + safeCategoryName(category), ex);
@@ -105,16 +105,16 @@ public class BookServiceImpl implements BookService {
try { try {
Optional<BookCategory> existingWithName = bookDao.findCategoryByName(category.getName()); Optional<BookCategory> existingWithName = bookDao.findCategoryByName(category.getName());
if (existingWithName.isPresent() && existingWithName.get().getId() != category.getId()) { if (existingWithName.isPresent() && existingWithName.get().getId() != category.getId()) {
errors.put("name", "Category name is already in use."); errors.put("name", "分类名称已被使用。");
return ServiceResult.validationFailure(CATEGORY_VALIDATION_MESSAGE, errors); return ServiceResult.validationFailure(CATEGORY_VALIDATION_MESSAGE, errors);
} }
if (!bookDao.updateCategory(category)) { if (!bookDao.updateCategory(category)) {
return ServiceResult.failure("Category was not found."); return ServiceResult.failure("未找到分类。");
} }
LOGGER.info("Updated book category id=" + category.getId() + " actorId=" + actor.getId()); LOGGER.info("Updated book category id=" + category.getId() + " actorId=" + actor.getId());
return ServiceResult.success(null, "Category updated."); return ServiceResult.success(null, "分类已更新。");
} catch (DaoException ex) { } catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to update book category id=" + category.getId() LOGGER.log(Level.SEVERE, "Unable to update book category id=" + category.getId()
+ " actorId=" + actor.getId(), ex); + " actorId=" + actor.getId(), ex);
@@ -128,25 +128,25 @@ public class BookServiceImpl implements BookService {
return ServiceResult.failure(DENIED_MESSAGE); return ServiceResult.failure(DENIED_MESSAGE);
} }
if (id <= 0) { if (id <= 0) {
return ServiceResult.failure("Select a valid category."); return ServiceResult.failure("请选择有效的分类。");
} }
try { try {
if (!bookDao.findCategoryById(id).isPresent()) { if (!bookDao.findCategoryById(id).isPresent()) {
return ServiceResult.failure("Category was not found."); return ServiceResult.failure("未找到分类。");
} }
if (bookDao.countBooksByCategoryId(id) > 0) { if (bookDao.countBooksByCategoryId(id) > 0) {
Map<String, String> errors = new LinkedHashMap<>(); Map<String, String> errors = new LinkedHashMap<>();
errors.put("category", "Category is used by existing books and cannot be deleted."); errors.put("category", "该分类已被现有图书使用,不能删除。");
return ServiceResult.validationFailure("Category is used by existing books and cannot be deleted.", return ServiceResult.validationFailure("该分类已被现有图书使用,不能删除。",
errors); errors);
} }
if (!bookDao.deleteCategory(id)) { if (!bookDao.deleteCategory(id)) {
return ServiceResult.failure("Category was not found."); return ServiceResult.failure("未找到分类。");
} }
LOGGER.info("Deleted book category id=" + id + " actorId=" + actor.getId()); LOGGER.info("Deleted book category id=" + id + " actorId=" + actor.getId());
return ServiceResult.success(null, "Category deleted."); return ServiceResult.success(null, "分类已删除。");
} catch (DaoException ex) { } catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to delete book category id=" + id + " actorId=" + actor.getId(), ex); LOGGER.log(Level.SEVERE, "Unable to delete book category id=" + id + " actorId=" + actor.getId(), ex);
return ServiceResult.failure(UNAVAILABLE_MESSAGE); return ServiceResult.failure(UNAVAILABLE_MESSAGE);
@@ -158,8 +158,8 @@ public class BookServiceImpl implements BookService {
BookSearchCriteria normalized = criteria == null ? new BookSearchCriteria() : criteria; BookSearchCriteria normalized = criteria == null ? new BookSearchCriteria() : criteria;
if (normalized.getCategoryId() != null && normalized.getCategoryId() <= 0) { if (normalized.getCategoryId() != null && normalized.getCategoryId() <= 0) {
Map<String, String> errors = new LinkedHashMap<>(); Map<String, String> errors = new LinkedHashMap<>();
errors.put("categoryId", "Select a valid category."); errors.put("categoryId", "请选择有效的分类。");
return ServiceResult.validationFailure("Please correct the catalog search filters.", errors); return ServiceResult.validationFailure("请修正馆藏检索筛选条件。", errors);
} }
try { try {
@@ -173,7 +173,7 @@ public class BookServiceImpl implements BookService {
@Override @Override
public ServiceResult<Optional<Book>> findBook(long id) { public ServiceResult<Optional<Book>> findBook(long id) {
if (id <= 0) { if (id <= 0) {
return ServiceResult.failure("Select a valid book."); return ServiceResult.failure("请选择有效的图书。");
} }
try { try {
@@ -198,13 +198,13 @@ public class BookServiceImpl implements BookService {
try { try {
if (bookDao.findByIdentifier(book.getIdentifier()).isPresent()) { if (bookDao.findByIdentifier(book.getIdentifier()).isPresent()) {
errors.put("identifier", "Book identifier is already in use."); errors.put("identifier", "图书编号已被使用。");
return ServiceResult.validationFailure(VALIDATION_MESSAGE, errors); return ServiceResult.validationFailure(VALIDATION_MESSAGE, errors);
} }
long id = bookDao.create(book); long id = bookDao.create(book);
LOGGER.info("Created book id=" + id + " actorId=" + actor.getId()); LOGGER.info("Created book id=" + id + " actorId=" + actor.getId());
return ServiceResult.success(id, "Book created."); return ServiceResult.success(id, "图书已创建。");
} catch (DaoException ex) { } catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to create book actorId=" + actor.getId(), ex); LOGGER.log(Level.SEVERE, "Unable to create book actorId=" + actor.getId(), ex);
return ServiceResult.failure(UNAVAILABLE_MESSAGE); return ServiceResult.failure(UNAVAILABLE_MESSAGE);
@@ -226,16 +226,16 @@ public class BookServiceImpl implements BookService {
try { try {
Optional<Book> existingWithIdentifier = bookDao.findByIdentifier(book.getIdentifier()); Optional<Book> existingWithIdentifier = bookDao.findByIdentifier(book.getIdentifier());
if (existingWithIdentifier.isPresent() && existingWithIdentifier.get().getId() != book.getId()) { if (existingWithIdentifier.isPresent() && existingWithIdentifier.get().getId() != book.getId()) {
errors.put("identifier", "Book identifier is already in use."); errors.put("identifier", "图书编号已被使用。");
return ServiceResult.validationFailure(VALIDATION_MESSAGE, errors); return ServiceResult.validationFailure(VALIDATION_MESSAGE, errors);
} }
if (!bookDao.update(book)) { if (!bookDao.update(book)) {
return ServiceResult.failure("Book was not found."); return ServiceResult.failure("未找到图书。");
} }
LOGGER.info("Updated book id=" + book.getId() + " actorId=" + actor.getId()); LOGGER.info("Updated book id=" + book.getId() + " actorId=" + actor.getId());
return ServiceResult.success(null, "Book updated."); return ServiceResult.success(null, "图书已更新。");
} catch (DaoException ex) { } catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to update book id=" + book.getId() + " actorId=" + actor.getId(), ex); LOGGER.log(Level.SEVERE, "Unable to update book id=" + book.getId() + " actorId=" + actor.getId(), ex);
return ServiceResult.failure(UNAVAILABLE_MESSAGE); return ServiceResult.failure(UNAVAILABLE_MESSAGE);
@@ -248,16 +248,16 @@ public class BookServiceImpl implements BookService {
return ServiceResult.failure(DENIED_MESSAGE); return ServiceResult.failure(DENIED_MESSAGE);
} }
if (id <= 0) { if (id <= 0) {
return ServiceResult.failure("Select a valid book."); return ServiceResult.failure("请选择有效的图书。");
} }
try { try {
if (!bookDao.delete(id)) { if (!bookDao.delete(id)) {
return ServiceResult.failure("Book was not found."); return ServiceResult.failure("未找到图书。");
} }
LOGGER.info("Deleted book id=" + id + " actorId=" + actor.getId()); LOGGER.info("Deleted book id=" + id + " actorId=" + actor.getId());
return ServiceResult.success(null, "Book deleted."); return ServiceResult.success(null, "图书已删除。");
} catch (DaoException ex) { } catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to delete book id=" + id + " actorId=" + actor.getId(), ex); LOGGER.log(Level.SEVERE, "Unable to delete book id=" + id + " actorId=" + actor.getId(), ex);
return ServiceResult.failure(UNAVAILABLE_MESSAGE); return ServiceResult.failure(UNAVAILABLE_MESSAGE);
@@ -288,30 +288,30 @@ public class BookServiceImpl implements BookService {
private Map<String, String> validate(Book book, boolean requireId) { private Map<String, String> validate(Book book, boolean requireId) {
Map<String, String> errors = new LinkedHashMap<>(); Map<String, String> errors = new LinkedHashMap<>();
if (book == null) { if (book == null) {
errors.put("book", "Book details are required."); errors.put("book", "请填写图书详情。");
return errors; return errors;
} }
if (requireId && book.getId() <= 0) { if (requireId && book.getId() <= 0) {
errors.put("id", "Select a valid book."); errors.put("id", "请选择有效的图书。");
} }
requireLength(errors, "identifier", book.getIdentifier(), "Book identifier", 64); requireLength(errors, "identifier", book.getIdentifier(), "图书编号", 64);
requireLength(errors, "title", book.getTitle(), "Title", 200); requireLength(errors, "title", book.getTitle(), "书名", 200);
requireLength(errors, "author", book.getAuthor(), "Author", 120); requireLength(errors, "author", book.getAuthor(), "作者", 120);
if (book.getCategoryId() <= 0) { if (book.getCategoryId() <= 0) {
errors.put("categoryId", "Select a category."); errors.put("categoryId", "请选择分类。");
} }
if (book.getTotalCopies() < 0) { if (book.getTotalCopies() < 0) {
errors.put("totalCopies", "Total copies cannot be negative."); errors.put("totalCopies", "馆藏总数不能为负数。");
} }
if (book.getAvailableCopies() < 0) { if (book.getAvailableCopies() < 0) {
errors.put("availableCopies", "Available copies cannot be negative."); errors.put("availableCopies", "可借数量不能为负数。");
} }
if (book.getAvailableCopies() > book.getTotalCopies()) { if (book.getAvailableCopies() > book.getTotalCopies()) {
errors.put("availableCopies", "Available copies cannot exceed total copies."); errors.put("availableCopies", "可借数量不能超过馆藏总数。");
} }
if (book.getStatus() == null) { if (book.getStatus() == null) {
errors.put("status", "Select a status."); errors.put("status", "请选择状态。");
} }
return errors; return errors;
} }
@@ -319,16 +319,16 @@ public class BookServiceImpl implements BookService {
private Map<String, String> validate(BookCategory category, boolean requireId) { private Map<String, String> validate(BookCategory category, boolean requireId) {
Map<String, String> errors = new LinkedHashMap<>(); Map<String, String> errors = new LinkedHashMap<>();
if (category == null) { if (category == null) {
errors.put("category", "Category details are required."); errors.put("category", "请填写分类详情。");
return errors; return errors;
} }
if (requireId && category.getId() <= 0) { if (requireId && category.getId() <= 0) {
errors.put("id", "Select a valid category."); errors.put("id", "请选择有效的分类。");
} }
requireLength(errors, "name", category.getName(), "Category name", 96); requireLength(errors, "name", category.getName(), "分类名称", 96);
if (category.getDescription() != null && category.getDescription().length() > 255) { if (category.getDescription() != null && category.getDescription().length() > 255) {
errors.put("description", "Description must be 255 characters or fewer."); errors.put("description", "说明不能超过 255 个字符。");
} }
return errors; return errors;
} }
@@ -339,11 +339,11 @@ public class BookServiceImpl implements BookService {
private void requireLength(Map<String, String> errors, String field, String value, String label, int maxLength) { private void requireLength(Map<String, String> errors, String field, String value, String label, int maxLength) {
if (value == null || value.isEmpty()) { if (value == null || value.isEmpty()) {
errors.put(field, label + " is required."); errors.put(field, "请填写" + label + "");
return; return;
} }
if (value.length() > maxLength) { if (value.length() > maxLength) {
errors.put(field, label + " must be " + maxLength + " characters or fewer."); errors.put(field, label + "不能超过 " + maxLength + " 个字符。");
} }
} }
@@ -35,10 +35,10 @@ public class BorrowingServiceImpl implements BorrowingService {
private static final Logger LOGGER = Logger.getLogger(BorrowingServiceImpl.class.getName()); private static final Logger LOGGER = Logger.getLogger(BorrowingServiceImpl.class.getName());
private static final String UNAVAILABLE_MESSAGE = private static final String UNAVAILABLE_MESSAGE =
"Borrowing service is temporarily unavailable. Please try again later."; "借阅服务暂时不可用,请稍后重试。";
private static final String VALIDATION_MESSAGE = "Please correct the highlighted borrowing fields."; private static final String VALIDATION_MESSAGE = "请修正高亮的借阅字段。";
private static final String DENIED_MESSAGE = "You do not have permission to manage borrowing."; private static final String DENIED_MESSAGE = "您无权管理借阅。";
private static final String HISTORY_DENIED_MESSAGE = "You do not have permission to view loan history."; private static final String HISTORY_DENIED_MESSAGE = "您无权查看借阅历史。";
private static final int LOAN_DAYS = 14; private static final int LOAN_DAYS = 14;
private static final int MAX_RENEWALS = 1; private static final int MAX_RENEWALS = 1;
@@ -68,7 +68,7 @@ public class BorrowingServiceImpl implements BorrowingService {
BorrowRecordSearchCriteria normalized = criteria == null ? new BorrowRecordSearchCriteria() : criteria; BorrowRecordSearchCriteria normalized = criteria == null ? new BorrowRecordSearchCriteria() : criteria;
Map<String, String> errors = validateSearch(normalized); Map<String, String> errors = validateSearch(normalized);
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
return ServiceResult.validationFailure("Please correct the borrowing search filters.", errors); return ServiceResult.validationFailure("请修正借阅检索筛选条件。", errors);
} }
try { try {
@@ -98,13 +98,13 @@ public class BorrowingServiceImpl implements BorrowingService {
Optional<Reader> readerResult = borrowRecordDao.findReaderByIdentifierForUpdate(connection, Optional<Reader> readerResult = borrowRecordDao.findReaderByIdentifierForUpdate(connection,
normalizedReaderIdentifier); normalizedReaderIdentifier);
if (!readerResult.isPresent()) { if (!readerResult.isPresent()) {
transactionErrors.put("readerIdentifier", "Reader was not found."); transactionErrors.put("readerIdentifier", "未找到读者。");
} }
Optional<Book> bookResult = borrowRecordDao.findBookByIdentifierForUpdate(connection, Optional<Book> bookResult = borrowRecordDao.findBookByIdentifierForUpdate(connection,
normalizedBookIdentifier); normalizedBookIdentifier);
if (!bookResult.isPresent()) { if (!bookResult.isPresent()) {
transactionErrors.put("bookIdentifier", "Book was not found."); transactionErrors.put("bookIdentifier", "未找到图书。");
} }
if (!transactionErrors.isEmpty()) { if (!transactionErrors.isEmpty()) {
@@ -134,7 +134,7 @@ public class BorrowingServiceImpl implements BorrowingService {
LOGGER.info("Borrowed book recordId=" + id + " readerId=" + reader.getId() LOGGER.info("Borrowed book recordId=" + id + " readerId=" + reader.getId()
+ " bookId=" + book.getId() + " actorId=" + actor.getId()); + " bookId=" + book.getId() + " actorId=" + actor.getId());
return ServiceResult.success(id, "Book borrowed."); return ServiceResult.success(id, "图书已借出。");
}); });
} catch (DaoException ex) { } catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to borrow book actorId=" + actor.getId() LOGGER.log(Level.SEVERE, "Unable to borrow book actorId=" + actor.getId()
@@ -150,20 +150,20 @@ public class BorrowingServiceImpl implements BorrowingService {
return ServiceResult.failure(DENIED_MESSAGE); return ServiceResult.failure(DENIED_MESSAGE);
} }
if (recordId <= 0) { if (recordId <= 0) {
return ServiceResult.failure("Select a valid borrowing record."); return ServiceResult.failure("请选择有效的借阅记录。");
} }
try { try {
return transactionExecutor.execute(connection -> { return transactionExecutor.execute(connection -> {
Optional<BorrowRecord> recordResult = borrowRecordDao.findByIdForUpdate(connection, recordId); Optional<BorrowRecord> recordResult = borrowRecordDao.findByIdForUpdate(connection, recordId);
if (!recordResult.isPresent()) { if (!recordResult.isPresent()) {
return ServiceResult.failure("Borrowing record was not found."); return ServiceResult.failure("未找到借阅记录。");
} }
BorrowRecord record = recordResult.get(); BorrowRecord record = recordResult.get();
Map<String, String> errors = validateActiveLoan(record); Map<String, String> errors = validateActiveLoan(record);
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
return ServiceResult.validationFailure("Borrowing record cannot be returned.", errors); return ServiceResult.validationFailure("借阅记录不能归还。", errors);
} }
if (!borrowRecordDao.markReturned(connection, recordId, now())) { if (!borrowRecordDao.markReturned(connection, recordId, now())) {
@@ -172,7 +172,7 @@ public class BorrowingServiceImpl implements BorrowingService {
borrowRecordDao.incrementAvailableCopies(connection, record.getBookId()); borrowRecordDao.incrementAvailableCopies(connection, record.getBookId());
LOGGER.info("Returned borrow recordId=" + recordId + " actorId=" + actor.getId()); LOGGER.info("Returned borrow recordId=" + recordId + " actorId=" + actor.getId());
return ServiceResult.success(null, "Book returned."); return ServiceResult.success(null, "图书已归还。");
}); });
} catch (DaoException ex) { } catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to return borrow record id=" + recordId + " actorId=" + actor.getId(), ex); LOGGER.log(Level.SEVERE, "Unable to return borrow record id=" + recordId + " actorId=" + actor.getId(), ex);
@@ -186,23 +186,23 @@ public class BorrowingServiceImpl implements BorrowingService {
return ServiceResult.failure(DENIED_MESSAGE); return ServiceResult.failure(DENIED_MESSAGE);
} }
if (recordId <= 0) { if (recordId <= 0) {
return ServiceResult.failure("Select a valid borrowing record."); return ServiceResult.failure("请选择有效的借阅记录。");
} }
try { try {
return transactionExecutor.execute(connection -> { return transactionExecutor.execute(connection -> {
Optional<BorrowRecord> recordResult = borrowRecordDao.findByIdForUpdate(connection, recordId); Optional<BorrowRecord> recordResult = borrowRecordDao.findByIdForUpdate(connection, recordId);
if (!recordResult.isPresent()) { if (!recordResult.isPresent()) {
return ServiceResult.failure("Borrowing record was not found."); return ServiceResult.failure("未找到借阅记录。");
} }
BorrowRecord record = recordResult.get(); BorrowRecord record = recordResult.get();
Map<String, String> errors = validateActiveLoan(record); Map<String, String> errors = validateActiveLoan(record);
if (record.getRenewalCount() >= MAX_RENEWALS) { if (record.getRenewalCount() >= MAX_RENEWALS) {
errors.put("renewalCount", "This loan has already reached the renewal limit."); errors.put("renewalCount", "该借阅已达到续借次数上限。");
} }
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
return ServiceResult.validationFailure("Borrowing record cannot be renewed.", errors); return ServiceResult.validationFailure("借阅记录不能续借。", errors);
} }
LocalDateTime currentDueAt = record.getDueAt() == null ? now() : record.getDueAt(); LocalDateTime currentDueAt = record.getDueAt() == null ? now() : record.getDueAt();
@@ -212,7 +212,7 @@ public class BorrowingServiceImpl implements BorrowingService {
} }
LOGGER.info("Renewed borrow recordId=" + recordId + " actorId=" + actor.getId()); LOGGER.info("Renewed borrow recordId=" + recordId + " actorId=" + actor.getId());
return ServiceResult.success(null, "Loan renewed."); return ServiceResult.success(null, "借阅已续借。");
}); });
} catch (DaoException ex) { } catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to renew borrow record id=" + recordId + " actorId=" + actor.getId(), ex); LOGGER.log(Level.SEVERE, "Unable to renew borrow record id=" + recordId + " actorId=" + actor.getId(), ex);
@@ -229,7 +229,7 @@ public class BorrowingServiceImpl implements BorrowingService {
try { try {
Optional<Reader> readerResult = borrowRecordDao.findReaderByUserId(actor.getId()); Optional<Reader> readerResult = borrowRecordDao.findReaderByUserId(actor.getId());
if (!readerResult.isPresent()) { if (!readerResult.isPresent()) {
return ServiceResult.success(Collections.emptyList(), "No reader profile is linked to your account."); return ServiceResult.success(Collections.emptyList(), "您的账户未关联读者档案。");
} }
return ServiceResult.success(borrowRecordDao.findByReaderId(readerResult.get().getId())); return ServiceResult.success(borrowRecordDao.findByReaderId(readerResult.get().getId()));
@@ -246,16 +246,16 @@ public class BorrowingServiceImpl implements BorrowingService {
private void validateBorrowEligibility(Map<String, String> errors, Reader reader, Book book, private void validateBorrowEligibility(Map<String, String> errors, Reader reader, Book book,
java.sql.Connection connection) { java.sql.Connection connection) {
if (reader.getStatus() != ReaderStatus.ACTIVE) { if (reader.getStatus() != ReaderStatus.ACTIVE) {
errors.put("readerIdentifier", "Reader must be active to borrow books."); errors.put("readerIdentifier", "读者状态必须为正常才能借阅图书。");
} }
int activeLoans = borrowRecordDao.countActiveByReaderId(connection, reader.getId()); int activeLoans = borrowRecordDao.countActiveByReaderId(connection, reader.getId());
if (activeLoans >= reader.getMaxBorrowCount()) { if (activeLoans >= reader.getMaxBorrowCount()) {
errors.put("readerIdentifier", "Reader has reached the active borrowing limit."); errors.put("readerIdentifier", "读者已达到在借数量上限。");
} }
if (book.getStatus() != BookStatus.AVAILABLE) { if (book.getStatus() != BookStatus.AVAILABLE) {
errors.put("bookIdentifier", "Book status does not allow borrowing."); errors.put("bookIdentifier", "图书状态不允许借阅。");
} else if (book.getAvailableCopies() <= 0) { } else if (book.getAvailableCopies() <= 0) {
errors.put("bookIdentifier", "No available copies remain for this book."); errors.put("bookIdentifier", "该图书没有可借副本。");
} }
} }
@@ -270,7 +270,7 @@ public class BorrowingServiceImpl implements BorrowingService {
try { try {
BorrowRecordStatus.fromCode(statusCode); BorrowRecordStatus.fromCode(statusCode);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
errors.put("status", "Select a valid borrowing status."); errors.put("status", "请选择有效的借阅状态。");
} }
} }
return errors; return errors;
@@ -278,26 +278,26 @@ public class BorrowingServiceImpl implements BorrowingService {
private Map<String, String> validateBorrowIdentifiers(String readerIdentifier, String bookIdentifier) { private Map<String, String> validateBorrowIdentifiers(String readerIdentifier, String bookIdentifier) {
Map<String, String> errors = new LinkedHashMap<>(); Map<String, String> errors = new LinkedHashMap<>();
requireLength(errors, "readerIdentifier", readerIdentifier, "Reader ID", 64); requireLength(errors, "readerIdentifier", readerIdentifier, "读者编号", 64);
requireLength(errors, "bookIdentifier", bookIdentifier, "Book ID", 64); requireLength(errors, "bookIdentifier", bookIdentifier, "图书编号", 64);
return errors; return errors;
} }
private Map<String, String> validateActiveLoan(BorrowRecord record) { private Map<String, String> validateActiveLoan(BorrowRecord record) {
Map<String, String> errors = new LinkedHashMap<>(); Map<String, String> errors = new LinkedHashMap<>();
if (record.getStatus() != BorrowRecordStatus.ACTIVE || record.getReturnedAt() != null) { if (record.getStatus() != BorrowRecordStatus.ACTIVE || record.getReturnedAt() != null) {
errors.put("status", "Only active loans can use this action."); errors.put("status", "只有借阅中的记录可以执行此操作。");
} }
return errors; return errors;
} }
private void requireLength(Map<String, String> errors, String field, String value, String label, int maxLength) { private void requireLength(Map<String, String> errors, String field, String value, String label, int maxLength) {
if (value == null || value.isEmpty()) { if (value == null || value.isEmpty()) {
errors.put(field, label + " is required."); errors.put(field, "请填写" + label + "");
return; return;
} }
if (value.length() > maxLength) { if (value.length() > maxLength) {
errors.put(field, label + " must be " + maxLength + " characters or fewer."); errors.put(field, label + "不能超过 " + maxLength + " 个字符。");
} }
} }
@@ -22,10 +22,10 @@ import java.util.logging.Logger;
public class ReaderServiceImpl implements ReaderService { public class ReaderServiceImpl implements ReaderService {
private static final Logger LOGGER = Logger.getLogger(ReaderServiceImpl.class.getName()); private static final Logger LOGGER = Logger.getLogger(ReaderServiceImpl.class.getName());
private static final String UNAVAILABLE_MESSAGE = private static final String UNAVAILABLE_MESSAGE =
"Reader service is temporarily unavailable. Please try again later."; "读者服务暂时不可用,请稍后重试。";
private static final String VALIDATION_MESSAGE = "Please correct the highlighted reader fields."; private static final String VALIDATION_MESSAGE = "请修正高亮的读者字段。";
private static final String SEARCH_VALIDATION_MESSAGE = "Please correct the reader search filters."; private static final String SEARCH_VALIDATION_MESSAGE = "请修正读者检索筛选条件。";
private static final String DENIED_MESSAGE = "You do not have permission to manage readers."; private static final String DENIED_MESSAGE = "您无权管理读者。";
private static final int MAX_BORROW_LIMIT = 50; private static final int MAX_BORROW_LIMIT = 50;
private static final Pattern PHONE_PATTERN = Pattern.compile("(?=.*\\d)[0-9+()\\-\\s]{6,32}"); private static final Pattern PHONE_PATTERN = Pattern.compile("(?=.*\\d)[0-9+()\\-\\s]{6,32}");
private static final Pattern EMAIL_PATTERN = Pattern.compile("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$"); private static final Pattern EMAIL_PATTERN = Pattern.compile("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$");
@@ -61,7 +61,7 @@ public class ReaderServiceImpl implements ReaderService {
@Override @Override
public ServiceResult<Optional<Reader>> findReader(long id) { public ServiceResult<Optional<Reader>> findReader(long id) {
if (id <= 0) { if (id <= 0) {
return ServiceResult.failure("Select a valid reader."); return ServiceResult.failure("请选择有效的读者。");
} }
try { try {
@@ -86,17 +86,17 @@ public class ReaderServiceImpl implements ReaderService {
try { try {
if (readerDao.findByIdentifier(reader.getIdentifier()).isPresent()) { if (readerDao.findByIdentifier(reader.getIdentifier()).isPresent()) {
errors.put("identifier", "Reader identifier is already in use."); errors.put("identifier", "读者编号已被使用。");
return ServiceResult.validationFailure(VALIDATION_MESSAGE, errors); return ServiceResult.validationFailure(VALIDATION_MESSAGE, errors);
} }
if (reader.getUserId() != null && readerDao.findByUserId(reader.getUserId()).isPresent()) { if (reader.getUserId() != null && readerDao.findByUserId(reader.getUserId()).isPresent()) {
errors.put("userId", "Linked account is already assigned to a reader profile."); errors.put("userId", "关联账户已绑定到其他读者档案。");
return ServiceResult.validationFailure(VALIDATION_MESSAGE, errors); return ServiceResult.validationFailure(VALIDATION_MESSAGE, errors);
} }
long id = readerDao.create(reader); long id = readerDao.create(reader);
LOGGER.info("Created reader id=" + id + " actorId=" + actor.getId()); LOGGER.info("Created reader id=" + id + " actorId=" + actor.getId());
return ServiceResult.success(id, "Reader profile created."); return ServiceResult.success(id, "读者档案已创建。");
} catch (DaoException ex) { } catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to create reader actorId=" + actor.getId(), ex); LOGGER.log(Level.SEVERE, "Unable to create reader actorId=" + actor.getId(), ex);
return ServiceResult.failure(UNAVAILABLE_MESSAGE); return ServiceResult.failure(UNAVAILABLE_MESSAGE);
@@ -118,23 +118,23 @@ public class ReaderServiceImpl implements ReaderService {
try { try {
Optional<Reader> existingWithIdentifier = readerDao.findByIdentifier(reader.getIdentifier()); Optional<Reader> existingWithIdentifier = readerDao.findByIdentifier(reader.getIdentifier());
if (existingWithIdentifier.isPresent() && existingWithIdentifier.get().getId() != reader.getId()) { if (existingWithIdentifier.isPresent() && existingWithIdentifier.get().getId() != reader.getId()) {
errors.put("identifier", "Reader identifier is already in use."); errors.put("identifier", "读者编号已被使用。");
return ServiceResult.validationFailure(VALIDATION_MESSAGE, errors); return ServiceResult.validationFailure(VALIDATION_MESSAGE, errors);
} }
if (reader.getUserId() != null) { if (reader.getUserId() != null) {
Optional<Reader> existingWithUser = readerDao.findByUserId(reader.getUserId()); Optional<Reader> existingWithUser = readerDao.findByUserId(reader.getUserId());
if (existingWithUser.isPresent() && existingWithUser.get().getId() != reader.getId()) { if (existingWithUser.isPresent() && existingWithUser.get().getId() != reader.getId()) {
errors.put("userId", "Linked account is already assigned to a reader profile."); errors.put("userId", "关联账户已绑定到其他读者档案。");
return ServiceResult.validationFailure(VALIDATION_MESSAGE, errors); return ServiceResult.validationFailure(VALIDATION_MESSAGE, errors);
} }
} }
if (!readerDao.update(reader)) { if (!readerDao.update(reader)) {
return ServiceResult.failure("Reader profile was not found."); return ServiceResult.failure("未找到读者档案。");
} }
LOGGER.info("Updated reader id=" + reader.getId() + " actorId=" + actor.getId()); LOGGER.info("Updated reader id=" + reader.getId() + " actorId=" + actor.getId());
return ServiceResult.success(null, "Reader profile updated."); return ServiceResult.success(null, "读者档案已更新。");
} catch (DaoException ex) { } catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to update reader id=" + reader.getId() + " actorId=" + actor.getId(), ex); LOGGER.log(Level.SEVERE, "Unable to update reader id=" + reader.getId() + " actorId=" + actor.getId(), ex);
return ServiceResult.failure(UNAVAILABLE_MESSAGE); return ServiceResult.failure(UNAVAILABLE_MESSAGE);
@@ -147,16 +147,16 @@ public class ReaderServiceImpl implements ReaderService {
return ServiceResult.failure(DENIED_MESSAGE); return ServiceResult.failure(DENIED_MESSAGE);
} }
if (id <= 0) { if (id <= 0) {
return ServiceResult.failure("Select a valid reader."); return ServiceResult.failure("请选择有效的读者。");
} }
try { try {
if (!readerDao.deactivate(id)) { if (!readerDao.deactivate(id)) {
return ServiceResult.failure("Reader profile was not found."); return ServiceResult.failure("未找到读者档案。");
} }
LOGGER.info("Deactivated reader id=" + id + " actorId=" + actor.getId()); LOGGER.info("Deactivated reader id=" + id + " actorId=" + actor.getId());
return ServiceResult.success(null, "Reader profile deactivated."); return ServiceResult.success(null, "读者档案已停用。");
} catch (DaoException ex) { } catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to deactivate reader id=" + id + " actorId=" + actor.getId(), ex); LOGGER.log(Level.SEVERE, "Unable to deactivate reader id=" + id + " actorId=" + actor.getId(), ex);
return ServiceResult.failure(UNAVAILABLE_MESSAGE); return ServiceResult.failure(UNAVAILABLE_MESSAGE);
@@ -183,7 +183,7 @@ public class ReaderServiceImpl implements ReaderService {
try { try {
criteria.setStatusCode(ReaderStatus.fromCode(criteria.getStatusCode()).getCode()); criteria.setStatusCode(ReaderStatus.fromCode(criteria.getStatusCode()).getCode());
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
errors.put("status", "Select a valid status."); errors.put("status", "请选择有效的状态。");
} }
} }
return errors; return errors;
@@ -192,24 +192,24 @@ public class ReaderServiceImpl implements ReaderService {
private Map<String, String> validate(Reader reader, boolean requireId) { private Map<String, String> validate(Reader reader, boolean requireId) {
Map<String, String> errors = new LinkedHashMap<>(); Map<String, String> errors = new LinkedHashMap<>();
if (reader == null) { if (reader == null) {
errors.put("reader", "Reader details are required."); errors.put("reader", "请填写读者详情。");
return errors; return errors;
} }
if (requireId && reader.getId() <= 0) { if (requireId && reader.getId() <= 0) {
errors.put("id", "Select a valid reader."); errors.put("id", "请选择有效的读者。");
} }
requireLength(errors, "identifier", reader.getIdentifier(), "Reader identifier", 64); requireLength(errors, "identifier", reader.getIdentifier(), "读者编号", 64);
requireLength(errors, "fullName", reader.getFullName(), "Full name", 100); requireLength(errors, "fullName", reader.getFullName(), "姓名", 100);
if (reader.getUserId() != null && reader.getUserId() <= 0) { if (reader.getUserId() != null && reader.getUserId() <= 0) {
errors.put("userId", "Linked account ID must be positive."); errors.put("userId", "关联账户 ID 必须为正数。");
} }
validateContact(errors, reader); validateContact(errors, reader);
if (reader.getStatus() == null) { if (reader.getStatus() == null) {
errors.put("status", "Select a status."); errors.put("status", "请选择状态。");
} }
if (reader.getMaxBorrowCount() < 1 || reader.getMaxBorrowCount() > MAX_BORROW_LIMIT) { if (reader.getMaxBorrowCount() < 1 || reader.getMaxBorrowCount() > MAX_BORROW_LIMIT) {
errors.put("maxBorrowCount", "Max borrow count must be between 1 and " + MAX_BORROW_LIMIT + "."); errors.put("maxBorrowCount", "最大借阅数量必须在 1 到 " + MAX_BORROW_LIMIT + " 之间。");
} }
return errors; return errors;
} }
@@ -218,24 +218,24 @@ public class ReaderServiceImpl implements ReaderService {
String phone = reader.getPhone(); String phone = reader.getPhone();
String email = reader.getEmail(); String email = reader.getEmail();
if ((phone == null || phone.isEmpty()) && (email == null || email.isEmpty())) { if ((phone == null || phone.isEmpty()) && (email == null || email.isEmpty())) {
errors.put("phone", "Phone or email is required."); errors.put("phone", "请填写电话或邮箱。");
return; return;
} }
if (phone != null && !phone.isEmpty() && !PHONE_PATTERN.matcher(phone).matches()) { if (phone != null && !phone.isEmpty() && !PHONE_PATTERN.matcher(phone).matches()) {
errors.put("phone", "Phone must include a digit and use 6 to 32 digits or common phone symbols."); errors.put("phone", "电话必须包含数字,并使用 6 32 位数字或常见电话符号。");
} }
if (email != null && !email.isEmpty() && !EMAIL_PATTERN.matcher(email).matches()) { if (email != null && !email.isEmpty() && !EMAIL_PATTERN.matcher(email).matches()) {
errors.put("email", "Email must be a valid address."); errors.put("email", "邮箱格式不正确。");
} }
} }
private void requireLength(Map<String, String> errors, String field, String value, String label, int maxLength) { private void requireLength(Map<String, String> errors, String field, String value, String label, int maxLength) {
if (value == null || value.isEmpty()) { if (value == null || value.isEmpty()) {
errors.put(field, label + " is required."); errors.put(field, "请填写" + label + "");
return; return;
} }
if (value.length() > maxLength) { if (value.length() > maxLength) {
errors.put(field, label + " must be " + maxLength + " characters or fewer."); errors.put(field, label + "不能超过 " + maxLength + " 个字符。");
} }
} }
@@ -15,8 +15,8 @@ import java.util.logging.Logger;
public class ReportServiceImpl implements ReportService { public class ReportServiceImpl implements ReportService {
private static final Logger LOGGER = Logger.getLogger(ReportServiceImpl.class.getName()); private static final Logger LOGGER = Logger.getLogger(ReportServiceImpl.class.getName());
private static final String UNAVAILABLE_MESSAGE = private static final String UNAVAILABLE_MESSAGE =
"Report service is temporarily unavailable. Please try again later."; "报表服务暂时不可用,请稍后重试。";
private static final String DENIED_MESSAGE = "You do not have permission to view reports."; private static final String DENIED_MESSAGE = "您无权查看报表。";
private static final int POPULAR_BOOK_LIMIT = 10; private static final int POPULAR_BOOK_LIMIT = 10;
private final ReportDao reportDao; private final ReportDao reportDao;
@@ -20,9 +20,9 @@ import java.util.logging.Logger;
public class SystemLogServiceImpl implements SystemLogService { public class SystemLogServiceImpl implements SystemLogService {
private static final Logger LOGGER = Logger.getLogger(SystemLogServiceImpl.class.getName()); private static final Logger LOGGER = Logger.getLogger(SystemLogServiceImpl.class.getName());
private static final String UNAVAILABLE_MESSAGE = private static final String UNAVAILABLE_MESSAGE =
"System log service is temporarily unavailable. Please try again later."; "系统日志服务暂时不可用,请稍后重试。";
private static final String DENIED_MESSAGE = "You do not have permission to view system logs."; private static final String DENIED_MESSAGE = "您无权查看系统日志。";
private static final String VALIDATION_MESSAGE = "Please correct the system log search filters."; private static final String VALIDATION_MESSAGE = "请修正系统日志检索筛选条件。";
private final SystemLogDao systemLogDao; private final SystemLogDao systemLogDao;
private final PermissionPolicy permissionPolicy; private final PermissionPolicy permissionPolicy;
@@ -62,18 +62,18 @@ public class SystemLogServiceImpl implements SystemLogService {
private Map<String, String> validate(SystemLogSearchCriteria criteria) { private Map<String, String> validate(SystemLogSearchCriteria criteria) {
Map<String, String> errors = new LinkedHashMap<>(); Map<String, String> errors = new LinkedHashMap<>();
if (criteria.getOperationType().length() > 64) { if (criteria.getOperationType().length() > 64) {
errors.put("operationType", "Operation type must be 64 characters or fewer."); errors.put("operationType", "操作类型不能超过 64 个字符。");
} }
if (criteria.getKeyword().length() > 120) { if (criteria.getKeyword().length() > 120) {
errors.put("keyword", "Keyword must be 120 characters or fewer."); errors.put("keyword", "关键词不能超过 120 个字符。");
} }
parseDate(criteria.getCreatedFromText(), "createdFrom", "Start date", errors, criteria, true); parseDate(criteria.getCreatedFromText(), "createdFrom", "开始日期", errors, criteria, true);
parseDate(criteria.getCreatedToText(), "createdTo", "End date", errors, criteria, false); parseDate(criteria.getCreatedToText(), "createdTo", "结束日期", errors, criteria, false);
if (criteria.getCreatedFrom() != null if (criteria.getCreatedFrom() != null
&& criteria.getCreatedTo() != null && criteria.getCreatedTo() != null
&& criteria.getCreatedFrom().isAfter(criteria.getCreatedTo())) { && criteria.getCreatedFrom().isAfter(criteria.getCreatedTo())) {
errors.put("createdTo", "End date must be on or after start date."); errors.put("createdTo", "结束日期必须晚于或等于开始日期。");
} }
return errors; return errors;
} }
@@ -91,7 +91,7 @@ public class SystemLogServiceImpl implements SystemLogService {
criteria.setCreatedTo(parsed); criteria.setCreatedTo(parsed);
} }
} catch (DateTimeParseException ex) { } catch (DateTimeParseException ex) {
errors.put(field, label + " must use YYYY-MM-DD."); errors.put(field, label + "必须使用 YYYY-MM-DD 格式。");
} }
} }
@@ -30,12 +30,12 @@ public class UserAccountServiceImpl implements UserAccountService {
private static final Logger LOGGER = Logger.getLogger(UserAccountServiceImpl.class.getName()); private static final Logger LOGGER = Logger.getLogger(UserAccountServiceImpl.class.getName());
private static final String UNAVAILABLE_MESSAGE = private static final String UNAVAILABLE_MESSAGE =
"User management service is temporarily unavailable. Please try again later."; "用户管理服务暂时不可用,请稍后重试。";
private static final String VALIDATION_MESSAGE = "Please correct the highlighted account fields."; private static final String VALIDATION_MESSAGE = "请修正高亮的账户字段。";
private static final String SEARCH_VALIDATION_MESSAGE = "Please correct the account search filters."; private static final String SEARCH_VALIDATION_MESSAGE = "请修正账户检索筛选条件。";
private static final String DENIED_MESSAGE = "You do not have permission to manage users."; private static final String DENIED_MESSAGE = "您无权管理用户。";
private static final String SELF_DEACTIVATE_MESSAGE = "You cannot deactivate your own administrator account."; private static final String SELF_DEACTIVATE_MESSAGE = "不能停用您自己的管理员账户。";
private static final String SELF_ROLE_MESSAGE = "You cannot change your own administrator role."; private static final String SELF_ROLE_MESSAGE = "不能修改您自己的管理员角色。";
private final UserAccountDao userAccountDao; private final UserAccountDao userAccountDao;
private final SystemLogDao systemLogDao; private final SystemLogDao systemLogDao;
@@ -80,7 +80,7 @@ public class UserAccountServiceImpl implements UserAccountService {
return ServiceResult.failure(DENIED_MESSAGE); return ServiceResult.failure(DENIED_MESSAGE);
} }
if (id <= 0) { if (id <= 0) {
return ServiceResult.failure("Select a valid user account."); return ServiceResult.failure("请选择有效的用户账户。");
} }
try { try {
@@ -105,7 +105,7 @@ public class UserAccountServiceImpl implements UserAccountService {
try { try {
if (userAccountDao.findByUsername(user.getUsername()).isPresent()) { if (userAccountDao.findByUsername(user.getUsername()).isPresent()) {
errors.put("username", "Username is already in use."); errors.put("username", "用户名已被使用。");
return ServiceResult.validationFailure(VALIDATION_MESSAGE, errors); return ServiceResult.validationFailure(VALIDATION_MESSAGE, errors);
} }
@@ -113,10 +113,10 @@ public class UserAccountServiceImpl implements UserAccountService {
return transactionExecutor.execute(connection -> { return transactionExecutor.execute(connection -> {
long id = userAccountDao.create(connection, user); long id = userAccountDao.create(connection, user);
systemLogDao.create(connection, auditLog(actor, "user.create", id, systemLogDao.create(connection, auditLog(actor, "user.create", id,
"Created account username=" + user.getUsername() + " role=" + user.getRole().getCode(), "创建账户:用户名=" + user.getUsername() + ",角色=" + user.getRole().getDisplayName(),
requestIp)); requestIp));
LOGGER.info("Created user id=" + id + " actorId=" + actor.getId()); LOGGER.info("Created user id=" + id + " actorId=" + actor.getId());
return ServiceResult.success(id, "User account created."); return ServiceResult.success(id, "用户账户已创建。");
}); });
} catch (DaoException | IllegalStateException ex) { } catch (DaoException | IllegalStateException ex) {
LOGGER.log(Level.SEVERE, "Unable to create user actorId=" + actor.getId() LOGGER.log(Level.SEVERE, "Unable to create user actorId=" + actor.getId()
@@ -140,7 +140,7 @@ public class UserAccountServiceImpl implements UserAccountService {
try { try {
Optional<User> existingResult = userAccountDao.findById(user.getId()); Optional<User> existingResult = userAccountDao.findById(user.getId());
if (!existingResult.isPresent()) { if (!existingResult.isPresent()) {
return ServiceResult.failure("User account was not found."); return ServiceResult.failure("未找到用户账户。");
} }
protectCurrentAdministrator(actor, user, errors); protectCurrentAdministrator(actor, user, errors);
@@ -158,15 +158,15 @@ public class UserAccountServiceImpl implements UserAccountService {
final boolean passwordChanged = updatePassword; final boolean passwordChanged = updatePassword;
return transactionExecutor.execute(connection -> { return transactionExecutor.execute(connection -> {
if (!userAccountDao.update(connection, user, passwordChanged)) { if (!userAccountDao.update(connection, user, passwordChanged)) {
return ServiceResult.failure("User account was not found."); return ServiceResult.failure("未找到用户账户。");
} }
systemLogDao.create(connection, auditLog(actor, "user.update", user.getId(), systemLogDao.create(connection, auditLog(actor, "user.update", user.getId(),
"Updated account username=" + user.getUsername() + " role=" + user.getRole().getCode() "更新账户:用户名=" + user.getUsername() + ",角色=" + user.getRole().getDisplayName()
+ " active=" + user.isActive() + ",状态=" + (user.isActive() ? "启用" : "停用")
+ (passwordChanged ? " passwordReset=true" : ""), + (passwordChanged ? ",已重置密码" : ""),
requestIp)); requestIp));
LOGGER.info("Updated user id=" + user.getId() + " actorId=" + actor.getId()); LOGGER.info("Updated user id=" + user.getId() + " actorId=" + actor.getId());
return ServiceResult.success(null, "User account updated."); return ServiceResult.success(null, "用户账户已更新。");
}); });
} catch (DaoException | IllegalStateException ex) { } catch (DaoException | IllegalStateException ex) {
LOGGER.log(Level.SEVERE, "Unable to update user id=" + user.getId() + " actorId=" + actor.getId(), ex); LOGGER.log(Level.SEVERE, "Unable to update user id=" + user.getId() + " actorId=" + actor.getId(), ex);
@@ -180,7 +180,7 @@ public class UserAccountServiceImpl implements UserAccountService {
return ServiceResult.failure(DENIED_MESSAGE); return ServiceResult.failure(DENIED_MESSAGE);
} }
if (id <= 0) { if (id <= 0) {
return ServiceResult.failure("Select a valid user account."); return ServiceResult.failure("请选择有效的用户账户。");
} }
if (actor.getId() == id) { if (actor.getId() == id) {
Map<String, String> errors = new LinkedHashMap<>(); Map<String, String> errors = new LinkedHashMap<>();
@@ -191,20 +191,20 @@ public class UserAccountServiceImpl implements UserAccountService {
try { try {
Optional<User> existingResult = userAccountDao.findById(id); Optional<User> existingResult = userAccountDao.findById(id);
if (!existingResult.isPresent()) { if (!existingResult.isPresent()) {
return ServiceResult.failure("User account was not found."); return ServiceResult.failure("未找到用户账户。");
} }
User user = existingResult.get(); User user = existingResult.get();
user.setActive(false); user.setActive(false);
return transactionExecutor.execute(connection -> { return transactionExecutor.execute(connection -> {
if (!userAccountDao.update(connection, user, false)) { if (!userAccountDao.update(connection, user, false)) {
return ServiceResult.failure("User account was not found."); return ServiceResult.failure("未找到用户账户。");
} }
systemLogDao.create(connection, auditLog(actor, "user.deactivate", id, systemLogDao.create(connection, auditLog(actor, "user.deactivate", id,
"Deactivated account username=" + user.getUsername(), "停用账户:用户名=" + user.getUsername(),
requestIp)); requestIp));
LOGGER.info("Deactivated user id=" + id + " actorId=" + actor.getId()); LOGGER.info("Deactivated user id=" + id + " actorId=" + actor.getId());
return ServiceResult.success(null, "User account deactivated."); return ServiceResult.success(null, "用户账户已停用。");
}); });
} catch (DaoException ex) { } catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to deactivate user id=" + id + " actorId=" + actor.getId(), ex); LOGGER.log(Level.SEVERE, "Unable to deactivate user id=" + id + " actorId=" + actor.getId(), ex);
@@ -218,7 +218,7 @@ public class UserAccountServiceImpl implements UserAccountService {
try { try {
criteria.setRoleCode(Role.fromCode(criteria.getRoleCode()).getCode()); criteria.setRoleCode(Role.fromCode(criteria.getRoleCode()).getCode());
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
errors.put("role", "Select a valid role."); errors.put("role", "请选择有效的角色。");
} }
} }
@@ -226,7 +226,7 @@ public class UserAccountServiceImpl implements UserAccountService {
if (!activeStatus.isEmpty() if (!activeStatus.isEmpty()
&& !UserSearchCriteria.ACTIVE_STATUS.equals(activeStatus) && !UserSearchCriteria.ACTIVE_STATUS.equals(activeStatus)
&& !UserSearchCriteria.INACTIVE_STATUS.equals(activeStatus)) { && !UserSearchCriteria.INACTIVE_STATUS.equals(activeStatus)) {
errors.put("active", "Select a valid active state."); errors.put("active", "请选择有效的启用状态。");
} }
return errors; return errors;
} }
@@ -234,19 +234,19 @@ public class UserAccountServiceImpl implements UserAccountService {
private Map<String, String> validateUser(User user, boolean requireId, String password, boolean requirePassword) { private Map<String, String> validateUser(User user, boolean requireId, String password, boolean requirePassword) {
Map<String, String> errors = new LinkedHashMap<>(); Map<String, String> errors = new LinkedHashMap<>();
if (user == null) { if (user == null) {
errors.put("user", "User account details are required."); errors.put("user", "请填写用户账户详情。");
return errors; return errors;
} }
if (requireId && user.getId() <= 0) { if (requireId && user.getId() <= 0) {
errors.put("id", "Select a valid user account."); errors.put("id", "请选择有效的用户账户。");
} }
if (!requireId) { if (!requireId) {
requireLength(errors, "username", user.getUsername(), "Username", 64); requireLength(errors, "username", user.getUsername(), "用户名", 64);
} }
requireLength(errors, "displayName", user.getDisplayName(), "Display name", 100); requireLength(errors, "displayName", user.getDisplayName(), "显示名称", 100);
if (user.getRole() == null) { if (user.getRole() == null) {
errors.put("role", "Select a role."); errors.put("role", "请选择角色。");
} }
validatePassword(errors, password, requirePassword); validatePassword(errors, password, requirePassword);
return errors; return errors;
@@ -256,12 +256,12 @@ public class UserAccountServiceImpl implements UserAccountService {
String trimmed = password == null ? "" : password.trim(); String trimmed = password == null ? "" : password.trim();
if (trimmed.isEmpty()) { if (trimmed.isEmpty()) {
if (required) { if (required) {
errors.put("password", "Password is required."); errors.put("password", "请填写密码。");
} }
return; return;
} }
if (password.length() > 128) { if (password.length() > 128) {
errors.put("password", "Password must be 128 characters or fewer."); errors.put("password", "密码不能超过 128 个字符。");
} }
} }
@@ -279,11 +279,11 @@ public class UserAccountServiceImpl implements UserAccountService {
private void requireLength(Map<String, String> errors, String field, String value, String label, int maxLength) { private void requireLength(Map<String, String> errors, String field, String value, String label, int maxLength) {
if (value == null || value.isEmpty()) { if (value == null || value.isEmpty()) {
errors.put(field, label + " is required."); errors.put(field, "请填写" + label + "");
return; return;
} }
if (value.length() > maxLength) { if (value.length() > maxLength) {
errors.put(field, label + " must be " + maxLength + " characters or fewer."); errors.put(field, label + "不能超过 " + maxLength + " 个字符。");
} }
} }
@@ -2,18 +2,18 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title><c:out value="${formTitle}" /> - MZH Library</title> <title><c:out value="${formTitle}" /> - MZH 图书馆</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css"> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css">
</head> </head>
<body> <body>
<%@ include file="/WEB-INF/jsp/common/header.jspf" %> <%@ include file="/WEB-INF/jsp/common/header.jspf" %>
<main class="page-shell"> <main class="page-shell">
<section class="form-panel" aria-labelledby="user-form-title"> <section class="form-panel" aria-labelledby="user-form-title">
<p class="eyebrow">Administration</p> <p class="eyebrow">系统管理</p>
<h1 id="user-form-title"><c:out value="${formTitle}" /></h1> <h1 id="user-form-title"><c:out value="${formTitle}" /></h1>
<c:if test="${not empty errorMessage}"> <c:if test="${not empty errorMessage}">
@@ -36,7 +36,7 @@
<div class="form-grid"> <div class="form-grid">
<div class="form-field"> <div class="form-field">
<label for="username">Username</label> <label for="username">用户名</label>
<c:choose> <c:choose>
<c:when test="${user.id > 0}"> <c:when test="${user.id > 0}">
<input id="username" type="text" value="${fn:escapeXml(usernameValue)}" disabled> <input id="username" type="text" value="${fn:escapeXml(usernameValue)}" disabled>
@@ -51,7 +51,7 @@
</div> </div>
<div class="form-field"> <div class="form-field">
<label for="displayName">Display name</label> <label for="displayName">显示名称</label>
<input id="displayName" name="displayName" type="text" <input id="displayName" name="displayName" type="text"
value="${fn:escapeXml(displayNameValue)}" required> value="${fn:escapeXml(displayNameValue)}" required>
<c:if test="${not empty errors.displayName}"> <c:if test="${not empty errors.displayName}">
@@ -60,9 +60,9 @@
</div> </div>
<div class="form-field"> <div class="form-field">
<label for="role">Role</label> <label for="role">角色</label>
<select id="role" name="role" required> <select id="role" name="role" required>
<option value="">Select role</option> <option value="">请选择角色</option>
<c:forEach var="role" items="${roles}"> <c:forEach var="role" items="${roles}">
<option value="${role.code}" <c:if test="${roleValue == role.code}">selected</c:if>> <option value="${role.code}" <c:if test="${roleValue == role.code}">selected</c:if>>
<c:out value="${role.displayName}" /> <c:out value="${role.displayName}" />
@@ -75,13 +75,13 @@
</div> </div>
<div class="form-field"> <div class="form-field">
<label for="active">Active state</label> <label for="active">启用状态</label>
<select id="active" name="active" required> <select id="active" name="active" required>
<option value="true" <c:if test="${activeValue == true or activeValue == 'true'}">selected</c:if>> <option value="true" <c:if test="${activeValue == true or activeValue == 'true'}">selected</c:if>>
Active 启用
</option> </option>
<option value="false" <c:if test="${activeValue == false or activeValue == 'false'}">selected</c:if>> <option value="false" <c:if test="${activeValue == false or activeValue == 'false'}">selected</c:if>>
Inactive 停用
</option> </option>
</select> </select>
<c:if test="${not empty errors.active}"> <c:if test="${not empty errors.active}">
@@ -92,8 +92,8 @@
<div class="form-field"> <div class="form-field">
<label for="password"> <label for="password">
<c:choose> <c:choose>
<c:when test="${user.id > 0}">New password</c:when> <c:when test="${user.id > 0}">新密码</c:when>
<c:otherwise>Password</c:otherwise> <c:otherwise>密码</c:otherwise>
</c:choose> </c:choose>
</label> </label>
<c:choose> <c:choose>
@@ -111,8 +111,8 @@
</div> </div>
<div class="form-actions"> <div class="form-actions">
<button class="button button-primary" type="submit">Save</button> <button class="button button-primary" type="submit">保存</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/users">Cancel</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/admin/users">取消</a>
</div> </div>
</form> </form>
</section> </section>
@@ -2,11 +2,11 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Manage Users - MZH Library</title> <title>用户管理 - MZH 图书馆</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css"> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css">
</head> </head>
<body> <body>
@@ -14,11 +14,11 @@
<main class="page-shell"> <main class="page-shell">
<section class="dashboard-hero catalog-hero" aria-labelledby="manage-users-title"> <section class="dashboard-hero catalog-hero" aria-labelledby="manage-users-title">
<div> <div>
<p class="eyebrow">Administration</p> <p class="eyebrow">系统管理</p>
<h1 id="manage-users-title">Manage users</h1> <h1 id="manage-users-title">管理用户</h1>
<p>Create, update, deactivate, and review administrator, librarian, and reader accounts.</p> <p>创建、更新、停用和查看管理员、馆员与读者账户。</p>
</div> </div>
<a class="button button-primary" href="${pageContext.request.contextPath}/admin/users/new">New user</a> <a class="button button-primary" href="${pageContext.request.contextPath}/admin/users/new">新增用户</a>
</section> </section>
<c:if test="${not empty successMessage}"> <c:if test="${not empty successMessage}">
@@ -32,10 +32,10 @@
</div> </div>
</c:if> </c:if>
<section class="toolbar-panel" aria-label="User management search"> <section class="toolbar-panel" aria-label="用户管理检索">
<form class="search-form" action="${pageContext.request.contextPath}/admin/users" method="get"> <form class="search-form" action="${pageContext.request.contextPath}/admin/users" method="get">
<div class="search-field"> <div class="search-field">
<label for="keyword">Keyword</label> <label for="keyword">关键词</label>
<input id="keyword" name="keyword" type="text" value="${fn:escapeXml(criteria.keyword)}"> <input id="keyword" name="keyword" type="text" value="${fn:escapeXml(criteria.keyword)}">
<c:if test="${not empty errors.keyword}"> <c:if test="${not empty errors.keyword}">
<span class="field-error"><c:out value="${errors.keyword}" /></span> <span class="field-error"><c:out value="${errors.keyword}" /></span>
@@ -43,9 +43,9 @@
</div> </div>
<div class="search-field"> <div class="search-field">
<label for="role">Role</label> <label for="role">角色</label>
<select id="role" name="role"> <select id="role" name="role">
<option value="">All roles</option> <option value="">全部角色</option>
<c:forEach var="role" items="${roles}"> <c:forEach var="role" items="${roles}">
<option value="${role.code}" <c:if test="${criteria.roleCode == role.code}">selected</c:if>> <option value="${role.code}" <c:if test="${criteria.roleCode == role.code}">selected</c:if>>
<c:out value="${role.displayName}" /> <c:out value="${role.displayName}" />
@@ -58,40 +58,40 @@
</div> </div>
<div class="search-field"> <div class="search-field">
<label for="active">Active state</label> <label for="active">启用状态</label>
<select id="active" name="active"> <select id="active" name="active">
<option value="">All states</option> <option value="">全部状态</option>
<option value="active" <c:if test="${criteria.activeStatus == 'active'}">selected</c:if>>Active</option> <option value="active" <c:if test="${criteria.activeStatus == 'active'}">selected</c:if>>启用</option>
<option value="inactive" <c:if test="${criteria.activeStatus == 'inactive'}">selected</c:if>>Inactive</option> <option value="inactive" <c:if test="${criteria.activeStatus == 'inactive'}">selected</c:if>>停用</option>
</select> </select>
<c:if test="${not empty errors.active}"> <c:if test="${not empty errors.active}">
<span class="field-error"><c:out value="${errors.active}" /></span> <span class="field-error"><c:out value="${errors.active}" /></span>
</c:if> </c:if>
</div> </div>
<button class="button button-primary" type="submit">Search</button> <button class="button button-primary" type="submit">检索</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/users">Clear</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/admin/users">清空</a>
</form> </form>
</section> </section>
<section class="table-panel" aria-labelledby="user-results-title"> <section class="table-panel" aria-labelledby="user-results-title">
<h2 id="user-results-title">User accounts</h2> <h2 id="user-results-title">用户账户</h2>
<c:choose> <c:choose>
<c:when test="${empty users}"> <c:when test="${empty users}">
<p class="empty-state">No user accounts match the current filters.</p> <p class="empty-state">没有符合当前筛选条件的用户账户。</p>
</c:when> </c:when>
<c:otherwise> <c:otherwise>
<div class="table-scroll"> <div class="table-scroll">
<table class="data-table user-table"> <table class="data-table user-table">
<thead> <thead>
<tr> <tr>
<th scope="col">Username</th> <th scope="col">用户名</th>
<th scope="col">Display name</th> <th scope="col">显示名称</th>
<th scope="col">Role</th> <th scope="col">角色</th>
<th scope="col">State</th> <th scope="col">状态</th>
<th scope="col">Created</th> <th scope="col">创建时间</th>
<th scope="col">Updated</th> <th scope="col">更新时间</th>
<th scope="col">Actions</th> <th scope="col">操作</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -110,17 +110,17 @@
<td> <td>
<div class="table-actions"> <div class="table-actions">
<a class="button button-secondary" <a class="button button-secondary"
href="${pageContext.request.contextPath}/admin/users/edit?id=${account.id}">Edit</a> href="${pageContext.request.contextPath}/admin/users/edit?id=${account.id}">编辑</a>
<c:choose> <c:choose>
<c:when test="${account.id == sessionScope.authenticatedUser.id or not account.active}"> <c:when test="${account.id == sessionScope.authenticatedUser.id or not account.active}">
<button class="button button-secondary" type="button" disabled>Deactivate</button> <button class="button button-secondary" type="button" disabled>停用</button>
</c:when> </c:when>
<c:otherwise> <c:otherwise>
<form action="${pageContext.request.contextPath}/admin/users/deactivate" <form action="${pageContext.request.contextPath}/admin/users/deactivate"
method="post" method="post"
onsubmit="return confirm('Deactivate this user account?');"> onsubmit="return confirm('确定停用这个用户账户吗?');">
<input type="hidden" name="id" value="${account.id}"> <input type="hidden" name="id" value="${account.id}">
<button class="button button-danger" type="submit">Deactivate</button> <button class="button button-danger" type="submit">停用</button>
</form> </form>
</c:otherwise> </c:otherwise>
</c:choose> </c:choose>
+7 -7
View File
@@ -2,11 +2,11 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Login - MZH Library</title> <title>登录 - MZH 图书馆</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css"> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css">
</head> </head>
<body class="auth-page"> <body class="auth-page">
@@ -14,8 +14,8 @@
<main class="auth-shell"> <main class="auth-shell">
<section class="login-panel" aria-labelledby="login-title"> <section class="login-panel" aria-labelledby="login-title">
<div> <div>
<p class="eyebrow">Library Management</p> <p class="eyebrow">图书馆管理</p>
<h1 id="login-title">Sign in</h1> <h1 id="login-title">登录</h1>
</div> </div>
<c:if test="${not empty errorMessage}"> <c:if test="${not empty errorMessage}">
@@ -26,7 +26,7 @@
<form class="login-form" action="${pageContext.request.contextPath}/login" method="post" novalidate> <form class="login-form" action="${pageContext.request.contextPath}/login" method="post" novalidate>
<input type="hidden" name="redirect" value="${fn:escapeXml(redirect)}"> <input type="hidden" name="redirect" value="${fn:escapeXml(redirect)}">
<label for="username">Username</label> <label for="username">用户名</label>
<input id="username" <input id="username"
name="username" name="username"
type="text" type="text"
@@ -34,14 +34,14 @@
autocomplete="username" autocomplete="username"
required> required>
<label for="password">Password</label> <label for="password">密码</label>
<input id="password" <input id="password"
name="password" name="password"
type="password" type="password"
autocomplete="current-password" autocomplete="current-password"
required> required>
<button class="button button-primary" type="submit">Sign in</button> <button class="button button-primary" type="submit">登录</button>
</form> </form>
</section> </section>
</main> </main>
@@ -1,27 +1,27 @@
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> <%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Unauthorized - MZH Library</title> <title>无权限 - MZH 图书馆</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css"> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css">
</head> </head>
<body> <body>
<%@ include file="/WEB-INF/jsp/common/header.jspf" %> <%@ include file="/WEB-INF/jsp/common/header.jspf" %>
<main class="page-shell"> <main class="page-shell">
<section class="notice-panel" aria-labelledby="unauthorized-title"> <section class="notice-panel" aria-labelledby="unauthorized-title">
<h1 id="unauthorized-title">Access denied</h1> <h1 id="unauthorized-title">无权访问</h1>
<p> <p>
<c:choose> <c:choose>
<c:when test="${not empty errorMessage}"> <c:when test="${not empty errorMessage}">
<c:out value="${errorMessage}" /> <c:out value="${errorMessage}" />
</c:when> </c:when>
<c:otherwise>You do not have permission to access this page.</c:otherwise> <c:otherwise>您无权访问此页面。</c:otherwise>
</c:choose> </c:choose>
</p> </p>
<a class="button button-primary" href="${pageContext.request.contextPath}/dashboard">Back to dashboard</a> <a class="button button-primary" href="${pageContext.request.contextPath}/dashboard">返回控制台</a>
</section> </section>
</main> </main>
</body> </body>
+22 -22
View File
@@ -2,20 +2,20 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Catalog - MZH Library</title> <title>馆藏检索 - MZH 图书馆</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css"> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css">
</head> </head>
<body> <body>
<%@ include file="/WEB-INF/jsp/common/header.jspf" %> <%@ include file="/WEB-INF/jsp/common/header.jspf" %>
<main class="page-shell"> <main class="page-shell">
<section class="dashboard-hero catalog-hero" aria-labelledby="catalog-title"> <section class="dashboard-hero catalog-hero" aria-labelledby="catalog-title">
<p class="eyebrow">Catalog</p> <p class="eyebrow">馆藏</p>
<h1 id="catalog-title">Book catalog</h1> <h1 id="catalog-title">馆藏检索</h1>
<p>Search the library collection by identifier, title, author, or category.</p> <p>按图书编号、书名、作者或分类检索馆藏。</p>
</section> </section>
<c:if test="${not empty errorMessage}"> <c:if test="${not empty errorMessage}">
@@ -24,27 +24,27 @@
</div> </div>
</c:if> </c:if>
<section class="toolbar-panel" aria-label="Catalog search"> <section class="toolbar-panel" aria-label="馆藏检索">
<form class="search-form" action="${pageContext.request.contextPath}/catalog" method="get"> <form class="search-form" action="${pageContext.request.contextPath}/catalog" method="get">
<div class="search-field"> <div class="search-field">
<label for="identifier">Book ID</label> <label for="identifier">图书编号</label>
<input id="identifier" name="identifier" type="text" value="${fn:escapeXml(criteria.identifier)}"> <input id="identifier" name="identifier" type="text" value="${fn:escapeXml(criteria.identifier)}">
</div> </div>
<div class="search-field"> <div class="search-field">
<label for="title">Title</label> <label for="title">书名</label>
<input id="title" name="title" type="text" value="${fn:escapeXml(criteria.title)}"> <input id="title" name="title" type="text" value="${fn:escapeXml(criteria.title)}">
</div> </div>
<div class="search-field"> <div class="search-field">
<label for="author">Author</label> <label for="author">作者</label>
<input id="author" name="author" type="text" value="${fn:escapeXml(criteria.author)}"> <input id="author" name="author" type="text" value="${fn:escapeXml(criteria.author)}">
</div> </div>
<div class="search-field"> <div class="search-field">
<label for="categoryId">Category</label> <label for="categoryId">分类</label>
<select id="categoryId" name="categoryId"> <select id="categoryId" name="categoryId">
<option value="">All categories</option> <option value="">全部分类</option>
<c:forEach var="category" items="${categories}"> <c:forEach var="category" items="${categories}">
<option value="${category.id}" <c:if test="${criteria.categoryId == category.id}">selected</c:if>> <option value="${category.id}" <c:if test="${criteria.categoryId == category.id}">selected</c:if>>
<c:out value="${category.name}" /> <c:out value="${category.name}" />
@@ -56,31 +56,31 @@
</c:if> </c:if>
</div> </div>
<button class="button button-primary" type="submit">Search</button> <button class="button button-primary" type="submit">检索</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/catalog">Clear</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/catalog">清空</a>
<c:if test="${canManageBooks}"> <c:if test="${canManageBooks}">
<a class="button button-secondary" href="${pageContext.request.contextPath}/books">Manage books</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/books">管理图书</a>
</c:if> </c:if>
</form> </form>
</section> </section>
<section class="table-panel" aria-labelledby="catalog-results-title"> <section class="table-panel" aria-labelledby="catalog-results-title">
<h2 id="catalog-results-title">Results</h2> <h2 id="catalog-results-title">检索结果</h2>
<c:choose> <c:choose>
<c:when test="${empty books}"> <c:when test="${empty books}">
<p class="empty-state">No books match the current filters.</p> <p class="empty-state">没有符合当前筛选条件的图书。</p>
</c:when> </c:when>
<c:otherwise> <c:otherwise>
<div class="table-scroll"> <div class="table-scroll">
<table class="data-table"> <table class="data-table">
<thead> <thead>
<tr> <tr>
<th scope="col">Book ID</th> <th scope="col">图书编号</th>
<th scope="col">Title</th> <th scope="col">书名</th>
<th scope="col">Author</th> <th scope="col">作者</th>
<th scope="col">Category</th> <th scope="col">分类</th>
<th scope="col">Copies</th> <th scope="col">馆藏数量</th>
<th scope="col">Status</th> <th scope="col">状态</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -1,11 +1,11 @@
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> <%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Manage Categories - MZH Library</title> <title>分类管理 - MZH 图书馆</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css"> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css">
</head> </head>
<body> <body>
@@ -13,13 +13,13 @@
<main class="page-shell"> <main class="page-shell">
<section class="dashboard-hero catalog-hero" aria-labelledby="category-title"> <section class="dashboard-hero catalog-hero" aria-labelledby="category-title">
<div> <div>
<p class="eyebrow">Book Management</p> <p class="eyebrow">图书管理</p>
<h1 id="category-title">Manage categories</h1> <h1 id="category-title">管理分类</h1>
<p>Maintain catalog groupings used by book records and search filters.</p> <p>维护图书记录和检索筛选使用的馆藏分组。</p>
</div> </div>
<div class="hero-actions"> <div class="hero-actions">
<a class="button button-primary" href="${pageContext.request.contextPath}/book-categories/new">New category</a> <a class="button button-primary" href="${pageContext.request.contextPath}/book-categories/new">新增分类</a>
<a class="button button-secondary" href="${pageContext.request.contextPath}/books">Manage books</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/books">管理图书</a>
</div> </div>
</section> </section>
@@ -35,19 +35,19 @@
</c:if> </c:if>
<section class="table-panel" aria-labelledby="category-results-title"> <section class="table-panel" aria-labelledby="category-results-title">
<h2 id="category-results-title">Category records</h2> <h2 id="category-results-title">分类记录</h2>
<c:choose> <c:choose>
<c:when test="${empty categories}"> <c:when test="${empty categories}">
<p class="empty-state">No categories have been created yet.</p> <p class="empty-state">尚未创建分类。</p>
</c:when> </c:when>
<c:otherwise> <c:otherwise>
<div class="table-scroll"> <div class="table-scroll">
<table class="data-table category-table"> <table class="data-table category-table">
<thead> <thead>
<tr> <tr>
<th scope="col">Name</th> <th scope="col">名称</th>
<th scope="col">Description</th> <th scope="col">说明</th>
<th scope="col">Actions</th> <th scope="col">操作</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -57,7 +57,7 @@
<td> <td>
<c:choose> <c:choose>
<c:when test="${empty category.description}"> <c:when test="${empty category.description}">
<span class="muted-text">No description</span> <span class="muted-text">无说明</span>
</c:when> </c:when>
<c:otherwise> <c:otherwise>
<c:out value="${category.description}" /> <c:out value="${category.description}" />
@@ -67,12 +67,12 @@
<td> <td>
<div class="table-actions"> <div class="table-actions">
<a class="button button-secondary" <a class="button button-secondary"
href="${pageContext.request.contextPath}/book-categories/edit?id=${category.id}">Edit</a> href="${pageContext.request.contextPath}/book-categories/edit?id=${category.id}">编辑</a>
<form action="${pageContext.request.contextPath}/book-categories/delete" <form action="${pageContext.request.contextPath}/book-categories/delete"
method="post" method="post"
onsubmit="return confirm('Delete this category?');"> onsubmit="return confirm('确定删除这个分类吗?');">
<input type="hidden" name="id" value="${category.id}"> <input type="hidden" name="id" value="${category.id}">
<button class="button button-danger" type="submit">Delete</button> <button class="button button-danger" type="submit">删除</button>
</form> </form>
</div> </div>
</td> </td>
@@ -2,18 +2,18 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title><c:out value="${formTitle}" /> - MZH Library</title> <title><c:out value="${formTitle}" /> - MZH 图书馆</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css"> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css">
</head> </head>
<body> <body>
<%@ include file="/WEB-INF/jsp/common/header.jspf" %> <%@ include file="/WEB-INF/jsp/common/header.jspf" %>
<main class="page-shell"> <main class="page-shell">
<section class="form-panel" aria-labelledby="category-form-title"> <section class="form-panel" aria-labelledby="category-form-title">
<p class="eyebrow">Book Management</p> <p class="eyebrow">图书管理</p>
<h1 id="category-form-title"><c:out value="${formTitle}" /></h1> <h1 id="category-form-title"><c:out value="${formTitle}" /></h1>
<c:if test="${not empty errorMessage}"> <c:if test="${not empty errorMessage}">
@@ -33,7 +33,7 @@
<div class="form-grid"> <div class="form-grid">
<div class="form-field"> <div class="form-field">
<label for="name">Category name</label> <label for="name">分类名称</label>
<input id="name" name="name" type="text" value="${fn:escapeXml(nameValue)}" required> <input id="name" name="name" type="text" value="${fn:escapeXml(nameValue)}" required>
<c:if test="${not empty errors.name}"> <c:if test="${not empty errors.name}">
<span class="field-error"><c:out value="${errors.name}" /></span> <span class="field-error"><c:out value="${errors.name}" /></span>
@@ -41,7 +41,7 @@
</div> </div>
<div class="form-field form-field-wide"> <div class="form-field form-field-wide">
<label for="description">Description</label> <label for="description">说明</label>
<textarea id="description" name="description" rows="4">${fn:escapeXml(descriptionValue)}</textarea> <textarea id="description" name="description" rows="4">${fn:escapeXml(descriptionValue)}</textarea>
<c:if test="${not empty errors.description}"> <c:if test="${not empty errors.description}">
<span class="field-error"><c:out value="${errors.description}" /></span> <span class="field-error"><c:out value="${errors.description}" /></span>
@@ -56,8 +56,8 @@
</c:if> </c:if>
<div class="form-actions"> <div class="form-actions">
<button class="button button-primary" type="submit">Save</button> <button class="button button-primary" type="submit">保存</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">Cancel</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">取消</a>
</div> </div>
</form> </form>
</section> </section>
+14 -14
View File
@@ -2,18 +2,18 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title><c:out value="${formTitle}" /> - MZH Library</title> <title><c:out value="${formTitle}" /> - MZH 图书馆</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css"> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css">
</head> </head>
<body> <body>
<%@ include file="/WEB-INF/jsp/common/header.jspf" %> <%@ include file="/WEB-INF/jsp/common/header.jspf" %>
<main class="page-shell"> <main class="page-shell">
<section class="form-panel" aria-labelledby="book-form-title"> <section class="form-panel" aria-labelledby="book-form-title">
<p class="eyebrow">Book Management</p> <p class="eyebrow">图书管理</p>
<h1 id="book-form-title"><c:out value="${formTitle}" /></h1> <h1 id="book-form-title"><c:out value="${formTitle}" /></h1>
<c:if test="${not empty errorMessage}"> <c:if test="${not empty errorMessage}">
@@ -38,7 +38,7 @@
<div class="form-grid"> <div class="form-grid">
<div class="form-field"> <div class="form-field">
<label for="identifier">Book ID</label> <label for="identifier">图书编号</label>
<input id="identifier" name="identifier" type="text" value="${fn:escapeXml(identifierValue)}" required> <input id="identifier" name="identifier" type="text" value="${fn:escapeXml(identifierValue)}" required>
<c:if test="${not empty errors.identifier}"> <c:if test="${not empty errors.identifier}">
<span class="field-error"><c:out value="${errors.identifier}" /></span> <span class="field-error"><c:out value="${errors.identifier}" /></span>
@@ -46,7 +46,7 @@
</div> </div>
<div class="form-field"> <div class="form-field">
<label for="title">Title</label> <label for="title">书名</label>
<input id="title" name="title" type="text" value="${fn:escapeXml(titleValue)}" required> <input id="title" name="title" type="text" value="${fn:escapeXml(titleValue)}" required>
<c:if test="${not empty errors.title}"> <c:if test="${not empty errors.title}">
<span class="field-error"><c:out value="${errors.title}" /></span> <span class="field-error"><c:out value="${errors.title}" /></span>
@@ -54,7 +54,7 @@
</div> </div>
<div class="form-field"> <div class="form-field">
<label for="author">Author</label> <label for="author">作者</label>
<input id="author" name="author" type="text" value="${fn:escapeXml(authorValue)}" required> <input id="author" name="author" type="text" value="${fn:escapeXml(authorValue)}" required>
<c:if test="${not empty errors.author}"> <c:if test="${not empty errors.author}">
<span class="field-error"><c:out value="${errors.author}" /></span> <span class="field-error"><c:out value="${errors.author}" /></span>
@@ -62,9 +62,9 @@
</div> </div>
<div class="form-field"> <div class="form-field">
<label for="categoryId">Category</label> <label for="categoryId">分类</label>
<select id="categoryId" name="categoryId" required> <select id="categoryId" name="categoryId" required>
<option value="">Select category</option> <option value="">请选择分类</option>
<c:forEach var="category" items="${categories}"> <c:forEach var="category" items="${categories}">
<option value="${category.id}" <c:if test="${categoryValue == category.id}">selected</c:if>> <option value="${category.id}" <c:if test="${categoryValue == category.id}">selected</c:if>>
<c:out value="${category.name}" /> <c:out value="${category.name}" />
@@ -77,7 +77,7 @@
</div> </div>
<div class="form-field"> <div class="form-field">
<label for="totalCopies">Total copies</label> <label for="totalCopies">馆藏总数</label>
<input id="totalCopies" name="totalCopies" type="number" min="0" value="${fn:escapeXml(totalCopiesValue)}" required> <input id="totalCopies" name="totalCopies" type="number" min="0" value="${fn:escapeXml(totalCopiesValue)}" required>
<c:if test="${not empty errors.totalCopies}"> <c:if test="${not empty errors.totalCopies}">
<span class="field-error"><c:out value="${errors.totalCopies}" /></span> <span class="field-error"><c:out value="${errors.totalCopies}" /></span>
@@ -85,7 +85,7 @@
</div> </div>
<div class="form-field"> <div class="form-field">
<label for="availableCopies">Available copies</label> <label for="availableCopies">可借数量</label>
<input id="availableCopies" name="availableCopies" type="number" min="0" value="${fn:escapeXml(availableCopiesValue)}" required> <input id="availableCopies" name="availableCopies" type="number" min="0" value="${fn:escapeXml(availableCopiesValue)}" required>
<c:if test="${not empty errors.availableCopies}"> <c:if test="${not empty errors.availableCopies}">
<span class="field-error"><c:out value="${errors.availableCopies}" /></span> <span class="field-error"><c:out value="${errors.availableCopies}" /></span>
@@ -93,9 +93,9 @@
</div> </div>
<div class="form-field"> <div class="form-field">
<label for="status">Status</label> <label for="status">状态</label>
<select id="status" name="status" required> <select id="status" name="status" required>
<option value="">Select status</option> <option value="">请选择状态</option>
<c:forEach var="status" items="${statuses}"> <c:forEach var="status" items="${statuses}">
<option value="${status.code}" <c:if test="${statusValue == status.code}">selected</c:if>> <option value="${status.code}" <c:if test="${statusValue == status.code}">selected</c:if>>
<c:out value="${status.displayName}" /> <c:out value="${status.displayName}" />
@@ -109,8 +109,8 @@
</div> </div>
<div class="form-actions"> <div class="form-actions">
<button class="button button-primary" type="submit">Save</button> <button class="button button-primary" type="submit">保存</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/books">Cancel</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/books">取消</a>
</div> </div>
</form> </form>
</section> </section>
+29 -29
View File
@@ -2,23 +2,23 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Manage Books - MZH Library</title> <title>图书管理 - MZH 图书馆</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css"> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css">
</head> </head>
<body> <body>
<%@ include file="/WEB-INF/jsp/common/header.jspf" %> <%@ include file="/WEB-INF/jsp/common/header.jspf" %>
<main class="page-shell"> <main class="page-shell">
<section class="dashboard-hero catalog-hero" aria-labelledby="manage-title"> <section class="dashboard-hero catalog-hero" aria-labelledby="manage-title">
<p class="eyebrow">Book Management</p> <p class="eyebrow">图书管理</p>
<h1 id="manage-title">Manage books</h1> <h1 id="manage-title">管理图书</h1>
<p>Create, update, delete, and review inventory for catalog records.</p> <p>创建、更新、删除和查看馆藏记录的库存信息。</p>
<div class="hero-actions"> <div class="hero-actions">
<a class="button button-primary" href="${pageContext.request.contextPath}/books/new">New book</a> <a class="button button-primary" href="${pageContext.request.contextPath}/books/new">新增图书</a>
<a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">Categories</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">分类</a>
</div> </div>
</section> </section>
@@ -33,27 +33,27 @@
</div> </div>
</c:if> </c:if>
<section class="toolbar-panel" aria-label="Book management search"> <section class="toolbar-panel" aria-label="图书管理检索">
<form class="search-form" action="${pageContext.request.contextPath}/books" method="get"> <form class="search-form" action="${pageContext.request.contextPath}/books" method="get">
<div class="search-field"> <div class="search-field">
<label for="identifier">Book ID</label> <label for="identifier">图书编号</label>
<input id="identifier" name="identifier" type="text" value="${fn:escapeXml(criteria.identifier)}"> <input id="identifier" name="identifier" type="text" value="${fn:escapeXml(criteria.identifier)}">
</div> </div>
<div class="search-field"> <div class="search-field">
<label for="title">Title</label> <label for="title">书名</label>
<input id="title" name="title" type="text" value="${fn:escapeXml(criteria.title)}"> <input id="title" name="title" type="text" value="${fn:escapeXml(criteria.title)}">
</div> </div>
<div class="search-field"> <div class="search-field">
<label for="author">Author</label> <label for="author">作者</label>
<input id="author" name="author" type="text" value="${fn:escapeXml(criteria.author)}"> <input id="author" name="author" type="text" value="${fn:escapeXml(criteria.author)}">
</div> </div>
<div class="search-field"> <div class="search-field">
<label for="categoryId">Category</label> <label for="categoryId">分类</label>
<select id="categoryId" name="categoryId"> <select id="categoryId" name="categoryId">
<option value="">All categories</option> <option value="">全部分类</option>
<c:forEach var="category" items="${categories}"> <c:forEach var="category" items="${categories}">
<option value="${category.id}" <c:if test="${criteria.categoryId == category.id}">selected</c:if>> <option value="${category.id}" <c:if test="${criteria.categoryId == category.id}">selected</c:if>>
<c:out value="${category.name}" /> <c:out value="${category.name}" />
@@ -65,31 +65,31 @@
</c:if> </c:if>
</div> </div>
<button class="button button-primary" type="submit">Search</button> <button class="button button-primary" type="submit">检索</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/books">Clear</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/books">清空</a>
<a class="button button-secondary" href="${pageContext.request.contextPath}/catalog">View catalog</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/catalog">查看馆藏</a>
<a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">Categories</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">分类</a>
</form> </form>
</section> </section>
<section class="table-panel" aria-labelledby="management-results-title"> <section class="table-panel" aria-labelledby="management-results-title">
<h2 id="management-results-title">Book records</h2> <h2 id="management-results-title">图书记录</h2>
<c:choose> <c:choose>
<c:when test="${empty books}"> <c:when test="${empty books}">
<p class="empty-state">No book records match the current filters.</p> <p class="empty-state">没有符合当前筛选条件的图书记录。</p>
</c:when> </c:when>
<c:otherwise> <c:otherwise>
<div class="table-scroll"> <div class="table-scroll">
<table class="data-table"> <table class="data-table">
<thead> <thead>
<tr> <tr>
<th scope="col">Book ID</th> <th scope="col">图书编号</th>
<th scope="col">Title</th> <th scope="col">书名</th>
<th scope="col">Author</th> <th scope="col">作者</th>
<th scope="col">Category</th> <th scope="col">分类</th>
<th scope="col">Copies</th> <th scope="col">馆藏数量</th>
<th scope="col">Status</th> <th scope="col">状态</th>
<th scope="col">Actions</th> <th scope="col">操作</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -108,12 +108,12 @@
<td> <td>
<div class="table-actions"> <div class="table-actions">
<a class="button button-secondary" <a class="button button-secondary"
href="${pageContext.request.contextPath}/books/edit?id=${book.id}">Edit</a> href="${pageContext.request.contextPath}/books/edit?id=${book.id}">编辑</a>
<form action="${pageContext.request.contextPath}/books/delete" <form action="${pageContext.request.contextPath}/books/delete"
method="post" method="post"
onsubmit="return confirm('Delete this book record?');"> onsubmit="return confirm('确定删除这条图书记录吗?');">
<input type="hidden" name="id" value="${book.id}"> <input type="hidden" name="id" value="${book.id}">
<button class="button button-danger" type="submit">Delete</button> <button class="button button-danger" type="submit">删除</button>
</form> </form>
</div> </div>
</td> </td>
@@ -2,19 +2,19 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>New Borrow - MZH Library</title> <title>新增借阅 - MZH 图书馆</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css"> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css">
</head> </head>
<body> <body>
<%@ include file="/WEB-INF/jsp/common/header.jspf" %> <%@ include file="/WEB-INF/jsp/common/header.jspf" %>
<main class="page-shell"> <main class="page-shell">
<section class="form-panel" aria-labelledby="borrow-form-title"> <section class="form-panel" aria-labelledby="borrow-form-title">
<p class="eyebrow">Borrowing Management</p> <p class="eyebrow">借阅管理</p>
<h1 id="borrow-form-title">New borrow</h1> <h1 id="borrow-form-title">新增借阅</h1>
<c:if test="${not empty errorMessage}"> <c:if test="${not empty errorMessage}">
<div class="message message-error" role="alert"> <div class="message message-error" role="alert">
@@ -28,7 +28,7 @@
<form class="borrow-form" action="${pageContext.request.contextPath}/borrowing/create" method="post" novalidate> <form class="borrow-form" action="${pageContext.request.contextPath}/borrowing/create" method="post" novalidate>
<div class="form-grid"> <div class="form-grid">
<div class="form-field"> <div class="form-field">
<label for="readerIdentifier">Reader ID</label> <label for="readerIdentifier">读者编号</label>
<input id="readerIdentifier" name="readerIdentifier" type="text" <input id="readerIdentifier" name="readerIdentifier" type="text"
value="${fn:escapeXml(readerIdentifierValue)}" required> value="${fn:escapeXml(readerIdentifierValue)}" required>
<c:if test="${not empty errors.readerIdentifier}"> <c:if test="${not empty errors.readerIdentifier}">
@@ -37,7 +37,7 @@
</div> </div>
<div class="form-field"> <div class="form-field">
<label for="bookIdentifier">Book ID</label> <label for="bookIdentifier">图书编号</label>
<input id="bookIdentifier" name="bookIdentifier" type="text" <input id="bookIdentifier" name="bookIdentifier" type="text"
value="${fn:escapeXml(bookIdentifierValue)}" required> value="${fn:escapeXml(bookIdentifierValue)}" required>
<c:if test="${not empty errors.bookIdentifier}"> <c:if test="${not empty errors.bookIdentifier}">
@@ -47,8 +47,8 @@
</div> </div>
<div class="form-actions"> <div class="form-actions">
<button class="button button-primary" type="submit">Borrow</button> <button class="button button-primary" type="submit">借出</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/borrowing">Cancel</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/borrowing">取消</a>
</div> </div>
</form> </form>
</section> </section>
@@ -2,11 +2,11 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Borrowing Management - MZH Library</title> <title>借阅管理 - MZH 图书馆</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css"> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css">
</head> </head>
<body> <body>
@@ -14,11 +14,11 @@
<main class="page-shell"> <main class="page-shell">
<section class="dashboard-hero catalog-hero" aria-labelledby="borrowing-title"> <section class="dashboard-hero catalog-hero" aria-labelledby="borrowing-title">
<div> <div>
<p class="eyebrow">Borrowing Management</p> <p class="eyebrow">借阅管理</p>
<h1 id="borrowing-title">Manage borrowing</h1> <h1 id="borrowing-title">管理借阅</h1>
<p>Create borrow records, process returns, renew active loans, and review overdue items.</p> <p>创建借阅记录、处理归还、续借有效借阅并查看逾期项目。</p>
</div> </div>
<a class="button button-primary" href="${pageContext.request.contextPath}/borrowing/new">New borrow</a> <a class="button button-primary" href="${pageContext.request.contextPath}/borrowing/new">新增借阅</a>
</section> </section>
<c:if test="${not empty successMessage}"> <c:if test="${not empty successMessage}">
@@ -32,31 +32,31 @@
</div> </div>
</c:if> </c:if>
<section class="toolbar-panel" aria-label="Borrowing search"> <section class="toolbar-panel" aria-label="借阅检索">
<form class="search-form borrowing-search-form" action="${pageContext.request.contextPath}/borrowing" method="get"> <form class="search-form borrowing-search-form" action="${pageContext.request.contextPath}/borrowing" method="get">
<div class="search-field"> <div class="search-field">
<label for="readerIdentifier">Reader ID</label> <label for="readerIdentifier">读者编号</label>
<input id="readerIdentifier" name="readerIdentifier" type="text" <input id="readerIdentifier" name="readerIdentifier" type="text"
value="${fn:escapeXml(criteria.readerIdentifier)}"> value="${fn:escapeXml(criteria.readerIdentifier)}">
</div> </div>
<div class="search-field"> <div class="search-field">
<label for="bookIdentifier">Book ID</label> <label for="bookIdentifier">图书编号</label>
<input id="bookIdentifier" name="bookIdentifier" type="text" <input id="bookIdentifier" name="bookIdentifier" type="text"
value="${fn:escapeXml(criteria.bookIdentifier)}"> value="${fn:escapeXml(criteria.bookIdentifier)}">
</div> </div>
<div class="search-field"> <div class="search-field">
<label for="status">Status</label> <label for="status">状态</label>
<select id="status" name="status"> <select id="status" name="status">
<option value="">All statuses</option> <option value="">全部状态</option>
<c:forEach var="status" items="${statuses}"> <c:forEach var="status" items="${statuses}">
<option value="${status.code}" <c:if test="${criteria.statusCode == status.code}">selected</c:if>> <option value="${status.code}" <c:if test="${criteria.statusCode == status.code}">selected</c:if>>
<c:out value="${status.displayName}" /> <c:out value="${status.displayName}" />
</option> </option>
</c:forEach> </c:forEach>
<option value="${overdueStatus}" <c:if test="${criteria.statusCode == overdueStatus}">selected</c:if>> <option value="${overdueStatus}" <c:if test="${criteria.statusCode == overdueStatus}">selected</c:if>>
Overdue 逾期
</option> </option>
</select> </select>
<c:if test="${not empty errors.status}"> <c:if test="${not empty errors.status}">
@@ -64,30 +64,30 @@
</c:if> </c:if>
</div> </div>
<button class="button button-primary" type="submit">Search</button> <button class="button button-primary" type="submit">检索</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/borrowing">Clear</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/borrowing">清空</a>
</form> </form>
</section> </section>
<section class="table-panel" aria-labelledby="borrowing-results-title"> <section class="table-panel" aria-labelledby="borrowing-results-title">
<h2 id="borrowing-results-title">Borrowing records</h2> <h2 id="borrowing-results-title">借阅记录</h2>
<c:choose> <c:choose>
<c:when test="${empty borrowRecords}"> <c:when test="${empty borrowRecords}">
<p class="empty-state">No borrowing records match the current filters.</p> <p class="empty-state">没有符合当前筛选条件的借阅记录。</p>
</c:when> </c:when>
<c:otherwise> <c:otherwise>
<div class="table-scroll"> <div class="table-scroll">
<table class="data-table borrowing-table"> <table class="data-table borrowing-table">
<thead> <thead>
<tr> <tr>
<th scope="col">Reader</th> <th scope="col">读者</th>
<th scope="col">Book</th> <th scope="col">图书</th>
<th scope="col">Borrowed</th> <th scope="col">借出时间</th>
<th scope="col">Due</th> <th scope="col">应还时间</th>
<th scope="col">Returned</th> <th scope="col">归还时间</th>
<th scope="col">Renewals</th> <th scope="col">续借次数</th>
<th scope="col">Status</th> <th scope="col">状态</th>
<th scope="col">Actions</th> <th scope="col">操作</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -108,7 +108,7 @@
<c:when test="${not empty record.returnedAtText}"> <c:when test="${not empty record.returnedAtText}">
<c:out value="${record.returnedAtText}" /> <c:out value="${record.returnedAtText}" />
</c:when> </c:when>
<c:otherwise>Not returned</c:otherwise> <c:otherwise>未归还</c:otherwise>
</c:choose> </c:choose>
</td> </td>
<td><c:out value="${record.renewalCount}" /> / <c:out value="${maxRenewals}" /></td> <td><c:out value="${record.renewalCount}" /> / <c:out value="${maxRenewals}" /></td>
@@ -123,22 +123,22 @@
<div class="table-actions"> <div class="table-actions">
<form action="${pageContext.request.contextPath}/borrowing/return" <form action="${pageContext.request.contextPath}/borrowing/return"
method="post" method="post"
onsubmit="return confirm('Return this book?');"> onsubmit="return confirm('确定归还这本书吗?');">
<input type="hidden" name="id" value="${record.id}"> <input type="hidden" name="id" value="${record.id}">
<button class="button button-secondary" type="submit">Return</button> <button class="button button-secondary" type="submit">归还</button>
</form> </form>
<c:if test="${record.renewalCount < maxRenewals}"> <c:if test="${record.renewalCount < maxRenewals}">
<form action="${pageContext.request.contextPath}/borrowing/renew" <form action="${pageContext.request.contextPath}/borrowing/renew"
method="post" method="post"
onsubmit="return confirm('Renew this loan?');"> onsubmit="return confirm('确定续借这条记录吗?');">
<input type="hidden" name="id" value="${record.id}"> <input type="hidden" name="id" value="${record.id}">
<button class="button button-secondary" type="submit">Renew</button> <button class="button button-secondary" type="submit">续借</button>
</form> </form>
</c:if> </c:if>
</div> </div>
</c:when> </c:when>
<c:otherwise> <c:otherwise>
<span class="muted-text">Complete</span> <span class="muted-text">已完成</span>
</c:otherwise> </c:otherwise>
</c:choose> </c:choose>
</td> </td>
+16 -16
View File
@@ -1,31 +1,31 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<header class="app-header"> <header class="app-header">
<a class="brand" href="${pageContext.request.contextPath}/dashboard">MZH Library</a> <a class="brand" href="${pageContext.request.contextPath}/dashboard">MZH 图书馆</a>
<c:if test="${not empty sessionScope.authenticatedUser}"> <c:if test="${not empty sessionScope.authenticatedUser}">
<nav class="top-nav" aria-label="Primary"> <nav class="top-nav" aria-label="主导航">
<a href="${pageContext.request.contextPath}/dashboard">Dashboard</a> <a href="${pageContext.request.contextPath}/dashboard">控制台</a>
<a href="${pageContext.request.contextPath}/catalog">Catalog</a> <a href="${pageContext.request.contextPath}/catalog">馆藏检索</a>
<c:if test="${sessionScope.userRole == 'administrator'}"> <c:if test="${sessionScope.userRole == 'administrator'}">
<a href="${pageContext.request.contextPath}/admin/home">Admin</a> <a href="${pageContext.request.contextPath}/admin/home">管理</a>
<a href="${pageContext.request.contextPath}/admin/users">Users</a> <a href="${pageContext.request.contextPath}/admin/users">用户</a>
<a href="${pageContext.request.contextPath}/admin/system-logs">Logs</a> <a href="${pageContext.request.contextPath}/admin/system-logs">日志</a>
</c:if> </c:if>
<c:if test="${sessionScope.userRole == 'administrator' or sessionScope.userRole == 'librarian'}"> <c:if test="${sessionScope.userRole == 'administrator' or sessionScope.userRole == 'librarian'}">
<a href="${pageContext.request.contextPath}/librarian/home">Librarian</a> <a href="${pageContext.request.contextPath}/librarian/home">馆员</a>
<a href="${pageContext.request.contextPath}/books">Books</a> <a href="${pageContext.request.contextPath}/books">图书</a>
<a href="${pageContext.request.contextPath}/book-categories">Categories</a> <a href="${pageContext.request.contextPath}/book-categories">分类</a>
<a href="${pageContext.request.contextPath}/readers">Readers</a> <a href="${pageContext.request.contextPath}/readers">读者</a>
<a href="${pageContext.request.contextPath}/borrowing">Borrowing</a> <a href="${pageContext.request.contextPath}/borrowing">借阅</a>
<a href="${pageContext.request.contextPath}/reports">Reports</a> <a href="${pageContext.request.contextPath}/reports">报表</a>
</c:if> </c:if>
<a href="${pageContext.request.contextPath}/reader/home">Reader</a> <a href="${pageContext.request.contextPath}/reader/home">读者中心</a>
<c:if test="${sessionScope.userRole == 'reader'}"> <c:if test="${sessionScope.userRole == 'reader'}">
<a href="${pageContext.request.contextPath}/reader/loans">My Loans</a> <a href="${pageContext.request.contextPath}/reader/loans">我的借阅</a>
</c:if> </c:if>
<span class="user-pill"> <span class="user-pill">
<c:out value="${sessionScope.authenticatedUser.displayName}" /> <c:out value="${sessionScope.authenticatedUser.displayName}" />
</span> </span>
<a class="button button-secondary" href="${pageContext.request.contextPath}/logout">Logout</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/logout">退出</a>
</nav> </nav>
</c:if> </c:if>
</header> </header>
+41 -41
View File
@@ -1,11 +1,11 @@
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> <%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dashboard - MZH Library</title> <title>控制台 - MZH 图书馆</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css"> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css">
</head> </head>
<body> <body>
@@ -15,86 +15,86 @@
<p class="eyebrow"> <p class="eyebrow">
<c:out value="${sessionScope.authenticatedUser.role.displayName}" /> <c:out value="${sessionScope.authenticatedUser.role.displayName}" />
</p> </p>
<h1 id="dashboard-title">Dashboard</h1> <h1 id="dashboard-title">控制台</h1>
<p>Signed in as <strong><c:out value="${sessionScope.authenticatedUser.displayName}" /></strong>.</p> <p>当前登录:<strong><c:out value="${sessionScope.authenticatedUser.displayName}" /></strong></p>
</section> </section>
<section class="card-grid" aria-label="Role workspaces"> <section class="card-grid" aria-label="角色工作区">
<c:if test="${sessionScope.userRole == 'administrator'}"> <c:if test="${sessionScope.userRole == 'administrator'}">
<article class="workspace-card"> <article class="workspace-card">
<h2>Administration</h2> <h2>系统管理</h2>
<p>Account, role, permission, and system-maintenance entry point.</p> <p>账户、角色、权限和系统维护入口。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/home">Open</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/admin/home">打开</a>
</article> </article>
<article class="workspace-card"> <article class="workspace-card">
<h2>User Management</h2> <h2>用户管理</h2>
<p>Create, update, deactivate, and review login accounts.</p> <p>创建、更新、停用和查看登录账户。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/users">Open</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/admin/users">打开</a>
</article> </article>
<article class="workspace-card"> <article class="workspace-card">
<h2>System Logs</h2> <h2>系统日志</h2>
<p>Review read-only audit entries for account and maintenance actions.</p> <p>查看账户与维护操作的只读审计记录。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/system-logs">Open</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/admin/system-logs">打开</a>
</article> </article>
</c:if> </c:if>
<c:if test="${sessionScope.userRole == 'administrator' or sessionScope.userRole == 'librarian'}"> <c:if test="${sessionScope.userRole == 'administrator' or sessionScope.userRole == 'librarian'}">
<article class="workspace-card"> <article class="workspace-card">
<h2>Librarian Workspace</h2> <h2>馆员工作台</h2>
<p>Book, reader, borrowing, return, renewal, and overdue entry point.</p> <p>图书、读者、借阅、归还、续借和逾期处理入口。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/librarian/home">Open</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/librarian/home">打开</a>
</article> </article>
<article class="workspace-card"> <article class="workspace-card">
<h2>Book Management</h2> <h2>图书管理</h2>
<p>Create, update, delete, and review book inventory records.</p> <p>创建、更新、删除和查看图书库存记录。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/books">Open</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/books">打开</a>
</article> </article>
<article class="workspace-card"> <article class="workspace-card">
<h2>Category Maintenance</h2> <h2>分类维护</h2>
<p>Maintain catalog categories used by book records and search filters.</p> <p>维护图书记录和检索筛选使用的馆藏分类。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">Open</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">打开</a>
</article> </article>
<article class="workspace-card"> <article class="workspace-card">
<h2>Reader Management</h2> <h2>读者管理</h2>
<p>Create, update, deactivate, and review reader eligibility records.</p> <p>创建、更新、停用和查看读者借阅资格记录。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/readers">Open</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/readers">打开</a>
</article> </article>
<article class="workspace-card"> <article class="workspace-card">
<h2>Borrowing Management</h2> <h2>借阅管理</h2>
<p>Create loans, process returns, renew active records, and review overdue items.</p> <p>创建借阅、处理归还、续借有效记录并查看逾期项目。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/borrowing">Open</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/borrowing">打开</a>
</article> </article>
<article class="workspace-card"> <article class="workspace-card">
<h2>Report Center</h2> <h2>报表中心</h2>
<p>Review inventory health, borrowing counts, overdue records, and popular books.</p> <p>查看库存状况、借阅统计、逾期记录和热门图书。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/reports">Open</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/reports">打开</a>
</article> </article>
</c:if> </c:if>
<article class="workspace-card"> <article class="workspace-card">
<h2>Book Catalog</h2> <h2>馆藏检索</h2>
<p>Search books by title, author, category, or book identifier.</p> <p>按书名、作者、分类或图书编号检索图书。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/catalog">Search</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/catalog">检索</a>
</article> </article>
<article class="workspace-card"> <article class="workspace-card">
<h2>Reader Center</h2> <h2>读者中心</h2>
<p>Reader self-service entry point for catalog access and loan history.</p> <p>读者自助访问馆藏和借阅历史的入口。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/reader/home">Open</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/reader/home">打开</a>
</article> </article>
<c:if test="${sessionScope.userRole == 'reader'}"> <c:if test="${sessionScope.userRole == 'reader'}">
<article class="workspace-card"> <article class="workspace-card">
<h2>My Loan History</h2> <h2>我的借阅历史</h2>
<p>Review your active, returned, and overdue borrowing records.</p> <p>查看您的在借、已还和逾期借阅记录。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/reader/loans">Open</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/reader/loans">打开</a>
</article> </article>
</c:if> </c:if>
</section> </section>
@@ -2,11 +2,11 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>System Logs - MZH Library</title> <title>系统日志 - MZH 图书馆</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css"> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css">
</head> </head>
<body> <body>
@@ -14,9 +14,9 @@
<main class="page-shell"> <main class="page-shell">
<section class="dashboard-hero catalog-hero" aria-labelledby="system-logs-title"> <section class="dashboard-hero catalog-hero" aria-labelledby="system-logs-title">
<div> <div>
<p class="eyebrow">System Maintenance</p> <p class="eyebrow">系统维护</p>
<h1 id="system-logs-title">System logs</h1> <h1 id="system-logs-title">系统日志</h1>
<p>Review administrative account changes and maintenance audit records.</p> <p>查看管理账户变更和维护审计记录。</p>
</div> </div>
</section> </section>
@@ -26,17 +26,22 @@
</div> </div>
</c:if> </c:if>
<section class="toolbar-panel" aria-label="System log search"> <section class="toolbar-panel" aria-label="系统日志检索">
<form class="search-form system-log-search-form" <form class="search-form system-log-search-form"
action="${pageContext.request.contextPath}/admin/system-logs" method="get"> action="${pageContext.request.contextPath}/admin/system-logs" method="get">
<div class="search-field"> <div class="search-field">
<label for="operationType">Operation</label> <label for="operationType">操作</label>
<select id="operationType" name="operationType"> <select id="operationType" name="operationType">
<option value="">All operations</option> <option value="">全部操作</option>
<c:forEach var="operationType" items="${operationTypes}"> <c:forEach var="operationType" items="${operationTypes}">
<option value="${fn:escapeXml(operationType)}" <option value="${fn:escapeXml(operationType)}"
<c:if test="${criteria.operationType == operationType}">selected</c:if>> <c:if test="${criteria.operationType == operationType}">selected</c:if>>
<c:out value="${operationType}" /> <c:choose>
<c:when test="${operationType == 'user.create'}">创建用户</c:when>
<c:when test="${operationType == 'user.update'}">更新用户</c:when>
<c:when test="${operationType == 'user.deactivate'}">停用用户</c:when>
<c:otherwise><c:out value="${operationType}" /></c:otherwise>
</c:choose>
</option> </option>
</c:forEach> </c:forEach>
<c:if test="${not empty criteria.operationType and empty operationTypes}"> <c:if test="${not empty criteria.operationType and empty operationTypes}">
@@ -51,7 +56,7 @@
</div> </div>
<div class="search-field"> <div class="search-field">
<label for="keyword">Keyword</label> <label for="keyword">关键词</label>
<input id="keyword" name="keyword" type="text" value="${fn:escapeXml(criteria.keyword)}"> <input id="keyword" name="keyword" type="text" value="${fn:escapeXml(criteria.keyword)}">
<c:if test="${not empty errors.keyword}"> <c:if test="${not empty errors.keyword}">
<span class="field-error"><c:out value="${errors.keyword}" /></span> <span class="field-error"><c:out value="${errors.keyword}" /></span>
@@ -59,7 +64,7 @@
</div> </div>
<div class="search-field"> <div class="search-field">
<label for="createdFrom">From</label> <label for="createdFrom">开始日期</label>
<input id="createdFrom" name="createdFrom" type="date" <input id="createdFrom" name="createdFrom" type="date"
value="${fn:escapeXml(criteria.createdFromText)}"> value="${fn:escapeXml(criteria.createdFromText)}">
<c:if test="${not empty errors.createdFrom}"> <c:if test="${not empty errors.createdFrom}">
@@ -68,7 +73,7 @@
</div> </div>
<div class="search-field"> <div class="search-field">
<label for="createdTo">To</label> <label for="createdTo">结束日期</label>
<input id="createdTo" name="createdTo" type="date" <input id="createdTo" name="createdTo" type="date"
value="${fn:escapeXml(criteria.createdToText)}"> value="${fn:escapeXml(criteria.createdToText)}">
<c:if test="${not empty errors.createdTo}"> <c:if test="${not empty errors.createdTo}">
@@ -76,29 +81,29 @@
</c:if> </c:if>
</div> </div>
<button class="button button-primary" type="submit">Search</button> <button class="button button-primary" type="submit">检索</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/system-logs">Clear</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/admin/system-logs">清空</a>
</form> </form>
</section> </section>
<section class="table-panel" aria-labelledby="system-log-results-title"> <section class="table-panel" aria-labelledby="system-log-results-title">
<h2 id="system-log-results-title">Log entries</h2> <h2 id="system-log-results-title">日志记录</h2>
<c:choose> <c:choose>
<c:when test="${empty logs}"> <c:when test="${empty logs}">
<p class="empty-state">No system logs match the current filters.</p> <p class="empty-state">没有符合当前筛选条件的系统日志。</p>
</c:when> </c:when>
<c:otherwise> <c:otherwise>
<div class="table-scroll"> <div class="table-scroll">
<table class="data-table system-log-table"> <table class="data-table system-log-table">
<thead> <thead>
<tr> <tr>
<th scope="col">Time</th> <th scope="col">时间</th>
<th scope="col">Operator</th> <th scope="col">操作人</th>
<th scope="col">Operation</th> <th scope="col">操作</th>
<th scope="col">Target</th> <th scope="col">目标</th>
<th scope="col">Result</th> <th scope="col">结果</th>
<th scope="col">IP address</th> <th scope="col">IP 地址</th>
<th scope="col">Detail</th> <th scope="col">详情</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -111,9 +116,16 @@
<div class="muted-text"><c:out value="${log.operatorMetaText}" /></div> <div class="muted-text"><c:out value="${log.operatorMetaText}" /></div>
</c:if> </c:if>
</td> </td>
<td><c:out value="${log.operationType}" /></td>
<td> <td>
<c:out value="${log.targetTable}" /> <c:choose>
<c:when test="${log.operationType == 'user.create'}">创建用户</c:when>
<c:when test="${log.operationType == 'user.update'}">更新用户</c:when>
<c:when test="${log.operationType == 'user.deactivate'}">停用用户</c:when>
<c:otherwise><c:out value="${log.operationType}" /></c:otherwise>
</c:choose>
</td>
<td>
<c:out value="${log.targetTableName}" />
<c:if test="${not empty log.targetId}"> <c:if test="${not empty log.targetId}">
#<c:out value="${log.targetId}" /> #<c:out value="${log.targetId}" />
</c:if> </c:if>
+16 -16
View File
@@ -1,11 +1,11 @@
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> <%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Loan History - MZH Library</title> <title>借阅历史 - MZH 图书馆</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css"> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css">
</head> </head>
<body> <body>
@@ -13,11 +13,11 @@
<main class="page-shell"> <main class="page-shell">
<section class="dashboard-hero catalog-hero" aria-labelledby="loan-history-title"> <section class="dashboard-hero catalog-hero" aria-labelledby="loan-history-title">
<div> <div>
<p class="eyebrow">Reader Center</p> <p class="eyebrow">读者中心</p>
<h1 id="loan-history-title">Loan history</h1> <h1 id="loan-history-title">借阅历史</h1>
<p>Review your active, returned, and overdue borrowing records.</p> <p>查看您的在借、已还和逾期借阅记录。</p>
</div> </div>
<a class="button button-secondary" href="${pageContext.request.contextPath}/catalog">Search catalog</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/catalog">检索馆藏</a>
</section> </section>
<c:if test="${not empty successMessage}"> <c:if test="${not empty successMessage}">
@@ -32,23 +32,23 @@
</c:if> </c:if>
<section class="table-panel" aria-labelledby="loan-results-title"> <section class="table-panel" aria-labelledby="loan-results-title">
<h2 id="loan-results-title">Borrowing records</h2> <h2 id="loan-results-title">借阅记录</h2>
<c:choose> <c:choose>
<c:when test="${empty borrowRecords}"> <c:when test="${empty borrowRecords}">
<p class="empty-state">No borrowing records are available for this account.</p> <p class="empty-state">此账户暂无借阅记录。</p>
</c:when> </c:when>
<c:otherwise> <c:otherwise>
<div class="table-scroll"> <div class="table-scroll">
<table class="data-table borrowing-table"> <table class="data-table borrowing-table">
<thead> <thead>
<tr> <tr>
<th scope="col">Book ID</th> <th scope="col">图书编号</th>
<th scope="col">Title</th> <th scope="col">书名</th>
<th scope="col">Borrowed</th> <th scope="col">借出时间</th>
<th scope="col">Due</th> <th scope="col">应还时间</th>
<th scope="col">Returned</th> <th scope="col">归还时间</th>
<th scope="col">Renewals</th> <th scope="col">续借次数</th>
<th scope="col">Status</th> <th scope="col">状态</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -63,7 +63,7 @@
<c:when test="${not empty record.returnedAtText}"> <c:when test="${not empty record.returnedAtText}">
<c:out value="${record.returnedAtText}" /> <c:out value="${record.returnedAtText}" />
</c:when> </c:when>
<c:otherwise>Not returned</c:otherwise> <c:otherwise>未归还</c:otherwise>
</c:choose> </c:choose>
</td> </td>
<td><c:out value="${record.renewalCount}" /></td> <td><c:out value="${record.renewalCount}" /></td>
+13 -13
View File
@@ -2,18 +2,18 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title><c:out value="${formTitle}" /> - MZH Library</title> <title><c:out value="${formTitle}" /> - MZH 图书馆</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css"> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css">
</head> </head>
<body> <body>
<%@ include file="/WEB-INF/jsp/common/header.jspf" %> <%@ include file="/WEB-INF/jsp/common/header.jspf" %>
<main class="page-shell"> <main class="page-shell">
<section class="form-panel" aria-labelledby="reader-form-title"> <section class="form-panel" aria-labelledby="reader-form-title">
<p class="eyebrow">Reader Management</p> <p class="eyebrow">读者管理</p>
<h1 id="reader-form-title"><c:out value="${formTitle}" /></h1> <h1 id="reader-form-title"><c:out value="${formTitle}" /></h1>
<c:if test="${not empty errorMessage}"> <c:if test="${not empty errorMessage}">
@@ -38,7 +38,7 @@
<div class="form-grid"> <div class="form-grid">
<div class="form-field"> <div class="form-field">
<label for="identifier">Reader ID</label> <label for="identifier">读者编号</label>
<input id="identifier" name="identifier" type="text" value="${fn:escapeXml(identifierValue)}" required> <input id="identifier" name="identifier" type="text" value="${fn:escapeXml(identifierValue)}" required>
<c:if test="${not empty errors.identifier}"> <c:if test="${not empty errors.identifier}">
<span class="field-error"><c:out value="${errors.identifier}" /></span> <span class="field-error"><c:out value="${errors.identifier}" /></span>
@@ -46,7 +46,7 @@
</div> </div>
<div class="form-field"> <div class="form-field">
<label for="fullName">Full name</label> <label for="fullName">姓名</label>
<input id="fullName" name="fullName" type="text" value="${fn:escapeXml(fullNameValue)}" required> <input id="fullName" name="fullName" type="text" value="${fn:escapeXml(fullNameValue)}" required>
<c:if test="${not empty errors.fullName}"> <c:if test="${not empty errors.fullName}">
<span class="field-error"><c:out value="${errors.fullName}" /></span> <span class="field-error"><c:out value="${errors.fullName}" /></span>
@@ -54,7 +54,7 @@
</div> </div>
<div class="form-field"> <div class="form-field">
<label for="phone">Phone</label> <label for="phone">电话</label>
<input id="phone" name="phone" type="tel" value="${fn:escapeXml(phoneValue)}"> <input id="phone" name="phone" type="tel" value="${fn:escapeXml(phoneValue)}">
<c:if test="${not empty errors.phone}"> <c:if test="${not empty errors.phone}">
<span class="field-error"><c:out value="${errors.phone}" /></span> <span class="field-error"><c:out value="${errors.phone}" /></span>
@@ -62,7 +62,7 @@
</div> </div>
<div class="form-field"> <div class="form-field">
<label for="email">Email</label> <label for="email">邮箱</label>
<input id="email" name="email" type="email" value="${fn:escapeXml(emailValue)}"> <input id="email" name="email" type="email" value="${fn:escapeXml(emailValue)}">
<c:if test="${not empty errors.email}"> <c:if test="${not empty errors.email}">
<span class="field-error"><c:out value="${errors.email}" /></span> <span class="field-error"><c:out value="${errors.email}" /></span>
@@ -70,7 +70,7 @@
</div> </div>
<div class="form-field"> <div class="form-field">
<label for="userId">Linked account ID</label> <label for="userId">关联账户 ID</label>
<input id="userId" name="userId" type="number" min="1" value="${fn:escapeXml(userIdValue)}"> <input id="userId" name="userId" type="number" min="1" value="${fn:escapeXml(userIdValue)}">
<c:if test="${not empty errors.userId}"> <c:if test="${not empty errors.userId}">
<span class="field-error"><c:out value="${errors.userId}" /></span> <span class="field-error"><c:out value="${errors.userId}" /></span>
@@ -78,7 +78,7 @@
</div> </div>
<div class="form-field"> <div class="form-field">
<label for="maxBorrowCount">Max borrow count</label> <label for="maxBorrowCount">最大借阅数量</label>
<input id="maxBorrowCount" name="maxBorrowCount" type="number" min="1" max="50" <input id="maxBorrowCount" name="maxBorrowCount" type="number" min="1" max="50"
value="${fn:escapeXml(maxBorrowCountValue)}" required> value="${fn:escapeXml(maxBorrowCountValue)}" required>
<c:if test="${not empty errors.maxBorrowCount}"> <c:if test="${not empty errors.maxBorrowCount}">
@@ -87,9 +87,9 @@
</div> </div>
<div class="form-field"> <div class="form-field">
<label for="status">Status</label> <label for="status">状态</label>
<select id="status" name="status" required> <select id="status" name="status" required>
<option value="">Select status</option> <option value="">请选择状态</option>
<c:forEach var="status" items="${statuses}"> <c:forEach var="status" items="${statuses}">
<option value="${status.code}" <c:if test="${statusValue == status.code}">selected</c:if>> <option value="${status.code}" <c:if test="${statusValue == status.code}">selected</c:if>>
<c:out value="${status.displayName}" /> <c:out value="${status.displayName}" />
@@ -103,8 +103,8 @@
</div> </div>
<div class="form-actions"> <div class="form-actions">
<button class="button button-primary" type="submit">Save</button> <button class="button button-primary" type="submit">保存</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/readers">Cancel</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/readers">取消</a>
</div> </div>
</form> </form>
</section> </section>
+27 -27
View File
@@ -2,21 +2,21 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Manage Readers - MZH Library</title> <title>读者管理 - MZH 图书馆</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css"> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css">
</head> </head>
<body> <body>
<%@ include file="/WEB-INF/jsp/common/header.jspf" %> <%@ include file="/WEB-INF/jsp/common/header.jspf" %>
<main class="page-shell"> <main class="page-shell">
<section class="dashboard-hero catalog-hero" aria-labelledby="manage-readers-title"> <section class="dashboard-hero catalog-hero" aria-labelledby="manage-readers-title">
<p class="eyebrow">Reader Management</p> <p class="eyebrow">读者管理</p>
<h1 id="manage-readers-title">Manage readers</h1> <h1 id="manage-readers-title">管理读者</h1>
<p>Create, update, and review reader eligibility and contact records.</p> <p>创建、更新和查看读者资格及联系方式记录。</p>
<a class="button button-primary" href="${pageContext.request.contextPath}/readers/new">New reader</a> <a class="button button-primary" href="${pageContext.request.contextPath}/readers/new">新增读者</a>
</section> </section>
<c:if test="${not empty successMessage}"> <c:if test="${not empty successMessage}">
@@ -30,27 +30,27 @@
</div> </div>
</c:if> </c:if>
<section class="toolbar-panel" aria-label="Reader management search"> <section class="toolbar-panel" aria-label="读者管理检索">
<form class="search-form" action="${pageContext.request.contextPath}/readers" method="get"> <form class="search-form" action="${pageContext.request.contextPath}/readers" method="get">
<div class="search-field"> <div class="search-field">
<label for="identifier">Reader ID</label> <label for="identifier">读者编号</label>
<input id="identifier" name="identifier" type="text" value="${fn:escapeXml(criteria.identifier)}"> <input id="identifier" name="identifier" type="text" value="${fn:escapeXml(criteria.identifier)}">
</div> </div>
<div class="search-field"> <div class="search-field">
<label for="name">Name</label> <label for="name">姓名</label>
<input id="name" name="name" type="text" value="${fn:escapeXml(criteria.name)}"> <input id="name" name="name" type="text" value="${fn:escapeXml(criteria.name)}">
</div> </div>
<div class="search-field"> <div class="search-field">
<label for="contact">Phone or email</label> <label for="contact">电话或邮箱</label>
<input id="contact" name="contact" type="text" value="${fn:escapeXml(criteria.contact)}"> <input id="contact" name="contact" type="text" value="${fn:escapeXml(criteria.contact)}">
</div> </div>
<div class="search-field"> <div class="search-field">
<label for="status">Status</label> <label for="status">状态</label>
<select id="status" name="status"> <select id="status" name="status">
<option value="">All statuses</option> <option value="">全部状态</option>
<c:forEach var="status" items="${statuses}"> <c:forEach var="status" items="${statuses}">
<option value="${status.code}" <c:if test="${criteria.statusCode == status.code}">selected</c:if>> <option value="${status.code}" <c:if test="${criteria.statusCode == status.code}">selected</c:if>>
<c:out value="${status.displayName}" /> <c:out value="${status.displayName}" />
@@ -62,29 +62,29 @@
</c:if> </c:if>
</div> </div>
<button class="button button-primary" type="submit">Search</button> <button class="button button-primary" type="submit">检索</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/readers">Clear</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/readers">清空</a>
</form> </form>
</section> </section>
<section class="table-panel" aria-labelledby="reader-results-title"> <section class="table-panel" aria-labelledby="reader-results-title">
<h2 id="reader-results-title">Reader records</h2> <h2 id="reader-results-title">读者记录</h2>
<c:choose> <c:choose>
<c:when test="${empty readers}"> <c:when test="${empty readers}">
<p class="empty-state">No reader records match the current filters.</p> <p class="empty-state">没有符合当前筛选条件的读者记录。</p>
</c:when> </c:when>
<c:otherwise> <c:otherwise>
<div class="table-scroll"> <div class="table-scroll">
<table class="data-table"> <table class="data-table">
<thead> <thead>
<tr> <tr>
<th scope="col">Reader ID</th> <th scope="col">读者编号</th>
<th scope="col">Name</th> <th scope="col">姓名</th>
<th scope="col">Contact</th> <th scope="col">联系方式</th>
<th scope="col">Account</th> <th scope="col">关联账户</th>
<th scope="col">Borrow limit</th> <th scope="col">借阅上限</th>
<th scope="col">Status</th> <th scope="col">状态</th>
<th scope="col">Actions</th> <th scope="col">操作</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -105,7 +105,7 @@
<c:when test="${not empty reader.username}"> <c:when test="${not empty reader.username}">
<c:out value="${reader.username}" /> <c:out value="${reader.username}" />
</c:when> </c:when>
<c:otherwise>Unlinked</c:otherwise> <c:otherwise>未关联</c:otherwise>
</c:choose> </c:choose>
</td> </td>
<td><c:out value="${reader.maxBorrowCount}" /></td> <td><c:out value="${reader.maxBorrowCount}" /></td>
@@ -117,12 +117,12 @@
<td> <td>
<div class="table-actions"> <div class="table-actions">
<a class="button button-secondary" <a class="button button-secondary"
href="${pageContext.request.contextPath}/readers/edit?id=${reader.id}">Edit</a> href="${pageContext.request.contextPath}/readers/edit?id=${reader.id}">编辑</a>
<form action="${pageContext.request.contextPath}/readers/delete" <form action="${pageContext.request.contextPath}/readers/delete"
method="post" method="post"
onsubmit="return confirm('Deactivate this reader profile?');"> onsubmit="return confirm('确定停用这个读者档案吗?');">
<input type="hidden" name="id" value="${reader.id}"> <input type="hidden" name="id" value="${reader.id}">
<button class="button button-danger" type="submit">Deactivate</button> <button class="button button-danger" type="submit">停用</button>
</form> </form>
</div> </div>
</td> </td>
@@ -1,11 +1,11 @@
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> <%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Reports - MZH Library</title> <title>报表 - MZH 图书馆</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css"> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css">
</head> </head>
<body> <body>
@@ -13,11 +13,11 @@
<main class="page-shell"> <main class="page-shell">
<section class="dashboard-hero catalog-hero" aria-labelledby="reports-title"> <section class="dashboard-hero catalog-hero" aria-labelledby="reports-title">
<div> <div>
<p class="eyebrow">Reports</p> <p class="eyebrow">报表</p>
<h1 id="reports-title">Report center</h1> <h1 id="reports-title">报表中心</h1>
<p>Review collection inventory, borrowing health, overdue loans, and popular books.</p> <p>查看馆藏库存、借阅状况、逾期借阅和热门图书。</p>
</div> </div>
<a class="button button-secondary" href="${pageContext.request.contextPath}/borrowing">Borrowing records</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/borrowing">借阅记录</a>
</section> </section>
<c:if test="${not empty errorMessage}"> <c:if test="${not empty errorMessage}">
@@ -27,59 +27,59 @@
</c:if> </c:if>
<c:if test="${not empty reportCenter}"> <c:if test="${not empty reportCenter}">
<section class="report-grid" aria-label="Report summary"> <section class="report-grid" aria-label="报表摘要">
<article class="report-card"> <article class="report-card">
<p class="eyebrow">Inventory</p> <p class="eyebrow">库存</p>
<h2>Total titles</h2> <h2>图书种类总数</h2>
<p class="report-metric"><c:out value="${reportCenter.inventorySummary.totalTitles}" /></p> <p class="report-metric"><c:out value="${reportCenter.inventorySummary.totalTitles}" /></p>
</article> </article>
<article class="report-card"> <article class="report-card">
<p class="eyebrow">Inventory</p> <p class="eyebrow">库存</p>
<h2>Total copies</h2> <h2>馆藏总册数</h2>
<p class="report-metric"><c:out value="${reportCenter.inventorySummary.totalCopies}" /></p> <p class="report-metric"><c:out value="${reportCenter.inventorySummary.totalCopies}" /></p>
</article> </article>
<article class="report-card"> <article class="report-card">
<p class="eyebrow">Inventory</p> <p class="eyebrow">库存</p>
<h2>Available copies</h2> <h2>可借册数</h2>
<p class="report-metric"><c:out value="${reportCenter.inventorySummary.availableCopies}" /></p> <p class="report-metric"><c:out value="${reportCenter.inventorySummary.availableCopies}" /></p>
</article> </article>
<article class="report-card"> <article class="report-card">
<p class="eyebrow">Attention</p> <p class="eyebrow">需关注</p>
<h2>Unavailable or empty</h2> <h2>不可借或无库存</h2>
<p class="report-metric"><c:out value="${reportCenter.inventorySummary.unavailableOrEmptyTitles}" /></p> <p class="report-metric"><c:out value="${reportCenter.inventorySummary.unavailableOrEmptyTitles}" /></p>
</article> </article>
<article class="report-card"> <article class="report-card">
<p class="eyebrow">Borrowing</p> <p class="eyebrow">借阅</p>
<h2>Currently borrowed</h2> <h2>当前借出</h2>
<p class="report-metric"><c:out value="${reportCenter.borrowingSummary.activeLoans}" /></p> <p class="report-metric"><c:out value="${reportCenter.borrowingSummary.activeLoans}" /></p>
</article> </article>
<article class="report-card"> <article class="report-card">
<p class="eyebrow">Borrowing</p> <p class="eyebrow">借阅</p>
<h2>Returned records</h2> <h2>已归还记录</h2>
<p class="report-metric"><c:out value="${reportCenter.borrowingSummary.returnedLoans}" /></p> <p class="report-metric"><c:out value="${reportCenter.borrowingSummary.returnedLoans}" /></p>
</article> </article>
<article class="report-card report-card-alert"> <article class="report-card report-card-alert">
<p class="eyebrow">Borrowing</p> <p class="eyebrow">借阅</p>
<h2>Overdue loans</h2> <h2>逾期借阅</h2>
<p class="report-metric"><c:out value="${reportCenter.borrowingSummary.overdueLoans}" /></p> <p class="report-metric"><c:out value="${reportCenter.borrowingSummary.overdueLoans}" /></p>
</article> </article>
</section> </section>
<section class="table-panel" aria-labelledby="overdue-report-title"> <section class="table-panel" aria-labelledby="overdue-report-title">
<h2 id="overdue-report-title">Overdue list</h2> <h2 id="overdue-report-title">逾期列表</h2>
<c:choose> <c:choose>
<c:when test="${empty reportCenter.overdueRows}"> <c:when test="${empty reportCenter.overdueRows}">
<p class="empty-state">No active overdue borrowing records.</p> <p class="empty-state">当前没有逾期未还的借阅记录。</p>
</c:when> </c:when>
<c:otherwise> <c:otherwise>
<div class="table-scroll"> <div class="table-scroll">
<table class="data-table"> <table class="data-table">
<thead> <thead>
<tr> <tr>
<th scope="col">Reader</th> <th scope="col">读者</th>
<th scope="col">Book</th> <th scope="col">图书</th>
<th scope="col">Due date</th> <th scope="col">应还日期</th>
<th scope="col">Overdue days</th> <th scope="col">逾期天数</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -96,7 +96,7 @@
<td><c:out value="${row.dueAtText}" /></td> <td><c:out value="${row.dueAtText}" /></td>
<td> <td>
<span class="status-pill status-overdue"> <span class="status-pill status-overdue">
<c:out value="${row.overdueDays}" /> days <c:out value="${row.overdueDays}" />
</span> </span>
</td> </td>
</tr> </tr>
@@ -109,19 +109,19 @@
</section> </section>
<section class="table-panel" aria-labelledby="popular-report-title"> <section class="table-panel" aria-labelledby="popular-report-title">
<h2 id="popular-report-title">Popular borrowing ranking</h2> <h2 id="popular-report-title">热门借阅排行</h2>
<c:choose> <c:choose>
<c:when test="${empty reportCenter.popularBooks}"> <c:when test="${empty reportCenter.popularBooks}">
<p class="empty-state">No borrowing records are available for ranking yet.</p> <p class="empty-state">暂无可用于排行的借阅记录。</p>
</c:when> </c:when>
<c:otherwise> <c:otherwise>
<div class="table-scroll"> <div class="table-scroll">
<table class="data-table"> <table class="data-table">
<thead> <thead>
<tr> <tr>
<th scope="col">Book</th> <th scope="col">图书</th>
<th scope="col">Author</th> <th scope="col">作者</th>
<th scope="col">Borrow records</th> <th scope="col">借阅次数</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
+31 -31
View File
@@ -1,11 +1,11 @@
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %> <%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html> <!doctype html>
<html lang="en"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title><c:out value="${areaName}" /> - MZH Library</title> <title><c:out value="${areaName}" /> - MZH 图书馆</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css"> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/app.css">
</head> </head>
<body> <body>
@@ -17,67 +17,67 @@
</p> </p>
<h1 id="area-title"><c:out value="${areaName}" /></h1> <h1 id="area-title"><c:out value="${areaName}" /></h1>
<p><c:out value="${areaSummary}" /></p> <p><c:out value="${areaSummary}" /></p>
<a class="button button-primary" href="${pageContext.request.contextPath}/dashboard">Back to dashboard</a> <a class="button button-primary" href="${pageContext.request.contextPath}/dashboard">返回控制台</a>
</section> </section>
<section class="card-grid role-actions" aria-label="Workspace actions"> <section class="card-grid role-actions" aria-label="工作区操作">
<article class="workspace-card"> <article class="workspace-card">
<h2>Book Catalog</h2> <h2>馆藏检索</h2>
<p>Search available collection records by title, author, category, or book identifier.</p> <p>按书名、作者、分类或图书编号检索可用馆藏记录。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/catalog">Search catalog</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/catalog">检索馆藏</a>
</article> </article>
<c:if test="${sessionScope.userRole == 'administrator' or sessionScope.userRole == 'librarian'}"> <c:if test="${sessionScope.userRole == 'administrator' or sessionScope.userRole == 'librarian'}">
<c:if test="${sessionScope.userRole == 'administrator'}"> <c:if test="${sessionScope.userRole == 'administrator'}">
<article class="workspace-card"> <article class="workspace-card">
<h2>User Management</h2> <h2>用户管理</h2>
<p>Create, update, deactivate, and review login accounts.</p> <p>创建、更新、停用和查看登录账户。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/users">Manage users</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/admin/users">管理用户</a>
</article> </article>
<article class="workspace-card"> <article class="workspace-card">
<h2>System Logs</h2> <h2>系统日志</h2>
<p>Review read-only audit entries for account and maintenance actions.</p> <p>查看账户与维护操作的只读审计记录。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/system-logs">View logs</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/admin/system-logs">查看日志</a>
</article> </article>
</c:if> </c:if>
<article class="workspace-card"> <article class="workspace-card">
<h2>Book Management</h2> <h2>图书管理</h2>
<p>Create, update, delete, and review inventory fields for book records.</p> <p>创建、更新、删除和查看图书记录的库存字段。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/books">Manage books</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/books">管理图书</a>
</article> </article>
<article class="workspace-card"> <article class="workspace-card">
<h2>Category Maintenance</h2> <h2>分类维护</h2>
<p>Create, update, and retire catalog categories used by book records.</p> <p>创建、更新和停用图书记录使用的馆藏分类。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">Manage categories</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">管理分类</a>
</article> </article>
<article class="workspace-card"> <article class="workspace-card">
<h2>Reader Management</h2> <h2>读者管理</h2>
<p>Create, update, deactivate, and review eligibility fields for reader records.</p> <p>创建、更新、停用和查看读者记录的资格字段。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/readers">Manage readers</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/readers">管理读者</a>
</article> </article>
<article class="workspace-card"> <article class="workspace-card">
<h2>Borrowing Management</h2> <h2>借阅管理</h2>
<p>Create loans, process returns, renew records, and review overdue items.</p> <p>创建借阅、处理归还、续借记录并查看逾期项目。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/borrowing">Manage borrowing</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/borrowing">管理借阅</a>
</article> </article>
<article class="workspace-card"> <article class="workspace-card">
<h2>Report Center</h2> <h2>报表中心</h2>
<p>Review inventory summaries, borrowing health, overdue lists, and popular books.</p> <p>查看库存摘要、借阅状况、逾期列表和热门图书。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/reports">View reports</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/reports">查看报表</a>
</article> </article>
</c:if> </c:if>
<c:if test="${sessionScope.userRole == 'reader'}"> <c:if test="${sessionScope.userRole == 'reader'}">
<article class="workspace-card"> <article class="workspace-card">
<h2>My Loan History</h2> <h2>我的借阅历史</h2>
<p>Review active loans, returned records, renewal counts, and overdue status.</p> <p>查看在借记录、已还记录、续借次数和逾期状态。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/reader/loans">View history</a> <a class="button button-secondary" href="${pageContext.request.contextPath}/reader/loans">查看历史</a>
</article> </article>
</c:if> </c:if>
</section> </section>
+1 -1
View File
@@ -4,7 +4,7 @@
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"> version="4.0">
<display-name>MZH Library Management</display-name> <display-name>MZH 图书馆管理系统</display-name>
<filter> <filter>
<filter-name>CharacterEncodingFilter</filter-name> <filter-name>CharacterEncodingFilter</filter-name>
@@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="960" viewBox="0 0 1440 960" role="img" aria-label="Library shelves"> <svg xmlns="http://www.w3.org/2000/svg" width="1440" height="960" viewBox="0 0 1440 960" role="img" aria-label="图书馆书架">
<rect width="1440" height="960" fill="#e8edf1"/> <rect width="1440" height="960" fill="#e8edf1"/>
<rect x="0" y="705" width="1440" height="255" fill="#d4ddd8"/> <rect x="0" y="705" width="1440" height="255" fill="#d4ddd8"/>
<g opacity="0.92"> <g opacity="0.92">

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -14,10 +14,10 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
public final class AuthServiceCheck { public final class AuthServiceCheck {
private static final String REQUIRED_MESSAGE = "Username and password are required."; private static final String REQUIRED_MESSAGE = "请输入用户名和密码。";
private static final String INVALID_MESSAGE = "Invalid username or password."; private static final String INVALID_MESSAGE = "用户名或密码不正确。";
private static final String UNAVAILABLE_MESSAGE = private static final String UNAVAILABLE_MESSAGE =
"Login service is temporarily unavailable. Please try again later."; "登录服务暂时不可用,请稍后重试。";
private AuthServiceCheck() { private AuthServiceCheck() {
} }
@@ -22,7 +22,7 @@ import java.util.logging.Logger;
public final class BookServiceCheck { public final class BookServiceCheck {
private static final String UNAVAILABLE_MESSAGE = private static final String UNAVAILABLE_MESSAGE =
"Book service is temporarily unavailable. Please try again later."; "图书服务暂时不可用,请稍后重试。";
private BookServiceCheck() { private BookServiceCheck() {
} }
@@ -44,13 +44,13 @@ public final class BookServiceCheck {
ServiceResult<Long> denied = service.createBook(reader, ServiceResult<Long> denied = service.createBook(reader,
book(0L, "BK-1001", "Reader Write", "Test Author", 1L, 1, 1, BookStatus.AVAILABLE)); book(0L, "BK-1001", "Reader Write", "Test Author", 1L, 1, 1, BookStatus.AVAILABLE));
require(!denied.isSuccessful(), "reader write should fail"); require(!denied.isSuccessful(), "reader write should fail");
require("You do not have permission to manage books.".equals(denied.getMessage()), require("您无权管理图书。".equals(denied.getMessage()),
"reader write should return permission message"); "reader write should return permission message");
ServiceResult<Long> deniedCategory = service.createCategory(reader, ServiceResult<Long> deniedCategory = service.createCategory(reader,
category(0L, "Reader Category", "Denied category")); category(0L, "Reader Category", "Denied category"));
require(!deniedCategory.isSuccessful(), "reader category create should fail"); require(!deniedCategory.isSuccessful(), "reader category create should fail");
require("You do not have permission to manage books.".equals(deniedCategory.getMessage()), require("您无权管理图书。".equals(deniedCategory.getMessage()),
"reader category write should return permission message"); "reader category write should return permission message");
ServiceResult<Long> created = service.createBook(librarian, ServiceResult<Long> created = service.createBook(librarian,
@@ -70,7 +70,7 @@ public final class BookServiceCheck {
ServiceResult<Void> deleteUsedCategory = service.deleteCategory(librarian, 1L); ServiceResult<Void> deleteUsedCategory = service.deleteCategory(librarian, 1L);
require(!deleteUsedCategory.isSuccessful(), "used category delete should fail"); require(!deleteUsedCategory.isSuccessful(), "used category delete should fail");
require("Category is used by existing books and cannot be deleted.".equals(deleteUsedCategory.getMessage()), require("该分类已被现有图书使用,不能删除。".equals(deleteUsedCategory.getMessage()),
"used category delete should return a safe specific message"); "used category delete should return a safe specific message");
require(deleteUsedCategory.getErrors().containsKey("category"), require(deleteUsedCategory.getErrors().containsKey("category"),
"used category delete should return a category-level field error"); "used category delete should return a category-level field error");
@@ -30,7 +30,7 @@ import java.util.logging.Logger;
public final class BorrowingServiceCheck { public final class BorrowingServiceCheck {
private static final String UNAVAILABLE_MESSAGE = private static final String UNAVAILABLE_MESSAGE =
"Borrowing service is temporarily unavailable. Please try again later."; "借阅服务暂时不可用,请稍后重试。";
private static final Clock FIXED_CLOCK = Clock.fixed( private static final Clock FIXED_CLOCK = Clock.fixed(
Instant.parse("2026-04-27T00:00:00Z"), Instant.parse("2026-04-27T00:00:00Z"),
ZoneId.of("UTC") ZoneId.of("UTC")
@@ -62,7 +62,7 @@ public final class BorrowingServiceCheck {
ServiceResult<Long> denied = service.borrowBook(readerUser, "RD-1000", "BK-1000"); ServiceResult<Long> denied = service.borrowBook(readerUser, "RD-1000", "BK-1000");
require(!denied.isSuccessful(), "reader should not manage borrow creation"); require(!denied.isSuccessful(), "reader should not manage borrow creation");
require("You do not have permission to manage borrowing.".equals(denied.getMessage()), require("您无权管理借阅。".equals(denied.getMessage()),
"reader borrow creation should return permission message"); "reader borrow creation should return permission message");
ServiceResult<Long> suspended = service.borrowBook(librarian, "RD-1001", "BK-1000"); ServiceResult<Long> suspended = service.borrowBook(librarian, "RD-1001", "BK-1000");
@@ -96,7 +96,7 @@ public final class BorrowingServiceCheck {
ServiceResult<List<BorrowRecord>> staffHistory = service.listCurrentReaderHistory(administrator); ServiceResult<List<BorrowRecord>> staffHistory = service.listCurrentReaderHistory(administrator);
require(!staffHistory.isSuccessful(), "staff should use management history, not reader loan history"); require(!staffHistory.isSuccessful(), "staff should use management history, not reader loan history");
require("You do not have permission to view loan history.".equals(staffHistory.getMessage()), require("您无权查看借阅历史。".equals(staffHistory.getMessage()),
"staff reader-history access should return permission message"); "staff reader-history access should return permission message");
ServiceResult<Void> returned = service.returnBook(librarian, borrowedId); ServiceResult<Void> returned = service.returnBook(librarian, borrowedId);
@@ -21,7 +21,7 @@ import java.util.logging.Logger;
public final class ReaderServiceCheck { public final class ReaderServiceCheck {
private static final String UNAVAILABLE_MESSAGE = private static final String UNAVAILABLE_MESSAGE =
"Reader service is temporarily unavailable. Please try again later."; "读者服务暂时不可用,请稍后重试。";
private ReaderServiceCheck() { private ReaderServiceCheck() {
} }
@@ -64,7 +64,7 @@ public final class ReaderServiceCheck {
ServiceResult<Long> denied = service.createReader(readerUser, ServiceResult<Long> denied = service.createReader(readerUser,
reader(0L, "RD-1006", null, "Reader Write", "13800000007", "", ReaderStatus.ACTIVE, 5)); reader(0L, "RD-1006", null, "Reader Write", "13800000007", "", ReaderStatus.ACTIVE, 5));
require(!denied.isSuccessful(), "reader write should fail"); require(!denied.isSuccessful(), "reader write should fail");
require("You do not have permission to manage readers.".equals(denied.getMessage()), require("您无权管理读者。".equals(denied.getMessage()),
"reader write should return permission message"); "reader write should return permission message");
ServiceResult<Long> created = service.createReader(librarian, ServiceResult<Long> created = service.createReader(librarian,
@@ -21,9 +21,9 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
public final class ReportServiceCheck { public final class ReportServiceCheck {
private static final String DENIED_MESSAGE = "You do not have permission to view reports."; private static final String DENIED_MESSAGE = "您无权查看报表。";
private static final String UNAVAILABLE_MESSAGE = private static final String UNAVAILABLE_MESSAGE =
"Report service is temporarily unavailable. Please try again later."; "报表服务暂时不可用,请稍后重试。";
private ReportServiceCheck() { private ReportServiceCheck() {
} }
@@ -20,9 +20,9 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
public final class SystemLogServiceCheck { public final class SystemLogServiceCheck {
private static final String DENIED_MESSAGE = "You do not have permission to view system logs."; private static final String DENIED_MESSAGE = "您无权查看系统日志。";
private static final String UNAVAILABLE_MESSAGE = private static final String UNAVAILABLE_MESSAGE =
"System log service is temporarily unavailable. Please try again later."; "系统日志服务暂时不可用,请稍后重试。";
private SystemLogServiceCheck() { private SystemLogServiceCheck() {
} }
@@ -59,10 +59,10 @@ public final class SystemLogServiceCheck {
SystemLog orphanedOperator = log(99L, "user.update", "Updated orphaned operator account"); SystemLog orphanedOperator = log(99L, "user.update", "Updated orphaned operator account");
orphanedOperator.setOperatorUsername(""); orphanedOperator.setOperatorUsername("");
orphanedOperator.setOperatorDisplayName(""); orphanedOperator.setOperatorDisplayName("");
require("User #1".equals(orphanedOperator.getOperatorLabel()), require("用户 #1".equals(orphanedOperator.getOperatorLabel()),
"operator id should still render when joined user names are unavailable"); "operator id should still render when joined user names are unavailable");
require("administrator".equals(orphanedOperator.getOperatorMetaText()), require("管理员".equals(orphanedOperator.getOperatorMetaText()),
"operator meta should preserve role when names are unavailable"); "operator meta should display role when names are unavailable");
SystemLog unsafeStatus = log(100L, "user.update", "Unsafe status check"); SystemLog unsafeStatus = log(100L, "user.update", "Unsafe status check");
unsafeStatus.setResultStatus("success\" onclick=\"x"); unsafeStatus.setResultStatus("success\" onclick=\"x");
@@ -26,9 +26,9 @@ import java.util.logging.Logger;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public final class UserAccountServiceCheck { public final class UserAccountServiceCheck {
private static final String DENIED_MESSAGE = "You do not have permission to manage users."; private static final String DENIED_MESSAGE = "您无权管理用户。";
private static final String UNAVAILABLE_MESSAGE = private static final String UNAVAILABLE_MESSAGE =
"User management service is temporarily unavailable. Please try again later."; "用户管理服务暂时不可用,请稍后重试。";
private UserAccountServiceCheck() { private UserAccountServiceCheck() {
} }