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
# be injected into Codex sessions.
sandbox_mode = "workspace-write"
sandbox_mode = "danger-full-access"
[sandbox_workspace_write]
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,
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 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 {
String path = request.getServletPath();
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);
return;
}
@@ -57,7 +57,7 @@ public class BookManagementServlet extends HttpServlet {
return;
}
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);
return;
}
@@ -134,12 +134,12 @@ public class BookManagementServlet extends HttpServlet {
long id = requiredLong(request.getParameter("id"), -1L);
ServiceResult<Optional<Book>> result = bookService.findBook(id);
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");
return;
}
renderForm(request, response, "Edit book", "/books/update", result.getData().get(),
renderForm(request, response, "编辑图书", "/books/update", result.getData().get(),
Collections.emptyMap(), Collections.emptyMap(), null);
}
@@ -160,26 +160,26 @@ public class BookManagementServlet extends HttpServlet {
long id = requiredLong(request.getParameter("id"), -1L);
ServiceResult<Optional<BookCategory>> result = bookService.findCategory(id);
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");
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);
}
private void createBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
BookForm form = readBookForm(request, false);
if (!form.getErrors().isEmpty()) {
renderForm(request, response, "Create book", "/books", form.getBook(), form.getValues(),
form.getErrors(), "Please correct the highlighted book fields.");
renderForm(request, response, "创建图书", "/books", form.getBook(), form.getValues(),
form.getErrors(), "请修正高亮的图书字段。");
return;
}
ServiceResult<Long> result = bookService.createBook(currentUser(request), form.getBook());
if (!result.isSuccessful()) {
handleFormFailure(request, response, "Create book", "/books", form, result);
handleFormFailure(request, response, "创建图书", "/books", form, result);
return;
}
@@ -190,14 +190,14 @@ public class BookManagementServlet extends HttpServlet {
private void updateBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
BookForm form = readBookForm(request, true);
if (!form.getErrors().isEmpty()) {
renderForm(request, response, "Edit book", "/books/update", form.getBook(), form.getValues(),
form.getErrors(), "Please correct the highlighted book fields.");
renderForm(request, response, "编辑图书", "/books/update", form.getBook(), form.getValues(),
form.getErrors(), "请修正高亮的图书字段。");
return;
}
ServiceResult<Void> result = bookService.updateBook(currentUser(request), form.getBook());
if (!result.isSuccessful()) {
handleFormFailure(request, response, "Edit book", "/books/update", form, result);
handleFormFailure(request, response, "编辑图书", "/books/update", form, result);
return;
}
@@ -224,14 +224,14 @@ public class BookManagementServlet extends HttpServlet {
throws ServletException, IOException {
CategoryForm form = readCategoryForm(request, false);
if (!form.getErrors().isEmpty()) {
renderCategoryForm(request, response, "Create category", "/book-categories", form.getCategory(),
form.getValues(), form.getErrors(), "Please correct the highlighted category fields.");
renderCategoryForm(request, response, "创建分类", "/book-categories", form.getCategory(),
form.getValues(), form.getErrors(), "请修正高亮的分类字段。");
return;
}
ServiceResult<Long> result = bookService.createCategory(currentUser(request), form.getCategory());
if (!result.isSuccessful()) {
handleCategoryFormFailure(request, response, "Create category", "/book-categories", form, result);
handleCategoryFormFailure(request, response, "创建分类", "/book-categories", form, result);
return;
}
@@ -243,14 +243,14 @@ public class BookManagementServlet extends HttpServlet {
throws ServletException, IOException {
CategoryForm form = readCategoryForm(request, true);
if (!form.getErrors().isEmpty()) {
renderCategoryForm(request, response, "Edit category", "/book-categories/update", form.getCategory(),
form.getValues(), form.getErrors(), "Please correct the highlighted category fields.");
renderCategoryForm(request, response, "编辑分类", "/book-categories/update", form.getCategory(),
form.getValues(), form.getErrors(), "请修正高亮的分类字段。");
return;
}
ServiceResult<Void> result = bookService.updateCategory(currentUser(request), form.getCategory());
if (!result.isSuccessful()) {
handleCategoryFormFailure(request, response, "Edit category", "/book-categories/update", form, result);
handleCategoryFormFailure(request, response, "编辑分类", "/book-categories/update", form, result);
return;
}
@@ -338,20 +338,20 @@ public class BookManagementServlet extends HttpServlet {
Book book = new Book();
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.setTitle(values.get("title"));
book.setAuthor(values.get("author"));
book.setCategoryId(parseLong(values.get("categoryId"), "categoryId", "Select a category.", errors));
book.setTotalCopies(parseInt(values.get("totalCopies"), "totalCopies", "Enter a valid total copy count.", errors));
book.setCategoryId(parseLong(values.get("categoryId"), "categoryId", "请选择分类。", errors));
book.setTotalCopies(parseInt(values.get("totalCopies"), "totalCopies", "请输入有效的馆藏总数。", errors));
book.setAvailableCopies(parseInt(values.get("availableCopies"), "availableCopies",
"Enter a valid available copy count.", errors));
"请输入有效的可借数量。", errors));
try {
book.setStatus(BookStatus.fromCode(values.get("status")));
} catch (IllegalArgumentException ex) {
errors.put("status", "Select a status.");
errors.put("status", "请选择状态。");
}
return new BookForm(book, values, errors);
@@ -376,7 +376,7 @@ public class BookManagementServlet extends HttpServlet {
BookCategory category = new BookCategory();
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.setDescription(values.get("description"));
@@ -454,7 +454,7 @@ public class BookManagementServlet extends HttpServlet {
}
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)
@@ -115,7 +115,7 @@ public class BorrowingManagementServlet extends HttpServlet {
throws IOException, ServletException {
long id = requiredLong(request.getParameter("id"), -1L);
ServiceResult<Void> result = id <= 0
? ServiceResult.failure("Select a valid borrowing record.")
? ServiceResult.failure("请选择有效的借阅记录。")
: borrowingService.returnBook(currentUser(request), id);
redirectWithResult(request, response, result);
}
@@ -124,7 +124,7 @@ public class BorrowingManagementServlet extends HttpServlet {
throws IOException, ServletException {
long id = requiredLong(request.getParameter("id"), -1L);
ServiceResult<Void> result = id <= 0
? ServiceResult.failure("Select a valid borrowing record.")
? ServiceResult.failure("请选择有效的借阅记录。")
: borrowingService.renewLoan(currentUser(request), id);
redirectWithResult(request, response, result);
}
@@ -185,12 +185,12 @@ public class BorrowingManagementServlet extends HttpServlet {
if (result.hasErrors()) {
return result.getErrors().values().iterator().next();
}
return "Borrowing action failed.";
return "借阅操作失败。";
}
private boolean isPermissionDenied(ServiceResult<?> result) {
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)
@@ -20,7 +20,7 @@ import javax.servlet.http.HttpSession;
public class ReaderLoanHistoryServlet extends HttpServlet {
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 HISTORY_DENIED_MESSAGE = "You do not have permission to view loan history.";
private static final String HISTORY_DENIED_MESSAGE = "您无权查看借阅历史。";
private BorrowingServiceImpl borrowingService;
@@ -41,7 +41,7 @@ public class ReaderManagementServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String path = request.getServletPath();
if ("/readers/new".equals(path)) {
renderForm(request, response, "Create reader", "/readers", defaultReader(), Collections.emptyMap(),
renderForm(request, response, "创建读者", "/readers", defaultReader(), Collections.emptyMap(),
Collections.emptyMap(), null);
return;
}
@@ -99,12 +99,12 @@ public class ReaderManagementServlet extends HttpServlet {
long id = requiredLong(request.getParameter("id"), -1L);
ServiceResult<Optional<Reader>> result = readerService.findReader(id);
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");
return;
}
renderForm(request, response, "Edit reader", "/readers/update", result.getData().get(),
renderForm(request, response, "编辑读者", "/readers/update", result.getData().get(),
Collections.emptyMap(), Collections.emptyMap(), null);
}
@@ -112,14 +112,14 @@ public class ReaderManagementServlet extends HttpServlet {
throws ServletException, IOException {
ReaderForm form = readReaderForm(request, false);
if (!form.getErrors().isEmpty()) {
renderForm(request, response, "Create reader", "/readers", form.getReader(), form.getValues(),
form.getErrors(), "Please correct the highlighted reader fields.");
renderForm(request, response, "创建读者", "/readers", form.getReader(), form.getValues(),
form.getErrors(), "请修正高亮的读者字段。");
return;
}
ServiceResult<Long> result = readerService.createReader(currentUser(request), form.getReader());
if (!result.isSuccessful()) {
handleFormFailure(request, response, "Create reader", "/readers", form, result);
handleFormFailure(request, response, "创建读者", "/readers", form, result);
return;
}
@@ -131,14 +131,14 @@ public class ReaderManagementServlet extends HttpServlet {
throws ServletException, IOException {
ReaderForm form = readReaderForm(request, true);
if (!form.getErrors().isEmpty()) {
renderForm(request, response, "Edit reader", "/readers/update", form.getReader(), form.getValues(),
form.getErrors(), "Please correct the highlighted reader fields.");
renderForm(request, response, "编辑读者", "/readers/update", form.getReader(), form.getValues(),
form.getErrors(), "请修正高亮的读者字段。");
return;
}
ServiceResult<Void> result = readerService.updateReader(currentUser(request), form.getReader());
if (!result.isSuccessful()) {
handleFormFailure(request, response, "Edit reader", "/readers/update", form, result);
handleFormFailure(request, response, "编辑读者", "/readers/update", form, result);
return;
}
@@ -195,21 +195,21 @@ public class ReaderManagementServlet extends HttpServlet {
Reader reader = new Reader();
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.setUserId(optionalPositiveLong(values.get("userId"), "userId",
"Enter a valid linked account ID.", errors));
"请输入有效的关联账户 ID", errors));
reader.setFullName(values.get("fullName"));
reader.setPhone(values.get("phone"));
reader.setEmail(values.get("email"));
reader.setMaxBorrowCount(parseInt(values.get("maxBorrowCount"), "maxBorrowCount",
"Enter a valid max borrow count.", errors));
"请输入有效的最大借阅数量。", errors));
try {
reader.setStatus(ReaderStatus.fromCode(values.get("status")));
} catch (IllegalArgumentException ex) {
errors.put("status", "Select a status.");
errors.put("status", "请选择状态。");
}
return new ReaderForm(reader, values, errors);
@@ -304,7 +304,7 @@ public class ReaderManagementServlet extends HttpServlet {
}
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)
@@ -46,7 +46,7 @@ public class ReportServlet extends HttpServlet {
private boolean isPermissionDenied(ServiceResult<?> result) {
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)
@@ -14,14 +14,14 @@ public class RoleAreaServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String servletPath = request.getServletPath();
if (servletPath.startsWith("/admin")) {
request.setAttribute("areaName", "Administration");
request.setAttribute("areaSummary", "Account, role, permission, and system-maintenance entry point.");
request.setAttribute("areaName", "系统管理");
request.setAttribute("areaSummary", "账户、角色、权限和系统维护入口。");
} else if (servletPath.startsWith("/librarian")) {
request.setAttribute("areaName", "Librarian Workspace");
request.setAttribute("areaSummary", "Book, reader, borrowing, return, renewal, and overdue entry point.");
request.setAttribute("areaName", "馆员工作台");
request.setAttribute("areaSummary", "图书、读者、借阅、归还、续借和逾期处理入口。");
} else {
request.setAttribute("areaName", "Reader Center");
request.setAttribute("areaSummary", "Catalog search and reader self-service entry point.");
request.setAttribute("areaName", "读者中心");
request.setAttribute("areaSummary", "馆藏检索和读者自助服务入口。");
}
request.getRequestDispatcher(ROLE_HOME_JSP).forward(request, response);
@@ -21,7 +21,7 @@ import javax.servlet.http.HttpSession;
public class SystemLogServlet extends HttpServlet {
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 DENIED_MESSAGE = "You do not have permission to view system logs.";
private static final String DENIED_MESSAGE = "您无权查看系统日志。";
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 FLASH_SUCCESS = "flashSuccess";
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;
@@ -44,7 +44,7 @@ public class UserManagementServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String path = request.getServletPath();
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);
return;
}
@@ -108,12 +108,12 @@ public class UserManagementServlet extends HttpServlet {
return;
}
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");
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);
}
@@ -121,15 +121,15 @@ public class UserManagementServlet extends HttpServlet {
throws ServletException, IOException {
UserForm form = readUserForm(request, false);
if (!form.getErrors().isEmpty()) {
renderForm(request, response, "Create user account", "/admin/users", form.getUser(), form.getValues(),
form.getErrors(), "Please correct the highlighted account fields.");
renderForm(request, response, "创建用户账户", "/admin/users", form.getUser(), form.getValues(),
form.getErrors(), "请修正高亮的账户字段。");
return;
}
ServiceResult<Long> result = userAccountService.createUser(currentUser(request), form.getUser(),
form.getPassword(), clientIp(request));
if (!result.isSuccessful()) {
handleFormFailure(request, response, "Create user account", "/admin/users", form, result);
handleFormFailure(request, response, "创建用户账户", "/admin/users", form, result);
return;
}
@@ -141,15 +141,15 @@ public class UserManagementServlet extends HttpServlet {
throws ServletException, IOException {
UserForm form = readUserForm(request, true);
if (!form.getErrors().isEmpty()) {
renderForm(request, response, "Edit user account", "/admin/users/update", form.getUser(), form.getValues(),
form.getErrors(), "Please correct the highlighted account fields.");
renderForm(request, response, "编辑用户账户", "/admin/users/update", form.getUser(), form.getValues(),
form.getErrors(), "请修正高亮的账户字段。");
return;
}
ServiceResult<Void> result = userAccountService.updateUser(currentUser(request), form.getUser(),
form.getPassword(), clientIp(request));
if (!result.isSuccessful()) {
handleFormFailure(request, response, "Edit user account", "/admin/users/update", form, result);
handleFormFailure(request, response, "编辑用户账户", "/admin/users/update", form, result);
return;
}
@@ -206,7 +206,7 @@ public class UserManagementServlet extends HttpServlet {
User user = new User();
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.setDisplayName(values.get("displayName"));
@@ -214,7 +214,7 @@ public class UserManagementServlet extends HttpServlet {
try {
user.setRole(Role.fromCode(values.get("role")));
} catch (IllegalArgumentException ex) {
errors.put("role", "Select a role.");
errors.put("role", "请选择角色。");
}
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)) {
return false;
}
errors.put("active", "Select an active state.");
errors.put("active", "请选择启用状态。");
return false;
}
@@ -3,9 +3,9 @@ package com.mzh.library.entity;
import java.util.Locale;
public enum BookStatus {
AVAILABLE("available", "Available"),
UNAVAILABLE("unavailable", "Unavailable"),
ARCHIVED("archived", "Archived");
AVAILABLE("available", "可借"),
UNAVAILABLE("unavailable", "不可借"),
ARCHIVED("archived", "已归档");
private final String code;
private final String displayName;
@@ -145,7 +145,7 @@ public class BorrowRecord {
}
public String getDisplayStatusName() {
return isOverdue() ? "Overdue" : status.getDisplayName();
return isOverdue() ? "逾期" : status.getDisplayName();
}
public String getBorrowedAtText() {
@@ -3,8 +3,8 @@ package com.mzh.library.entity;
import java.util.Locale;
public enum BorrowRecordStatus {
ACTIVE("active", "Active"),
RETURNED("returned", "Returned");
ACTIVE("active", "借阅中"),
RETURNED("returned", "已归还");
private final String code;
private final String displayName;
@@ -3,9 +3,9 @@ package com.mzh.library.entity;
import java.util.Locale;
public enum ReaderStatus {
ACTIVE("active", "Active"),
SUSPENDED("suspended", "Suspended"),
INACTIVE("inactive", "Inactive");
ACTIVE("active", "正常"),
SUSPENDED("suspended", "暂停"),
INACTIVE("inactive", "停用");
private final String code;
private final String displayName;
@@ -3,9 +3,9 @@ package com.mzh.library.entity;
import java.util.Locale;
public enum Role {
ADMINISTRATOR("administrator", "Administrator"),
LIBRARIAN("librarian", "Librarian"),
READER("reader", "Reader");
ADMINISTRATOR("administrator", "管理员"),
LIBRARIAN("librarian", "馆员"),
READER("reader", "读者");
private final String code;
private final String displayName;
@@ -131,7 +131,7 @@ public class SystemLog {
return username;
}
return operatorId == null ? "System" : "User #" + operatorId;
return operatorId == null ? "系统" : "用户 #" + operatorId;
}
public String getOperatorMetaText() {
@@ -144,7 +144,7 @@ public class SystemLog {
if (operatorId != null && (!displayName.isEmpty() || !username.isEmpty())) {
appendMeta(meta, "#" + operatorId);
}
appendMeta(meta, trim(operatorRole));
appendMeta(meta, displayRole(operatorRole));
return meta.toString();
}
@@ -157,8 +157,22 @@ public class SystemLog {
}
public String getResultStatusName() {
String trimmed = trim(resultStatus);
return trimmed.isEmpty() ? "Unknown" : trimmed;
String normalized = trim(resultStatus).toLowerCase(Locale.ROOT);
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) {
@@ -174,4 +188,16 @@ public class SystemLog {
private String trim(String value) {
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() {
return active ? "Active" : "Inactive";
return active ? "启用" : "停用";
}
public String getCreatedAtText() {
@@ -62,7 +62,7 @@ public class AuthorizationFilter implements Filter {
logDeniedAccess(user, requiredRule, path);
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);
}
@@ -17,9 +17,9 @@ import java.util.logging.Logger;
public class AuthServiceImpl implements AuthService {
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 INVALID_MESSAGE = "Invalid username or password.";
private static final String UNAVAILABLE_MESSAGE = "Login service is temporarily unavailable. Please try again later.";
private static final String REQUIRED_MESSAGE = "请输入用户名和密码。";
private static final String INVALID_MESSAGE = "用户名或密码不正确。";
private static final String UNAVAILABLE_MESSAGE = "登录服务暂时不可用,请稍后重试。";
private final UserDao userDao;
private final PermissionPolicy permissionPolicy;
@@ -21,10 +21,10 @@ import java.util.logging.Logger;
public class BookServiceImpl implements BookService {
private static final Logger LOGGER = Logger.getLogger(BookServiceImpl.class.getName());
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 CATEGORY_VALIDATION_MESSAGE = "Please correct the highlighted category fields.";
private static final String DENIED_MESSAGE = "You do not have permission to manage books.";
"图书服务暂时不可用,请稍后重试。";
private static final String VALIDATION_MESSAGE = "请修正高亮的图书字段。";
private static final String CATEGORY_VALIDATION_MESSAGE = "请修正高亮的分类字段。";
private static final String DENIED_MESSAGE = "您无权管理图书。";
private final BookDao bookDao;
private final PermissionPolicy permissionPolicy;
@@ -51,7 +51,7 @@ public class BookServiceImpl implements BookService {
@Override
public ServiceResult<Optional<BookCategory>> findCategory(long id) {
if (id <= 0) {
return ServiceResult.failure("Select a valid category.");
return ServiceResult.failure("请选择有效的分类。");
}
try {
@@ -76,13 +76,13 @@ public class BookServiceImpl implements BookService {
try {
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);
}
long id = bookDao.createCategory(category);
LOGGER.info("Created book category id=" + id + " actorId=" + actor.getId());
return ServiceResult.success(id, "Category created.");
return ServiceResult.success(id, "分类已创建。");
} catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to create book category actorId=" + actor.getId()
+ " name=" + safeCategoryName(category), ex);
@@ -105,16 +105,16 @@ public class BookServiceImpl implements BookService {
try {
Optional<BookCategory> existingWithName = bookDao.findCategoryByName(category.getName());
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);
}
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());
return ServiceResult.success(null, "Category updated.");
return ServiceResult.success(null, "分类已更新。");
} catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to update book category id=" + category.getId()
+ " actorId=" + actor.getId(), ex);
@@ -128,25 +128,25 @@ public class BookServiceImpl implements BookService {
return ServiceResult.failure(DENIED_MESSAGE);
}
if (id <= 0) {
return ServiceResult.failure("Select a valid category.");
return ServiceResult.failure("请选择有效的分类。");
}
try {
if (!bookDao.findCategoryById(id).isPresent()) {
return ServiceResult.failure("Category was not found.");
return ServiceResult.failure("未找到分类。");
}
if (bookDao.countBooksByCategoryId(id) > 0) {
Map<String, String> errors = new LinkedHashMap<>();
errors.put("category", "Category is used by existing books and cannot be deleted.");
return ServiceResult.validationFailure("Category is used by existing books and cannot be deleted.",
errors.put("category", "该分类已被现有图书使用,不能删除。");
return ServiceResult.validationFailure("该分类已被现有图书使用,不能删除。",
errors);
}
if (!bookDao.deleteCategory(id)) {
return ServiceResult.failure("Category was not found.");
return ServiceResult.failure("未找到分类。");
}
LOGGER.info("Deleted book category id=" + id + " actorId=" + actor.getId());
return ServiceResult.success(null, "Category deleted.");
return ServiceResult.success(null, "分类已删除。");
} catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to delete book category id=" + id + " actorId=" + actor.getId(), ex);
return ServiceResult.failure(UNAVAILABLE_MESSAGE);
@@ -158,8 +158,8 @@ public class BookServiceImpl implements BookService {
BookSearchCriteria normalized = criteria == null ? new BookSearchCriteria() : criteria;
if (normalized.getCategoryId() != null && normalized.getCategoryId() <= 0) {
Map<String, String> errors = new LinkedHashMap<>();
errors.put("categoryId", "Select a valid category.");
return ServiceResult.validationFailure("Please correct the catalog search filters.", errors);
errors.put("categoryId", "请选择有效的分类。");
return ServiceResult.validationFailure("请修正馆藏检索筛选条件。", errors);
}
try {
@@ -173,7 +173,7 @@ public class BookServiceImpl implements BookService {
@Override
public ServiceResult<Optional<Book>> findBook(long id) {
if (id <= 0) {
return ServiceResult.failure("Select a valid book.");
return ServiceResult.failure("请选择有效的图书。");
}
try {
@@ -198,13 +198,13 @@ public class BookServiceImpl implements BookService {
try {
if (bookDao.findByIdentifier(book.getIdentifier()).isPresent()) {
errors.put("identifier", "Book identifier is already in use.");
errors.put("identifier", "图书编号已被使用。");
return ServiceResult.validationFailure(VALIDATION_MESSAGE, errors);
}
long id = bookDao.create(book);
LOGGER.info("Created book id=" + id + " actorId=" + actor.getId());
return ServiceResult.success(id, "Book created.");
return ServiceResult.success(id, "图书已创建。");
} catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to create book actorId=" + actor.getId(), ex);
return ServiceResult.failure(UNAVAILABLE_MESSAGE);
@@ -226,16 +226,16 @@ public class BookServiceImpl implements BookService {
try {
Optional<Book> existingWithIdentifier = bookDao.findByIdentifier(book.getIdentifier());
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);
}
if (!bookDao.update(book)) {
return ServiceResult.failure("Book was not found.");
return ServiceResult.failure("未找到图书。");
}
LOGGER.info("Updated book id=" + book.getId() + " actorId=" + actor.getId());
return ServiceResult.success(null, "Book updated.");
return ServiceResult.success(null, "图书已更新。");
} catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to update book id=" + book.getId() + " actorId=" + actor.getId(), ex);
return ServiceResult.failure(UNAVAILABLE_MESSAGE);
@@ -248,16 +248,16 @@ public class BookServiceImpl implements BookService {
return ServiceResult.failure(DENIED_MESSAGE);
}
if (id <= 0) {
return ServiceResult.failure("Select a valid book.");
return ServiceResult.failure("请选择有效的图书。");
}
try {
if (!bookDao.delete(id)) {
return ServiceResult.failure("Book was not found.");
return ServiceResult.failure("未找到图书。");
}
LOGGER.info("Deleted book id=" + id + " actorId=" + actor.getId());
return ServiceResult.success(null, "Book deleted.");
return ServiceResult.success(null, "图书已删除。");
} catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to delete book id=" + id + " actorId=" + actor.getId(), ex);
return ServiceResult.failure(UNAVAILABLE_MESSAGE);
@@ -288,30 +288,30 @@ public class BookServiceImpl implements BookService {
private Map<String, String> validate(Book book, boolean requireId) {
Map<String, String> errors = new LinkedHashMap<>();
if (book == null) {
errors.put("book", "Book details are required.");
errors.put("book", "请填写图书详情。");
return errors;
}
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, "title", book.getTitle(), "Title", 200);
requireLength(errors, "author", book.getAuthor(), "Author", 120);
requireLength(errors, "identifier", book.getIdentifier(), "图书编号", 64);
requireLength(errors, "title", book.getTitle(), "书名", 200);
requireLength(errors, "author", book.getAuthor(), "作者", 120);
if (book.getCategoryId() <= 0) {
errors.put("categoryId", "Select a category.");
errors.put("categoryId", "请选择分类。");
}
if (book.getTotalCopies() < 0) {
errors.put("totalCopies", "Total copies cannot be negative.");
errors.put("totalCopies", "馆藏总数不能为负数。");
}
if (book.getAvailableCopies() < 0) {
errors.put("availableCopies", "Available copies cannot be negative.");
errors.put("availableCopies", "可借数量不能为负数。");
}
if (book.getAvailableCopies() > book.getTotalCopies()) {
errors.put("availableCopies", "Available copies cannot exceed total copies.");
errors.put("availableCopies", "可借数量不能超过馆藏总数。");
}
if (book.getStatus() == null) {
errors.put("status", "Select a status.");
errors.put("status", "请选择状态。");
}
return errors;
}
@@ -319,16 +319,16 @@ public class BookServiceImpl implements BookService {
private Map<String, String> validate(BookCategory category, boolean requireId) {
Map<String, String> errors = new LinkedHashMap<>();
if (category == null) {
errors.put("category", "Category details are required.");
errors.put("category", "请填写分类详情。");
return errors;
}
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) {
errors.put("description", "Description must be 255 characters or fewer.");
errors.put("description", "说明不能超过 255 个字符。");
}
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) {
if (value == null || value.isEmpty()) {
errors.put(field, label + " is required.");
errors.put(field, "请填写" + label + "");
return;
}
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 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 DENIED_MESSAGE = "You do not have permission to manage borrowing.";
private static final String HISTORY_DENIED_MESSAGE = "You do not have permission to view loan history.";
"借阅服务暂时不可用,请稍后重试。";
private static final String VALIDATION_MESSAGE = "请修正高亮的借阅字段。";
private static final String DENIED_MESSAGE = "您无权管理借阅。";
private static final String HISTORY_DENIED_MESSAGE = "您无权查看借阅历史。";
private static final int LOAN_DAYS = 14;
private static final int MAX_RENEWALS = 1;
@@ -68,7 +68,7 @@ public class BorrowingServiceImpl implements BorrowingService {
BorrowRecordSearchCriteria normalized = criteria == null ? new BorrowRecordSearchCriteria() : criteria;
Map<String, String> errors = validateSearch(normalized);
if (!errors.isEmpty()) {
return ServiceResult.validationFailure("Please correct the borrowing search filters.", errors);
return ServiceResult.validationFailure("请修正借阅检索筛选条件。", errors);
}
try {
@@ -98,13 +98,13 @@ public class BorrowingServiceImpl implements BorrowingService {
Optional<Reader> readerResult = borrowRecordDao.findReaderByIdentifierForUpdate(connection,
normalizedReaderIdentifier);
if (!readerResult.isPresent()) {
transactionErrors.put("readerIdentifier", "Reader was not found.");
transactionErrors.put("readerIdentifier", "未找到读者。");
}
Optional<Book> bookResult = borrowRecordDao.findBookByIdentifierForUpdate(connection,
normalizedBookIdentifier);
if (!bookResult.isPresent()) {
transactionErrors.put("bookIdentifier", "Book was not found.");
transactionErrors.put("bookIdentifier", "未找到图书。");
}
if (!transactionErrors.isEmpty()) {
@@ -134,7 +134,7 @@ public class BorrowingServiceImpl implements BorrowingService {
LOGGER.info("Borrowed book recordId=" + id + " readerId=" + reader.getId()
+ " bookId=" + book.getId() + " actorId=" + actor.getId());
return ServiceResult.success(id, "Book borrowed.");
return ServiceResult.success(id, "图书已借出。");
});
} catch (DaoException ex) {
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);
}
if (recordId <= 0) {
return ServiceResult.failure("Select a valid borrowing record.");
return ServiceResult.failure("请选择有效的借阅记录。");
}
try {
return transactionExecutor.execute(connection -> {
Optional<BorrowRecord> recordResult = borrowRecordDao.findByIdForUpdate(connection, recordId);
if (!recordResult.isPresent()) {
return ServiceResult.failure("Borrowing record was not found.");
return ServiceResult.failure("未找到借阅记录。");
}
BorrowRecord record = recordResult.get();
Map<String, String> errors = validateActiveLoan(record);
if (!errors.isEmpty()) {
return ServiceResult.validationFailure("Borrowing record cannot be returned.", errors);
return ServiceResult.validationFailure("借阅记录不能归还。", errors);
}
if (!borrowRecordDao.markReturned(connection, recordId, now())) {
@@ -172,7 +172,7 @@ public class BorrowingServiceImpl implements BorrowingService {
borrowRecordDao.incrementAvailableCopies(connection, record.getBookId());
LOGGER.info("Returned borrow recordId=" + recordId + " actorId=" + actor.getId());
return ServiceResult.success(null, "Book returned.");
return ServiceResult.success(null, "图书已归还。");
});
} catch (DaoException 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);
}
if (recordId <= 0) {
return ServiceResult.failure("Select a valid borrowing record.");
return ServiceResult.failure("请选择有效的借阅记录。");
}
try {
return transactionExecutor.execute(connection -> {
Optional<BorrowRecord> recordResult = borrowRecordDao.findByIdForUpdate(connection, recordId);
if (!recordResult.isPresent()) {
return ServiceResult.failure("Borrowing record was not found.");
return ServiceResult.failure("未找到借阅记录。");
}
BorrowRecord record = recordResult.get();
Map<String, String> errors = validateActiveLoan(record);
if (record.getRenewalCount() >= MAX_RENEWALS) {
errors.put("renewalCount", "This loan has already reached the renewal limit.");
errors.put("renewalCount", "该借阅已达到续借次数上限。");
}
if (!errors.isEmpty()) {
return ServiceResult.validationFailure("Borrowing record cannot be renewed.", errors);
return ServiceResult.validationFailure("借阅记录不能续借。", errors);
}
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());
return ServiceResult.success(null, "Loan renewed.");
return ServiceResult.success(null, "借阅已续借。");
});
} catch (DaoException 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 {
Optional<Reader> readerResult = borrowRecordDao.findReaderByUserId(actor.getId());
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()));
@@ -246,16 +246,16 @@ public class BorrowingServiceImpl implements BorrowingService {
private void validateBorrowEligibility(Map<String, String> errors, Reader reader, Book book,
java.sql.Connection connection) {
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());
if (activeLoans >= reader.getMaxBorrowCount()) {
errors.put("readerIdentifier", "Reader has reached the active borrowing limit.");
errors.put("readerIdentifier", "读者已达到在借数量上限。");
}
if (book.getStatus() != BookStatus.AVAILABLE) {
errors.put("bookIdentifier", "Book status does not allow borrowing.");
errors.put("bookIdentifier", "图书状态不允许借阅。");
} 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 {
BorrowRecordStatus.fromCode(statusCode);
} catch (IllegalArgumentException ex) {
errors.put("status", "Select a valid borrowing status.");
errors.put("status", "请选择有效的借阅状态。");
}
}
return errors;
@@ -278,26 +278,26 @@ public class BorrowingServiceImpl implements BorrowingService {
private Map<String, String> validateBorrowIdentifiers(String readerIdentifier, String bookIdentifier) {
Map<String, String> errors = new LinkedHashMap<>();
requireLength(errors, "readerIdentifier", readerIdentifier, "Reader ID", 64);
requireLength(errors, "bookIdentifier", bookIdentifier, "Book ID", 64);
requireLength(errors, "readerIdentifier", readerIdentifier, "读者编号", 64);
requireLength(errors, "bookIdentifier", bookIdentifier, "图书编号", 64);
return errors;
}
private Map<String, String> validateActiveLoan(BorrowRecord record) {
Map<String, String> errors = new LinkedHashMap<>();
if (record.getStatus() != BorrowRecordStatus.ACTIVE || record.getReturnedAt() != null) {
errors.put("status", "Only active loans can use this action.");
errors.put("status", "只有借阅中的记录可以执行此操作。");
}
return errors;
}
private void requireLength(Map<String, String> errors, String field, String value, String label, int maxLength) {
if (value == null || value.isEmpty()) {
errors.put(field, label + " is required.");
errors.put(field, "请填写" + label + "");
return;
}
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 {
private static final Logger LOGGER = Logger.getLogger(ReaderServiceImpl.class.getName());
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 SEARCH_VALIDATION_MESSAGE = "Please correct the reader search filters.";
private static final String DENIED_MESSAGE = "You do not have permission to manage readers.";
"读者服务暂时不可用,请稍后重试。";
private static final String VALIDATION_MESSAGE = "请修正高亮的读者字段。";
private static final String SEARCH_VALIDATION_MESSAGE = "请修正读者检索筛选条件。";
private static final String DENIED_MESSAGE = "您无权管理读者。";
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 EMAIL_PATTERN = Pattern.compile("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$");
@@ -61,7 +61,7 @@ public class ReaderServiceImpl implements ReaderService {
@Override
public ServiceResult<Optional<Reader>> findReader(long id) {
if (id <= 0) {
return ServiceResult.failure("Select a valid reader.");
return ServiceResult.failure("请选择有效的读者。");
}
try {
@@ -86,17 +86,17 @@ public class ReaderServiceImpl implements ReaderService {
try {
if (readerDao.findByIdentifier(reader.getIdentifier()).isPresent()) {
errors.put("identifier", "Reader identifier is already in use.");
errors.put("identifier", "读者编号已被使用。");
return ServiceResult.validationFailure(VALIDATION_MESSAGE, errors);
}
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);
}
long id = readerDao.create(reader);
LOGGER.info("Created reader id=" + id + " actorId=" + actor.getId());
return ServiceResult.success(id, "Reader profile created.");
return ServiceResult.success(id, "读者档案已创建。");
} catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to create reader actorId=" + actor.getId(), ex);
return ServiceResult.failure(UNAVAILABLE_MESSAGE);
@@ -118,23 +118,23 @@ public class ReaderServiceImpl implements ReaderService {
try {
Optional<Reader> existingWithIdentifier = readerDao.findByIdentifier(reader.getIdentifier());
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);
}
if (reader.getUserId() != null) {
Optional<Reader> existingWithUser = readerDao.findByUserId(reader.getUserId());
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);
}
}
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());
return ServiceResult.success(null, "Reader profile updated.");
return ServiceResult.success(null, "读者档案已更新。");
} catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to update reader id=" + reader.getId() + " actorId=" + actor.getId(), ex);
return ServiceResult.failure(UNAVAILABLE_MESSAGE);
@@ -147,16 +147,16 @@ public class ReaderServiceImpl implements ReaderService {
return ServiceResult.failure(DENIED_MESSAGE);
}
if (id <= 0) {
return ServiceResult.failure("Select a valid reader.");
return ServiceResult.failure("请选择有效的读者。");
}
try {
if (!readerDao.deactivate(id)) {
return ServiceResult.failure("Reader profile was not found.");
return ServiceResult.failure("未找到读者档案。");
}
LOGGER.info("Deactivated reader id=" + id + " actorId=" + actor.getId());
return ServiceResult.success(null, "Reader profile deactivated.");
return ServiceResult.success(null, "读者档案已停用。");
} catch (DaoException ex) {
LOGGER.log(Level.SEVERE, "Unable to deactivate reader id=" + id + " actorId=" + actor.getId(), ex);
return ServiceResult.failure(UNAVAILABLE_MESSAGE);
@@ -183,7 +183,7 @@ public class ReaderServiceImpl implements ReaderService {
try {
criteria.setStatusCode(ReaderStatus.fromCode(criteria.getStatusCode()).getCode());
} catch (IllegalArgumentException ex) {
errors.put("status", "Select a valid status.");
errors.put("status", "请选择有效的状态。");
}
}
return errors;
@@ -192,24 +192,24 @@ public class ReaderServiceImpl implements ReaderService {
private Map<String, String> validate(Reader reader, boolean requireId) {
Map<String, String> errors = new LinkedHashMap<>();
if (reader == null) {
errors.put("reader", "Reader details are required.");
errors.put("reader", "请填写读者详情。");
return errors;
}
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, "fullName", reader.getFullName(), "Full name", 100);
requireLength(errors, "identifier", reader.getIdentifier(), "读者编号", 64);
requireLength(errors, "fullName", reader.getFullName(), "姓名", 100);
if (reader.getUserId() != null && reader.getUserId() <= 0) {
errors.put("userId", "Linked account ID must be positive.");
errors.put("userId", "关联账户 ID 必须为正数。");
}
validateContact(errors, reader);
if (reader.getStatus() == null) {
errors.put("status", "Select a status.");
errors.put("status", "请选择状态。");
}
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;
}
@@ -218,24 +218,24 @@ public class ReaderServiceImpl implements ReaderService {
String phone = reader.getPhone();
String email = reader.getEmail();
if ((phone == null || phone.isEmpty()) && (email == null || email.isEmpty())) {
errors.put("phone", "Phone or email is required.");
errors.put("phone", "请填写电话或邮箱。");
return;
}
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()) {
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) {
if (value == null || value.isEmpty()) {
errors.put(field, label + " is required.");
errors.put(field, "请填写" + label + "");
return;
}
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 {
private static final Logger LOGGER = Logger.getLogger(ReportServiceImpl.class.getName());
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 final ReportDao reportDao;
@@ -20,9 +20,9 @@ import java.util.logging.Logger;
public class SystemLogServiceImpl implements SystemLogService {
private static final Logger LOGGER = Logger.getLogger(SystemLogServiceImpl.class.getName());
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 VALIDATION_MESSAGE = "Please correct the system log search filters.";
"系统日志服务暂时不可用,请稍后重试。";
private static final String DENIED_MESSAGE = "您无权查看系统日志。";
private static final String VALIDATION_MESSAGE = "请修正系统日志检索筛选条件。";
private final SystemLogDao systemLogDao;
private final PermissionPolicy permissionPolicy;
@@ -62,18 +62,18 @@ public class SystemLogServiceImpl implements SystemLogService {
private Map<String, String> validate(SystemLogSearchCriteria criteria) {
Map<String, String> errors = new LinkedHashMap<>();
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) {
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.getCreatedToText(), "createdTo", "End date", errors, criteria, false);
parseDate(criteria.getCreatedFromText(), "createdFrom", "开始日期", errors, criteria, true);
parseDate(criteria.getCreatedToText(), "createdTo", "结束日期", errors, criteria, false);
if (criteria.getCreatedFrom() != null
&& criteria.getCreatedTo() != null
&& criteria.getCreatedFrom().isAfter(criteria.getCreatedTo())) {
errors.put("createdTo", "End date must be on or after start date.");
errors.put("createdTo", "结束日期必须晚于或等于开始日期。");
}
return errors;
}
@@ -91,7 +91,7 @@ public class SystemLogServiceImpl implements SystemLogService {
criteria.setCreatedTo(parsed);
}
} 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 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 SEARCH_VALIDATION_MESSAGE = "Please correct the account search filters.";
private static final String DENIED_MESSAGE = "You do not have permission to manage users.";
private static final String SELF_DEACTIVATE_MESSAGE = "You cannot deactivate your own administrator account.";
private static final String SELF_ROLE_MESSAGE = "You cannot change your own administrator role.";
"用户管理服务暂时不可用,请稍后重试。";
private static final String VALIDATION_MESSAGE = "请修正高亮的账户字段。";
private static final String SEARCH_VALIDATION_MESSAGE = "请修正账户检索筛选条件。";
private static final String DENIED_MESSAGE = "您无权管理用户。";
private static final String SELF_DEACTIVATE_MESSAGE = "不能停用您自己的管理员账户。";
private static final String SELF_ROLE_MESSAGE = "不能修改您自己的管理员角色。";
private final UserAccountDao userAccountDao;
private final SystemLogDao systemLogDao;
@@ -80,7 +80,7 @@ public class UserAccountServiceImpl implements UserAccountService {
return ServiceResult.failure(DENIED_MESSAGE);
}
if (id <= 0) {
return ServiceResult.failure("Select a valid user account.");
return ServiceResult.failure("请选择有效的用户账户。");
}
try {
@@ -105,7 +105,7 @@ public class UserAccountServiceImpl implements UserAccountService {
try {
if (userAccountDao.findByUsername(user.getUsername()).isPresent()) {
errors.put("username", "Username is already in use.");
errors.put("username", "用户名已被使用。");
return ServiceResult.validationFailure(VALIDATION_MESSAGE, errors);
}
@@ -113,10 +113,10 @@ public class UserAccountServiceImpl implements UserAccountService {
return transactionExecutor.execute(connection -> {
long id = userAccountDao.create(connection, user);
systemLogDao.create(connection, auditLog(actor, "user.create", id,
"Created account username=" + user.getUsername() + " role=" + user.getRole().getCode(),
"创建账户:用户名=" + user.getUsername() + ",角色=" + user.getRole().getDisplayName(),
requestIp));
LOGGER.info("Created user id=" + id + " actorId=" + actor.getId());
return ServiceResult.success(id, "User account created.");
return ServiceResult.success(id, "用户账户已创建。");
});
} catch (DaoException | IllegalStateException ex) {
LOGGER.log(Level.SEVERE, "Unable to create user actorId=" + actor.getId()
@@ -140,7 +140,7 @@ public class UserAccountServiceImpl implements UserAccountService {
try {
Optional<User> existingResult = userAccountDao.findById(user.getId());
if (!existingResult.isPresent()) {
return ServiceResult.failure("User account was not found.");
return ServiceResult.failure("未找到用户账户。");
}
protectCurrentAdministrator(actor, user, errors);
@@ -158,15 +158,15 @@ public class UserAccountServiceImpl implements UserAccountService {
final boolean passwordChanged = updatePassword;
return transactionExecutor.execute(connection -> {
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(),
"Updated account username=" + user.getUsername() + " role=" + user.getRole().getCode()
+ " active=" + user.isActive()
+ (passwordChanged ? " passwordReset=true" : ""),
"更新账户:用户名=" + user.getUsername() + ",角色=" + user.getRole().getDisplayName()
+ ",状态=" + (user.isActive() ? "启用" : "停用")
+ (passwordChanged ? ",已重置密码" : ""),
requestIp));
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) {
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);
}
if (id <= 0) {
return ServiceResult.failure("Select a valid user account.");
return ServiceResult.failure("请选择有效的用户账户。");
}
if (actor.getId() == id) {
Map<String, String> errors = new LinkedHashMap<>();
@@ -191,20 +191,20 @@ public class UserAccountServiceImpl implements UserAccountService {
try {
Optional<User> existingResult = userAccountDao.findById(id);
if (!existingResult.isPresent()) {
return ServiceResult.failure("User account was not found.");
return ServiceResult.failure("未找到用户账户。");
}
User user = existingResult.get();
user.setActive(false);
return transactionExecutor.execute(connection -> {
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,
"Deactivated account username=" + user.getUsername(),
"停用账户:用户名=" + user.getUsername(),
requestIp));
LOGGER.info("Deactivated user id=" + id + " actorId=" + actor.getId());
return ServiceResult.success(null, "User account deactivated.");
return ServiceResult.success(null, "用户账户已停用。");
});
} catch (DaoException 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 {
criteria.setRoleCode(Role.fromCode(criteria.getRoleCode()).getCode());
} 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()
&& !UserSearchCriteria.ACTIVE_STATUS.equals(activeStatus)
&& !UserSearchCriteria.INACTIVE_STATUS.equals(activeStatus)) {
errors.put("active", "Select a valid active state.");
errors.put("active", "请选择有效的启用状态。");
}
return errors;
}
@@ -234,19 +234,19 @@ public class UserAccountServiceImpl implements UserAccountService {
private Map<String, String> validateUser(User user, boolean requireId, String password, boolean requirePassword) {
Map<String, String> errors = new LinkedHashMap<>();
if (user == null) {
errors.put("user", "User account details are required.");
errors.put("user", "请填写用户账户详情。");
return errors;
}
if (requireId && user.getId() <= 0) {
errors.put("id", "Select a valid user account.");
errors.put("id", "请选择有效的用户账户。");
}
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) {
errors.put("role", "Select a role.");
errors.put("role", "请选择角色。");
}
validatePassword(errors, password, requirePassword);
return errors;
@@ -256,12 +256,12 @@ public class UserAccountServiceImpl implements UserAccountService {
String trimmed = password == null ? "" : password.trim();
if (trimmed.isEmpty()) {
if (required) {
errors.put("password", "Password is required.");
errors.put("password", "请填写密码。");
}
return;
}
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) {
if (value == null || value.isEmpty()) {
errors.put(field, label + " is required.");
errors.put(field, "请填写" + label + "");
return;
}
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="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">
</head>
<body>
<%@ include file="/WEB-INF/jsp/common/header.jspf" %>
<main class="page-shell">
<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>
<c:if test="${not empty errorMessage}">
@@ -36,7 +36,7 @@
<div class="form-grid">
<div class="form-field">
<label for="username">Username</label>
<label for="username">用户名</label>
<c:choose>
<c:when test="${user.id > 0}">
<input id="username" type="text" value="${fn:escapeXml(usernameValue)}" disabled>
@@ -51,7 +51,7 @@
</div>
<div class="form-field">
<label for="displayName">Display name</label>
<label for="displayName">显示名称</label>
<input id="displayName" name="displayName" type="text"
value="${fn:escapeXml(displayNameValue)}" required>
<c:if test="${not empty errors.displayName}">
@@ -60,9 +60,9 @@
</div>
<div class="form-field">
<label for="role">Role</label>
<label for="role">角色</label>
<select id="role" name="role" required>
<option value="">Select role</option>
<option value="">请选择角色</option>
<c:forEach var="role" items="${roles}">
<option value="${role.code}" <c:if test="${roleValue == role.code}">selected</c:if>>
<c:out value="${role.displayName}" />
@@ -75,13 +75,13 @@
</div>
<div class="form-field">
<label for="active">Active state</label>
<label for="active">启用状态</label>
<select id="active" name="active" required>
<option value="true" <c:if test="${activeValue == true or activeValue == 'true'}">selected</c:if>>
Active
启用
</option>
<option value="false" <c:if test="${activeValue == false or activeValue == 'false'}">selected</c:if>>
Inactive
停用
</option>
</select>
<c:if test="${not empty errors.active}">
@@ -92,8 +92,8 @@
<div class="form-field">
<label for="password">
<c:choose>
<c:when test="${user.id > 0}">New password</c:when>
<c:otherwise>Password</c:otherwise>
<c:when test="${user.id > 0}">新密码</c:when>
<c:otherwise>密码</c:otherwise>
</c:choose>
</label>
<c:choose>
@@ -111,8 +111,8 @@
</div>
<div class="form-actions">
<button class="button button-primary" type="submit">Save</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/users">Cancel</a>
<button class="button button-primary" type="submit">保存</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/users">取消</a>
</div>
</form>
</section>
@@ -2,11 +2,11 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">
</head>
<body>
@@ -14,11 +14,11 @@
<main class="page-shell">
<section class="dashboard-hero catalog-hero" aria-labelledby="manage-users-title">
<div>
<p class="eyebrow">Administration</p>
<h1 id="manage-users-title">Manage users</h1>
<p>Create, update, deactivate, and review administrator, librarian, and reader accounts.</p>
<p class="eyebrow">系统管理</p>
<h1 id="manage-users-title">管理用户</h1>
<p>创建、更新、停用和查看管理员、馆员与读者账户。</p>
</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>
<c:if test="${not empty successMessage}">
@@ -32,10 +32,10 @@
</div>
</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">
<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)}">
<c:if test="${not empty errors.keyword}">
<span class="field-error"><c:out value="${errors.keyword}" /></span>
@@ -43,9 +43,9 @@
</div>
<div class="search-field">
<label for="role">Role</label>
<label for="role">角色</label>
<select id="role" name="role">
<option value="">All roles</option>
<option value="">全部角色</option>
<c:forEach var="role" items="${roles}">
<option value="${role.code}" <c:if test="${criteria.roleCode == role.code}">selected</c:if>>
<c:out value="${role.displayName}" />
@@ -58,40 +58,40 @@
</div>
<div class="search-field">
<label for="active">Active state</label>
<label for="active">启用状态</label>
<select id="active" name="active">
<option value="">All states</option>
<option value="active" <c:if test="${criteria.activeStatus == 'active'}">selected</c:if>>Active</option>
<option value="inactive" <c:if test="${criteria.activeStatus == 'inactive'}">selected</c:if>>Inactive</option>
<option value="">全部状态</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>>停用</option>
</select>
<c:if test="${not empty errors.active}">
<span class="field-error"><c:out value="${errors.active}" /></span>
</c:if>
</div>
<button class="button button-primary" type="submit">Search</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/users">Clear</a>
<button class="button button-primary" type="submit">检索</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/users">清空</a>
</form>
</section>
<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:when test="${empty users}">
<p class="empty-state">No user accounts match the current filters.</p>
<p class="empty-state">没有符合当前筛选条件的用户账户。</p>
</c:when>
<c:otherwise>
<div class="table-scroll">
<table class="data-table user-table">
<thead>
<tr>
<th scope="col">Username</th>
<th scope="col">Display name</th>
<th scope="col">Role</th>
<th scope="col">State</th>
<th scope="col">Created</th>
<th scope="col">Updated</th>
<th scope="col">Actions</th>
<th scope="col">用户名</th>
<th scope="col">显示名称</th>
<th scope="col">角色</th>
<th scope="col">状态</th>
<th scope="col">创建时间</th>
<th scope="col">更新时间</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody>
@@ -110,17 +110,17 @@
<td>
<div class="table-actions">
<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: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:otherwise>
<form action="${pageContext.request.contextPath}/admin/users/deactivate"
method="post"
onsubmit="return confirm('Deactivate this user account?');">
onsubmit="return confirm('确定停用这个用户账户吗?');">
<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>
</c:otherwise>
</c:choose>
+7 -7
View File
@@ -2,11 +2,11 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">
</head>
<body class="auth-page">
@@ -14,8 +14,8 @@
<main class="auth-shell">
<section class="login-panel" aria-labelledby="login-title">
<div>
<p class="eyebrow">Library Management</p>
<h1 id="login-title">Sign in</h1>
<p class="eyebrow">图书馆管理</p>
<h1 id="login-title">登录</h1>
</div>
<c:if test="${not empty errorMessage}">
@@ -26,7 +26,7 @@
<form class="login-form" action="${pageContext.request.contextPath}/login" method="post" novalidate>
<input type="hidden" name="redirect" value="${fn:escapeXml(redirect)}">
<label for="username">Username</label>
<label for="username">用户名</label>
<input id="username"
name="username"
type="text"
@@ -34,14 +34,14 @@
autocomplete="username"
required>
<label for="password">Password</label>
<label for="password">密码</label>
<input id="password"
name="password"
type="password"
autocomplete="current-password"
required>
<button class="button button-primary" type="submit">Sign in</button>
<button class="button button-primary" type="submit">登录</button>
</form>
</section>
</main>
@@ -1,27 +1,27 @@
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">
</head>
<body>
<%@ include file="/WEB-INF/jsp/common/header.jspf" %>
<main class="page-shell">
<section class="notice-panel" aria-labelledby="unauthorized-title">
<h1 id="unauthorized-title">Access denied</h1>
<h1 id="unauthorized-title">无权访问</h1>
<p>
<c:choose>
<c:when test="${not empty errorMessage}">
<c:out value="${errorMessage}" />
</c:when>
<c:otherwise>You do not have permission to access this page.</c:otherwise>
<c:otherwise>您无权访问此页面。</c:otherwise>
</c:choose>
</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>
</main>
</body>
+22 -22
View File
@@ -2,20 +2,20 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">
</head>
<body>
<%@ include file="/WEB-INF/jsp/common/header.jspf" %>
<main class="page-shell">
<section class="dashboard-hero catalog-hero" aria-labelledby="catalog-title">
<p class="eyebrow">Catalog</p>
<h1 id="catalog-title">Book catalog</h1>
<p>Search the library collection by identifier, title, author, or category.</p>
<p class="eyebrow">馆藏</p>
<h1 id="catalog-title">馆藏检索</h1>
<p>按图书编号、书名、作者或分类检索馆藏。</p>
</section>
<c:if test="${not empty errorMessage}">
@@ -24,27 +24,27 @@
</div>
</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">
<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)}">
</div>
<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)}">
</div>
<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)}">
</div>
<div class="search-field">
<label for="categoryId">Category</label>
<label for="categoryId">分类</label>
<select id="categoryId" name="categoryId">
<option value="">All categories</option>
<option value="">全部分类</option>
<c:forEach var="category" items="${categories}">
<option value="${category.id}" <c:if test="${criteria.categoryId == category.id}">selected</c:if>>
<c:out value="${category.name}" />
@@ -56,31 +56,31 @@
</c:if>
</div>
<button class="button button-primary" type="submit">Search</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/catalog">Clear</a>
<button class="button button-primary" type="submit">检索</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/catalog">清空</a>
<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>
</form>
</section>
<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:when test="${empty books}">
<p class="empty-state">No books match the current filters.</p>
<p class="empty-state">没有符合当前筛选条件的图书。</p>
</c:when>
<c:otherwise>
<div class="table-scroll">
<table class="data-table">
<thead>
<tr>
<th scope="col">Book ID</th>
<th scope="col">Title</th>
<th scope="col">Author</th>
<th scope="col">Category</th>
<th scope="col">Copies</th>
<th scope="col">Status</th>
<th scope="col">图书编号</th>
<th scope="col">书名</th>
<th scope="col">作者</th>
<th scope="col">分类</th>
<th scope="col">馆藏数量</th>
<th scope="col">状态</th>
</tr>
</thead>
<tbody>
@@ -1,11 +1,11 @@
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">
</head>
<body>
@@ -13,13 +13,13 @@
<main class="page-shell">
<section class="dashboard-hero catalog-hero" aria-labelledby="category-title">
<div>
<p class="eyebrow">Book Management</p>
<h1 id="category-title">Manage categories</h1>
<p>Maintain catalog groupings used by book records and search filters.</p>
<p class="eyebrow">图书管理</p>
<h1 id="category-title">管理分类</h1>
<p>维护图书记录和检索筛选使用的馆藏分组。</p>
</div>
<div class="hero-actions">
<a class="button button-primary" href="${pageContext.request.contextPath}/book-categories/new">New category</a>
<a class="button button-secondary" href="${pageContext.request.contextPath}/books">Manage books</a>
<a class="button button-primary" href="${pageContext.request.contextPath}/book-categories/new">新增分类</a>
<a class="button button-secondary" href="${pageContext.request.contextPath}/books">管理图书</a>
</div>
</section>
@@ -35,19 +35,19 @@
</c:if>
<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:when test="${empty categories}">
<p class="empty-state">No categories have been created yet.</p>
<p class="empty-state">尚未创建分类。</p>
</c:when>
<c:otherwise>
<div class="table-scroll">
<table class="data-table category-table">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Description</th>
<th scope="col">Actions</th>
<th scope="col">名称</th>
<th scope="col">说明</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody>
@@ -57,7 +57,7 @@
<td>
<c:choose>
<c:when test="${empty category.description}">
<span class="muted-text">No description</span>
<span class="muted-text">无说明</span>
</c:when>
<c:otherwise>
<c:out value="${category.description}" />
@@ -67,12 +67,12 @@
<td>
<div class="table-actions">
<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"
method="post"
onsubmit="return confirm('Delete this category?');">
onsubmit="return confirm('确定删除这个分类吗?');">
<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>
</div>
</td>
@@ -2,18 +2,18 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">
</head>
<body>
<%@ include file="/WEB-INF/jsp/common/header.jspf" %>
<main class="page-shell">
<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>
<c:if test="${not empty errorMessage}">
@@ -33,7 +33,7 @@
<div class="form-grid">
<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>
<c:if test="${not empty errors.name}">
<span class="field-error"><c:out value="${errors.name}" /></span>
@@ -41,7 +41,7 @@
</div>
<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>
<c:if test="${not empty errors.description}">
<span class="field-error"><c:out value="${errors.description}" /></span>
@@ -56,8 +56,8 @@
</c:if>
<div class="form-actions">
<button class="button button-primary" type="submit">Save</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">Cancel</a>
<button class="button button-primary" type="submit">保存</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">取消</a>
</div>
</form>
</section>
+14 -14
View File
@@ -2,18 +2,18 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">
</head>
<body>
<%@ include file="/WEB-INF/jsp/common/header.jspf" %>
<main class="page-shell">
<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>
<c:if test="${not empty errorMessage}">
@@ -38,7 +38,7 @@
<div class="form-grid">
<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>
<c:if test="${not empty errors.identifier}">
<span class="field-error"><c:out value="${errors.identifier}" /></span>
@@ -46,7 +46,7 @@
</div>
<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>
<c:if test="${not empty errors.title}">
<span class="field-error"><c:out value="${errors.title}" /></span>
@@ -54,7 +54,7 @@
</div>
<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>
<c:if test="${not empty errors.author}">
<span class="field-error"><c:out value="${errors.author}" /></span>
@@ -62,9 +62,9 @@
</div>
<div class="form-field">
<label for="categoryId">Category</label>
<label for="categoryId">分类</label>
<select id="categoryId" name="categoryId" required>
<option value="">Select category</option>
<option value="">请选择分类</option>
<c:forEach var="category" items="${categories}">
<option value="${category.id}" <c:if test="${categoryValue == category.id}">selected</c:if>>
<c:out value="${category.name}" />
@@ -77,7 +77,7 @@
</div>
<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>
<c:if test="${not empty errors.totalCopies}">
<span class="field-error"><c:out value="${errors.totalCopies}" /></span>
@@ -85,7 +85,7 @@
</div>
<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>
<c:if test="${not empty errors.availableCopies}">
<span class="field-error"><c:out value="${errors.availableCopies}" /></span>
@@ -93,9 +93,9 @@
</div>
<div class="form-field">
<label for="status">Status</label>
<label for="status">状态</label>
<select id="status" name="status" required>
<option value="">Select status</option>
<option value="">请选择状态</option>
<c:forEach var="status" items="${statuses}">
<option value="${status.code}" <c:if test="${statusValue == status.code}">selected</c:if>>
<c:out value="${status.displayName}" />
@@ -109,8 +109,8 @@
</div>
<div class="form-actions">
<button class="button button-primary" type="submit">Save</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/books">Cancel</a>
<button class="button button-primary" type="submit">保存</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/books">取消</a>
</div>
</form>
</section>
+29 -29
View File
@@ -2,23 +2,23 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">
</head>
<body>
<%@ include file="/WEB-INF/jsp/common/header.jspf" %>
<main class="page-shell">
<section class="dashboard-hero catalog-hero" aria-labelledby="manage-title">
<p class="eyebrow">Book Management</p>
<h1 id="manage-title">Manage books</h1>
<p>Create, update, delete, and review inventory for catalog records.</p>
<p class="eyebrow">图书管理</p>
<h1 id="manage-title">管理图书</h1>
<p>创建、更新、删除和查看馆藏记录的库存信息。</p>
<div class="hero-actions">
<a class="button button-primary" href="${pageContext.request.contextPath}/books/new">New book</a>
<a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">Categories</a>
<a class="button button-primary" href="${pageContext.request.contextPath}/books/new">新增图书</a>
<a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">分类</a>
</div>
</section>
@@ -33,27 +33,27 @@
</div>
</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">
<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)}">
</div>
<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)}">
</div>
<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)}">
</div>
<div class="search-field">
<label for="categoryId">Category</label>
<label for="categoryId">分类</label>
<select id="categoryId" name="categoryId">
<option value="">All categories</option>
<option value="">全部分类</option>
<c:forEach var="category" items="${categories}">
<option value="${category.id}" <c:if test="${criteria.categoryId == category.id}">selected</c:if>>
<c:out value="${category.name}" />
@@ -65,31 +65,31 @@
</c:if>
</div>
<button class="button button-primary" type="submit">Search</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/books">Clear</a>
<a class="button button-secondary" href="${pageContext.request.contextPath}/catalog">View catalog</a>
<a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">Categories</a>
<button class="button button-primary" type="submit">检索</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/books">清空</a>
<a class="button button-secondary" href="${pageContext.request.contextPath}/catalog">查看馆藏</a>
<a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">分类</a>
</form>
</section>
<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:when test="${empty books}">
<p class="empty-state">No book records match the current filters.</p>
<p class="empty-state">没有符合当前筛选条件的图书记录。</p>
</c:when>
<c:otherwise>
<div class="table-scroll">
<table class="data-table">
<thead>
<tr>
<th scope="col">Book ID</th>
<th scope="col">Title</th>
<th scope="col">Author</th>
<th scope="col">Category</th>
<th scope="col">Copies</th>
<th scope="col">Status</th>
<th scope="col">Actions</th>
<th scope="col">图书编号</th>
<th scope="col">书名</th>
<th scope="col">作者</th>
<th scope="col">分类</th>
<th scope="col">馆藏数量</th>
<th scope="col">状态</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody>
@@ -108,12 +108,12 @@
<td>
<div class="table-actions">
<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"
method="post"
onsubmit="return confirm('Delete this book record?');">
onsubmit="return confirm('确定删除这条图书记录吗?');">
<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>
</div>
</td>
@@ -2,19 +2,19 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">
</head>
<body>
<%@ include file="/WEB-INF/jsp/common/header.jspf" %>
<main class="page-shell">
<section class="form-panel" aria-labelledby="borrow-form-title">
<p class="eyebrow">Borrowing Management</p>
<h1 id="borrow-form-title">New borrow</h1>
<p class="eyebrow">借阅管理</p>
<h1 id="borrow-form-title">新增借阅</h1>
<c:if test="${not empty errorMessage}">
<div class="message message-error" role="alert">
@@ -28,7 +28,7 @@
<form class="borrow-form" action="${pageContext.request.contextPath}/borrowing/create" method="post" novalidate>
<div class="form-grid">
<div class="form-field">
<label for="readerIdentifier">Reader ID</label>
<label for="readerIdentifier">读者编号</label>
<input id="readerIdentifier" name="readerIdentifier" type="text"
value="${fn:escapeXml(readerIdentifierValue)}" required>
<c:if test="${not empty errors.readerIdentifier}">
@@ -37,7 +37,7 @@
</div>
<div class="form-field">
<label for="bookIdentifier">Book ID</label>
<label for="bookIdentifier">图书编号</label>
<input id="bookIdentifier" name="bookIdentifier" type="text"
value="${fn:escapeXml(bookIdentifierValue)}" required>
<c:if test="${not empty errors.bookIdentifier}">
@@ -47,8 +47,8 @@
</div>
<div class="form-actions">
<button class="button button-primary" type="submit">Borrow</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/borrowing">Cancel</a>
<button class="button button-primary" type="submit">借出</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/borrowing">取消</a>
</div>
</form>
</section>
@@ -2,11 +2,11 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">
</head>
<body>
@@ -14,11 +14,11 @@
<main class="page-shell">
<section class="dashboard-hero catalog-hero" aria-labelledby="borrowing-title">
<div>
<p class="eyebrow">Borrowing Management</p>
<h1 id="borrowing-title">Manage borrowing</h1>
<p>Create borrow records, process returns, renew active loans, and review overdue items.</p>
<p class="eyebrow">借阅管理</p>
<h1 id="borrowing-title">管理借阅</h1>
<p>创建借阅记录、处理归还、续借有效借阅并查看逾期项目。</p>
</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>
<c:if test="${not empty successMessage}">
@@ -32,31 +32,31 @@
</div>
</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">
<div class="search-field">
<label for="readerIdentifier">Reader ID</label>
<label for="readerIdentifier">读者编号</label>
<input id="readerIdentifier" name="readerIdentifier" type="text"
value="${fn:escapeXml(criteria.readerIdentifier)}">
</div>
<div class="search-field">
<label for="bookIdentifier">Book ID</label>
<label for="bookIdentifier">图书编号</label>
<input id="bookIdentifier" name="bookIdentifier" type="text"
value="${fn:escapeXml(criteria.bookIdentifier)}">
</div>
<div class="search-field">
<label for="status">Status</label>
<label for="status">状态</label>
<select id="status" name="status">
<option value="">All statuses</option>
<option value="">全部状态</option>
<c:forEach var="status" items="${statuses}">
<option value="${status.code}" <c:if test="${criteria.statusCode == status.code}">selected</c:if>>
<c:out value="${status.displayName}" />
</option>
</c:forEach>
<option value="${overdueStatus}" <c:if test="${criteria.statusCode == overdueStatus}">selected</c:if>>
Overdue
逾期
</option>
</select>
<c:if test="${not empty errors.status}">
@@ -64,30 +64,30 @@
</c:if>
</div>
<button class="button button-primary" type="submit">Search</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/borrowing">Clear</a>
<button class="button button-primary" type="submit">检索</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/borrowing">清空</a>
</form>
</section>
<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:when test="${empty borrowRecords}">
<p class="empty-state">No borrowing records match the current filters.</p>
<p class="empty-state">没有符合当前筛选条件的借阅记录。</p>
</c:when>
<c:otherwise>
<div class="table-scroll">
<table class="data-table borrowing-table">
<thead>
<tr>
<th scope="col">Reader</th>
<th scope="col">Book</th>
<th scope="col">Borrowed</th>
<th scope="col">Due</th>
<th scope="col">Returned</th>
<th scope="col">Renewals</th>
<th scope="col">Status</th>
<th scope="col">Actions</th>
<th scope="col">读者</th>
<th scope="col">图书</th>
<th scope="col">借出时间</th>
<th scope="col">应还时间</th>
<th scope="col">归还时间</th>
<th scope="col">续借次数</th>
<th scope="col">状态</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody>
@@ -108,7 +108,7 @@
<c:when test="${not empty record.returnedAtText}">
<c:out value="${record.returnedAtText}" />
</c:when>
<c:otherwise>Not returned</c:otherwise>
<c:otherwise>未归还</c:otherwise>
</c:choose>
</td>
<td><c:out value="${record.renewalCount}" /> / <c:out value="${maxRenewals}" /></td>
@@ -123,22 +123,22 @@
<div class="table-actions">
<form action="${pageContext.request.contextPath}/borrowing/return"
method="post"
onsubmit="return confirm('Return this book?');">
onsubmit="return confirm('确定归还这本书吗?');">
<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>
<c:if test="${record.renewalCount < maxRenewals}">
<form action="${pageContext.request.contextPath}/borrowing/renew"
method="post"
onsubmit="return confirm('Renew this loan?');">
onsubmit="return confirm('确定续借这条记录吗?');">
<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>
</c:if>
</div>
</c:when>
<c:otherwise>
<span class="muted-text">Complete</span>
<span class="muted-text">已完成</span>
</c:otherwise>
</c:choose>
</td>
+16 -16
View File
@@ -1,31 +1,31 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<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}">
<nav class="top-nav" aria-label="Primary">
<a href="${pageContext.request.contextPath}/dashboard">Dashboard</a>
<a href="${pageContext.request.contextPath}/catalog">Catalog</a>
<nav class="top-nav" aria-label="主导航">
<a href="${pageContext.request.contextPath}/dashboard">控制台</a>
<a href="${pageContext.request.contextPath}/catalog">馆藏检索</a>
<c:if test="${sessionScope.userRole == 'administrator'}">
<a href="${pageContext.request.contextPath}/admin/home">Admin</a>
<a href="${pageContext.request.contextPath}/admin/users">Users</a>
<a href="${pageContext.request.contextPath}/admin/system-logs">Logs</a>
<a href="${pageContext.request.contextPath}/admin/home">管理</a>
<a href="${pageContext.request.contextPath}/admin/users">用户</a>
<a href="${pageContext.request.contextPath}/admin/system-logs">日志</a>
</c:if>
<c:if test="${sessionScope.userRole == 'administrator' or sessionScope.userRole == 'librarian'}">
<a href="${pageContext.request.contextPath}/librarian/home">Librarian</a>
<a href="${pageContext.request.contextPath}/books">Books</a>
<a href="${pageContext.request.contextPath}/book-categories">Categories</a>
<a href="${pageContext.request.contextPath}/readers">Readers</a>
<a href="${pageContext.request.contextPath}/borrowing">Borrowing</a>
<a href="${pageContext.request.contextPath}/reports">Reports</a>
<a href="${pageContext.request.contextPath}/librarian/home">馆员</a>
<a href="${pageContext.request.contextPath}/books">图书</a>
<a href="${pageContext.request.contextPath}/book-categories">分类</a>
<a href="${pageContext.request.contextPath}/readers">读者</a>
<a href="${pageContext.request.contextPath}/borrowing">借阅</a>
<a href="${pageContext.request.contextPath}/reports">报表</a>
</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'}">
<a href="${pageContext.request.contextPath}/reader/loans">My Loans</a>
<a href="${pageContext.request.contextPath}/reader/loans">我的借阅</a>
</c:if>
<span class="user-pill">
<c:out value="${sessionScope.authenticatedUser.displayName}" />
</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>
</c:if>
</header>
+41 -41
View File
@@ -1,11 +1,11 @@
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">
</head>
<body>
@@ -15,86 +15,86 @@
<p class="eyebrow">
<c:out value="${sessionScope.authenticatedUser.role.displayName}" />
</p>
<h1 id="dashboard-title">Dashboard</h1>
<p>Signed in as <strong><c:out value="${sessionScope.authenticatedUser.displayName}" /></strong>.</p>
<h1 id="dashboard-title">控制台</h1>
<p>当前登录:<strong><c:out value="${sessionScope.authenticatedUser.displayName}" /></strong></p>
</section>
<section class="card-grid" aria-label="Role workspaces">
<section class="card-grid" aria-label="角色工作区">
<c:if test="${sessionScope.userRole == 'administrator'}">
<article class="workspace-card">
<h2>Administration</h2>
<p>Account, role, permission, and system-maintenance entry point.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/home">Open</a>
<h2>系统管理</h2>
<p>账户、角色、权限和系统维护入口。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/home">打开</a>
</article>
<article class="workspace-card">
<h2>User Management</h2>
<p>Create, update, deactivate, and review login accounts.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/users">Open</a>
<h2>用户管理</h2>
<p>创建、更新、停用和查看登录账户。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/users">打开</a>
</article>
<article class="workspace-card">
<h2>System Logs</h2>
<p>Review read-only audit entries for account and maintenance actions.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/system-logs">Open</a>
<h2>系统日志</h2>
<p>查看账户与维护操作的只读审计记录。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/system-logs">打开</a>
</article>
</c:if>
<c:if test="${sessionScope.userRole == 'administrator' or sessionScope.userRole == 'librarian'}">
<article class="workspace-card">
<h2>Librarian Workspace</h2>
<p>Book, reader, borrowing, return, renewal, and overdue entry point.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/librarian/home">Open</a>
<h2>馆员工作台</h2>
<p>图书、读者、借阅、归还、续借和逾期处理入口。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/librarian/home">打开</a>
</article>
<article class="workspace-card">
<h2>Book Management</h2>
<p>Create, update, delete, and review book inventory records.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/books">Open</a>
<h2>图书管理</h2>
<p>创建、更新、删除和查看图书库存记录。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/books">打开</a>
</article>
<article class="workspace-card">
<h2>Category Maintenance</h2>
<p>Maintain catalog categories used by book records and search filters.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">Open</a>
<h2>分类维护</h2>
<p>维护图书记录和检索筛选使用的馆藏分类。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">打开</a>
</article>
<article class="workspace-card">
<h2>Reader Management</h2>
<p>Create, update, deactivate, and review reader eligibility records.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/readers">Open</a>
<h2>读者管理</h2>
<p>创建、更新、停用和查看读者借阅资格记录。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/readers">打开</a>
</article>
<article class="workspace-card">
<h2>Borrowing Management</h2>
<p>Create loans, process returns, renew active records, and review overdue items.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/borrowing">Open</a>
<h2>借阅管理</h2>
<p>创建借阅、处理归还、续借有效记录并查看逾期项目。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/borrowing">打开</a>
</article>
<article class="workspace-card">
<h2>Report Center</h2>
<p>Review inventory health, borrowing counts, overdue records, and popular books.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/reports">Open</a>
<h2>报表中心</h2>
<p>查看库存状况、借阅统计、逾期记录和热门图书。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/reports">打开</a>
</article>
</c:if>
<article class="workspace-card">
<h2>Book Catalog</h2>
<p>Search books by title, author, category, or book identifier.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/catalog">Search</a>
<h2>馆藏检索</h2>
<p>按书名、作者、分类或图书编号检索图书。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/catalog">检索</a>
</article>
<article class="workspace-card">
<h2>Reader Center</h2>
<p>Reader self-service entry point for catalog access and loan history.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/reader/home">Open</a>
<h2>读者中心</h2>
<p>读者自助访问馆藏和借阅历史的入口。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/reader/home">打开</a>
</article>
<c:if test="${sessionScope.userRole == 'reader'}">
<article class="workspace-card">
<h2>My Loan History</h2>
<p>Review your active, returned, and overdue borrowing records.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/reader/loans">Open</a>
<h2>我的借阅历史</h2>
<p>查看您的在借、已还和逾期借阅记录。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/reader/loans">打开</a>
</article>
</c:if>
</section>
@@ -2,11 +2,11 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">
</head>
<body>
@@ -14,9 +14,9 @@
<main class="page-shell">
<section class="dashboard-hero catalog-hero" aria-labelledby="system-logs-title">
<div>
<p class="eyebrow">System Maintenance</p>
<h1 id="system-logs-title">System logs</h1>
<p>Review administrative account changes and maintenance audit records.</p>
<p class="eyebrow">系统维护</p>
<h1 id="system-logs-title">系统日志</h1>
<p>查看管理账户变更和维护审计记录。</p>
</div>
</section>
@@ -26,17 +26,22 @@
</div>
</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"
action="${pageContext.request.contextPath}/admin/system-logs" method="get">
<div class="search-field">
<label for="operationType">Operation</label>
<label for="operationType">操作</label>
<select id="operationType" name="operationType">
<option value="">All operations</option>
<option value="">全部操作</option>
<c:forEach var="operationType" items="${operationTypes}">
<option value="${fn:escapeXml(operationType)}"
<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>
</c:forEach>
<c:if test="${not empty criteria.operationType and empty operationTypes}">
@@ -51,7 +56,7 @@
</div>
<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)}">
<c:if test="${not empty errors.keyword}">
<span class="field-error"><c:out value="${errors.keyword}" /></span>
@@ -59,7 +64,7 @@
</div>
<div class="search-field">
<label for="createdFrom">From</label>
<label for="createdFrom">开始日期</label>
<input id="createdFrom" name="createdFrom" type="date"
value="${fn:escapeXml(criteria.createdFromText)}">
<c:if test="${not empty errors.createdFrom}">
@@ -68,7 +73,7 @@
</div>
<div class="search-field">
<label for="createdTo">To</label>
<label for="createdTo">结束日期</label>
<input id="createdTo" name="createdTo" type="date"
value="${fn:escapeXml(criteria.createdToText)}">
<c:if test="${not empty errors.createdTo}">
@@ -76,29 +81,29 @@
</c:if>
</div>
<button class="button button-primary" type="submit">Search</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/system-logs">Clear</a>
<button class="button button-primary" type="submit">检索</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/system-logs">清空</a>
</form>
</section>
<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:when test="${empty logs}">
<p class="empty-state">No system logs match the current filters.</p>
<p class="empty-state">没有符合当前筛选条件的系统日志。</p>
</c:when>
<c:otherwise>
<div class="table-scroll">
<table class="data-table system-log-table">
<thead>
<tr>
<th scope="col">Time</th>
<th scope="col">Operator</th>
<th scope="col">Operation</th>
<th scope="col">Target</th>
<th scope="col">Result</th>
<th scope="col">IP address</th>
<th scope="col">Detail</th>
<th scope="col">时间</th>
<th scope="col">操作人</th>
<th scope="col">操作</th>
<th scope="col">目标</th>
<th scope="col">结果</th>
<th scope="col">IP 地址</th>
<th scope="col">详情</th>
</tr>
</thead>
<tbody>
@@ -111,9 +116,16 @@
<div class="muted-text"><c:out value="${log.operatorMetaText}" /></div>
</c:if>
</td>
<td><c:out value="${log.operationType}" /></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:out value="${log.targetId}" />
</c:if>
+16 -16
View File
@@ -1,11 +1,11 @@
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">
</head>
<body>
@@ -13,11 +13,11 @@
<main class="page-shell">
<section class="dashboard-hero catalog-hero" aria-labelledby="loan-history-title">
<div>
<p class="eyebrow">Reader Center</p>
<h1 id="loan-history-title">Loan history</h1>
<p>Review your active, returned, and overdue borrowing records.</p>
<p class="eyebrow">读者中心</p>
<h1 id="loan-history-title">借阅历史</h1>
<p>查看您的在借、已还和逾期借阅记录。</p>
</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>
<c:if test="${not empty successMessage}">
@@ -32,23 +32,23 @@
</c:if>
<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: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:otherwise>
<div class="table-scroll">
<table class="data-table borrowing-table">
<thead>
<tr>
<th scope="col">Book ID</th>
<th scope="col">Title</th>
<th scope="col">Borrowed</th>
<th scope="col">Due</th>
<th scope="col">Returned</th>
<th scope="col">Renewals</th>
<th scope="col">Status</th>
<th scope="col">图书编号</th>
<th scope="col">书名</th>
<th scope="col">借出时间</th>
<th scope="col">应还时间</th>
<th scope="col">归还时间</th>
<th scope="col">续借次数</th>
<th scope="col">状态</th>
</tr>
</thead>
<tbody>
@@ -63,7 +63,7 @@
<c:when test="${not empty record.returnedAtText}">
<c:out value="${record.returnedAtText}" />
</c:when>
<c:otherwise>Not returned</c:otherwise>
<c:otherwise>未归还</c:otherwise>
</c:choose>
</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="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">
</head>
<body>
<%@ include file="/WEB-INF/jsp/common/header.jspf" %>
<main class="page-shell">
<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>
<c:if test="${not empty errorMessage}">
@@ -38,7 +38,7 @@
<div class="form-grid">
<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>
<c:if test="${not empty errors.identifier}">
<span class="field-error"><c:out value="${errors.identifier}" /></span>
@@ -46,7 +46,7 @@
</div>
<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>
<c:if test="${not empty errors.fullName}">
<span class="field-error"><c:out value="${errors.fullName}" /></span>
@@ -54,7 +54,7 @@
</div>
<div class="form-field">
<label for="phone">Phone</label>
<label for="phone">电话</label>
<input id="phone" name="phone" type="tel" value="${fn:escapeXml(phoneValue)}">
<c:if test="${not empty errors.phone}">
<span class="field-error"><c:out value="${errors.phone}" /></span>
@@ -62,7 +62,7 @@
</div>
<div class="form-field">
<label for="email">Email</label>
<label for="email">邮箱</label>
<input id="email" name="email" type="email" value="${fn:escapeXml(emailValue)}">
<c:if test="${not empty errors.email}">
<span class="field-error"><c:out value="${errors.email}" /></span>
@@ -70,7 +70,7 @@
</div>
<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)}">
<c:if test="${not empty errors.userId}">
<span class="field-error"><c:out value="${errors.userId}" /></span>
@@ -78,7 +78,7 @@
</div>
<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"
value="${fn:escapeXml(maxBorrowCountValue)}" required>
<c:if test="${not empty errors.maxBorrowCount}">
@@ -87,9 +87,9 @@
</div>
<div class="form-field">
<label for="status">Status</label>
<label for="status">状态</label>
<select id="status" name="status" required>
<option value="">Select status</option>
<option value="">请选择状态</option>
<c:forEach var="status" items="${statuses}">
<option value="${status.code}" <c:if test="${statusValue == status.code}">selected</c:if>>
<c:out value="${status.displayName}" />
@@ -103,8 +103,8 @@
</div>
<div class="form-actions">
<button class="button button-primary" type="submit">Save</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/readers">Cancel</a>
<button class="button button-primary" type="submit">保存</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/readers">取消</a>
</div>
</form>
</section>
+27 -27
View File
@@ -2,21 +2,21 @@
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">
</head>
<body>
<%@ include file="/WEB-INF/jsp/common/header.jspf" %>
<main class="page-shell">
<section class="dashboard-hero catalog-hero" aria-labelledby="manage-readers-title">
<p class="eyebrow">Reader Management</p>
<h1 id="manage-readers-title">Manage readers</h1>
<p>Create, update, and review reader eligibility and contact records.</p>
<a class="button button-primary" href="${pageContext.request.contextPath}/readers/new">New reader</a>
<p class="eyebrow">读者管理</p>
<h1 id="manage-readers-title">管理读者</h1>
<p>创建、更新和查看读者资格及联系方式记录。</p>
<a class="button button-primary" href="${pageContext.request.contextPath}/readers/new">新增读者</a>
</section>
<c:if test="${not empty successMessage}">
@@ -30,27 +30,27 @@
</div>
</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">
<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)}">
</div>
<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)}">
</div>
<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)}">
</div>
<div class="search-field">
<label for="status">Status</label>
<label for="status">状态</label>
<select id="status" name="status">
<option value="">All statuses</option>
<option value="">全部状态</option>
<c:forEach var="status" items="${statuses}">
<option value="${status.code}" <c:if test="${criteria.statusCode == status.code}">selected</c:if>>
<c:out value="${status.displayName}" />
@@ -62,29 +62,29 @@
</c:if>
</div>
<button class="button button-primary" type="submit">Search</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/readers">Clear</a>
<button class="button button-primary" type="submit">检索</button>
<a class="button button-secondary" href="${pageContext.request.contextPath}/readers">清空</a>
</form>
</section>
<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:when test="${empty readers}">
<p class="empty-state">No reader records match the current filters.</p>
<p class="empty-state">没有符合当前筛选条件的读者记录。</p>
</c:when>
<c:otherwise>
<div class="table-scroll">
<table class="data-table">
<thead>
<tr>
<th scope="col">Reader ID</th>
<th scope="col">Name</th>
<th scope="col">Contact</th>
<th scope="col">Account</th>
<th scope="col">Borrow limit</th>
<th scope="col">Status</th>
<th scope="col">Actions</th>
<th scope="col">读者编号</th>
<th scope="col">姓名</th>
<th scope="col">联系方式</th>
<th scope="col">关联账户</th>
<th scope="col">借阅上限</th>
<th scope="col">状态</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody>
@@ -105,7 +105,7 @@
<c:when test="${not empty reader.username}">
<c:out value="${reader.username}" />
</c:when>
<c:otherwise>Unlinked</c:otherwise>
<c:otherwise>未关联</c:otherwise>
</c:choose>
</td>
<td><c:out value="${reader.maxBorrowCount}" /></td>
@@ -117,12 +117,12 @@
<td>
<div class="table-actions">
<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"
method="post"
onsubmit="return confirm('Deactivate this reader profile?');">
onsubmit="return confirm('确定停用这个读者档案吗?');">
<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>
</div>
</td>
@@ -1,11 +1,11 @@
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">
</head>
<body>
@@ -13,11 +13,11 @@
<main class="page-shell">
<section class="dashboard-hero catalog-hero" aria-labelledby="reports-title">
<div>
<p class="eyebrow">Reports</p>
<h1 id="reports-title">Report center</h1>
<p>Review collection inventory, borrowing health, overdue loans, and popular books.</p>
<p class="eyebrow">报表</p>
<h1 id="reports-title">报表中心</h1>
<p>查看馆藏库存、借阅状况、逾期借阅和热门图书。</p>
</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>
<c:if test="${not empty errorMessage}">
@@ -27,59 +27,59 @@
</c:if>
<c:if test="${not empty reportCenter}">
<section class="report-grid" aria-label="Report summary">
<section class="report-grid" aria-label="报表摘要">
<article class="report-card">
<p class="eyebrow">Inventory</p>
<h2>Total titles</h2>
<p class="eyebrow">库存</p>
<h2>图书种类总数</h2>
<p class="report-metric"><c:out value="${reportCenter.inventorySummary.totalTitles}" /></p>
</article>
<article class="report-card">
<p class="eyebrow">Inventory</p>
<h2>Total copies</h2>
<p class="eyebrow">库存</p>
<h2>馆藏总册数</h2>
<p class="report-metric"><c:out value="${reportCenter.inventorySummary.totalCopies}" /></p>
</article>
<article class="report-card">
<p class="eyebrow">Inventory</p>
<h2>Available copies</h2>
<p class="eyebrow">库存</p>
<h2>可借册数</h2>
<p class="report-metric"><c:out value="${reportCenter.inventorySummary.availableCopies}" /></p>
</article>
<article class="report-card">
<p class="eyebrow">Attention</p>
<h2>Unavailable or empty</h2>
<p class="eyebrow">需关注</p>
<h2>不可借或无库存</h2>
<p class="report-metric"><c:out value="${reportCenter.inventorySummary.unavailableOrEmptyTitles}" /></p>
</article>
<article class="report-card">
<p class="eyebrow">Borrowing</p>
<h2>Currently borrowed</h2>
<p class="eyebrow">借阅</p>
<h2>当前借出</h2>
<p class="report-metric"><c:out value="${reportCenter.borrowingSummary.activeLoans}" /></p>
</article>
<article class="report-card">
<p class="eyebrow">Borrowing</p>
<h2>Returned records</h2>
<p class="eyebrow">借阅</p>
<h2>已归还记录</h2>
<p class="report-metric"><c:out value="${reportCenter.borrowingSummary.returnedLoans}" /></p>
</article>
<article class="report-card report-card-alert">
<p class="eyebrow">Borrowing</p>
<h2>Overdue loans</h2>
<p class="eyebrow">借阅</p>
<h2>逾期借阅</h2>
<p class="report-metric"><c:out value="${reportCenter.borrowingSummary.overdueLoans}" /></p>
</article>
</section>
<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:when test="${empty reportCenter.overdueRows}">
<p class="empty-state">No active overdue borrowing records.</p>
<p class="empty-state">当前没有逾期未还的借阅记录。</p>
</c:when>
<c:otherwise>
<div class="table-scroll">
<table class="data-table">
<thead>
<tr>
<th scope="col">Reader</th>
<th scope="col">Book</th>
<th scope="col">Due date</th>
<th scope="col">Overdue days</th>
<th scope="col">读者</th>
<th scope="col">图书</th>
<th scope="col">应还日期</th>
<th scope="col">逾期天数</th>
</tr>
</thead>
<tbody>
@@ -96,7 +96,7 @@
<td><c:out value="${row.dueAtText}" /></td>
<td>
<span class="status-pill status-overdue">
<c:out value="${row.overdueDays}" /> days
<c:out value="${row.overdueDays}" />
</span>
</td>
</tr>
@@ -109,19 +109,19 @@
</section>
<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: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:otherwise>
<div class="table-scroll">
<table class="data-table">
<thead>
<tr>
<th scope="col">Book</th>
<th scope="col">Author</th>
<th scope="col">Borrow records</th>
<th scope="col">图书</th>
<th scope="col">作者</th>
<th scope="col">借阅次数</th>
</tr>
</thead>
<tbody>
+31 -31
View File
@@ -1,11 +1,11 @@
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<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">
</head>
<body>
@@ -17,67 +17,67 @@
</p>
<h1 id="area-title"><c:out value="${areaName}" /></h1>
<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 class="card-grid role-actions" aria-label="Workspace actions">
<section class="card-grid role-actions" aria-label="工作区操作">
<article class="workspace-card">
<h2>Book Catalog</h2>
<p>Search available collection records by title, author, category, or book identifier.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/catalog">Search catalog</a>
<h2>馆藏检索</h2>
<p>按书名、作者、分类或图书编号检索可用馆藏记录。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/catalog">检索馆藏</a>
</article>
<c:if test="${sessionScope.userRole == 'administrator' or sessionScope.userRole == 'librarian'}">
<c:if test="${sessionScope.userRole == 'administrator'}">
<article class="workspace-card">
<h2>User Management</h2>
<p>Create, update, deactivate, and review login accounts.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/users">Manage users</a>
<h2>用户管理</h2>
<p>创建、更新、停用和查看登录账户。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/users">管理用户</a>
</article>
<article class="workspace-card">
<h2>System Logs</h2>
<p>Review read-only audit entries for account and maintenance actions.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/system-logs">View logs</a>
<h2>系统日志</h2>
<p>查看账户与维护操作的只读审计记录。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/admin/system-logs">查看日志</a>
</article>
</c:if>
<article class="workspace-card">
<h2>Book Management</h2>
<p>Create, update, delete, and review inventory fields for book records.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/books">Manage books</a>
<h2>图书管理</h2>
<p>创建、更新、删除和查看图书记录的库存字段。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/books">管理图书</a>
</article>
<article class="workspace-card">
<h2>Category Maintenance</h2>
<p>Create, update, and retire catalog categories used by book records.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">Manage categories</a>
<h2>分类维护</h2>
<p>创建、更新和停用图书记录使用的馆藏分类。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/book-categories">管理分类</a>
</article>
<article class="workspace-card">
<h2>Reader Management</h2>
<p>Create, update, deactivate, and review eligibility fields for reader records.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/readers">Manage readers</a>
<h2>读者管理</h2>
<p>创建、更新、停用和查看读者记录的资格字段。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/readers">管理读者</a>
</article>
<article class="workspace-card">
<h2>Borrowing Management</h2>
<p>Create loans, process returns, renew records, and review overdue items.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/borrowing">Manage borrowing</a>
<h2>借阅管理</h2>
<p>创建借阅、处理归还、续借记录并查看逾期项目。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/borrowing">管理借阅</a>
</article>
<article class="workspace-card">
<h2>Report Center</h2>
<p>Review inventory summaries, borrowing health, overdue lists, and popular books.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/reports">View reports</a>
<h2>报表中心</h2>
<p>查看库存摘要、借阅状况、逾期列表和热门图书。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/reports">查看报表</a>
</article>
</c:if>
<c:if test="${sessionScope.userRole == 'reader'}">
<article class="workspace-card">
<h2>My Loan History</h2>
<p>Review active loans, returned records, renewal counts, and overdue status.</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/reader/loans">View history</a>
<h2>我的借阅历史</h2>
<p>查看在借记录、已还记录、续借次数和逾期状态。</p>
<a class="button button-secondary" href="${pageContext.request.contextPath}/reader/loans">查看历史</a>
</article>
</c:if>
</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"
version="4.0">
<display-name>MZH Library Management</display-name>
<display-name>MZH 图书馆管理系统</display-name>
<filter>
<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 x="0" y="705" width="1440" height="255" fill="#d4ddd8"/>
<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;
public final class AuthServiceCheck {
private static final String REQUIRED_MESSAGE = "Username and password are required.";
private static final String INVALID_MESSAGE = "Invalid username or password.";
private static final String REQUIRED_MESSAGE = "请输入用户名和密码。";
private static final String INVALID_MESSAGE = "用户名或密码不正确。";
private static final String UNAVAILABLE_MESSAGE =
"Login service is temporarily unavailable. Please try again later.";
"登录服务暂时不可用,请稍后重试。";
private AuthServiceCheck() {
}
@@ -22,7 +22,7 @@ import java.util.logging.Logger;
public final class BookServiceCheck {
private static final String UNAVAILABLE_MESSAGE =
"Book service is temporarily unavailable. Please try again later.";
"图书服务暂时不可用,请稍后重试。";
private BookServiceCheck() {
}
@@ -44,13 +44,13 @@ public final class BookServiceCheck {
ServiceResult<Long> denied = service.createBook(reader,
book(0L, "BK-1001", "Reader Write", "Test Author", 1L, 1, 1, BookStatus.AVAILABLE));
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");
ServiceResult<Long> deniedCategory = service.createCategory(reader,
category(0L, "Reader Category", "Denied category"));
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");
ServiceResult<Long> created = service.createBook(librarian,
@@ -70,7 +70,7 @@ public final class BookServiceCheck {
ServiceResult<Void> deleteUsedCategory = service.deleteCategory(librarian, 1L);
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");
require(deleteUsedCategory.getErrors().containsKey("category"),
"used category delete should return a category-level field error");
@@ -30,7 +30,7 @@ import java.util.logging.Logger;
public final class BorrowingServiceCheck {
private static final String UNAVAILABLE_MESSAGE =
"Borrowing service is temporarily unavailable. Please try again later.";
"借阅服务暂时不可用,请稍后重试。";
private static final Clock FIXED_CLOCK = Clock.fixed(
Instant.parse("2026-04-27T00:00:00Z"),
ZoneId.of("UTC")
@@ -62,7 +62,7 @@ public final class BorrowingServiceCheck {
ServiceResult<Long> denied = service.borrowBook(readerUser, "RD-1000", "BK-1000");
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");
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);
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");
ServiceResult<Void> returned = service.returnBook(librarian, borrowedId);
@@ -21,7 +21,7 @@ import java.util.logging.Logger;
public final class ReaderServiceCheck {
private static final String UNAVAILABLE_MESSAGE =
"Reader service is temporarily unavailable. Please try again later.";
"读者服务暂时不可用,请稍后重试。";
private ReaderServiceCheck() {
}
@@ -64,7 +64,7 @@ public final class ReaderServiceCheck {
ServiceResult<Long> denied = service.createReader(readerUser,
reader(0L, "RD-1006", null, "Reader Write", "13800000007", "", ReaderStatus.ACTIVE, 5));
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");
ServiceResult<Long> created = service.createReader(librarian,
@@ -21,9 +21,9 @@ import java.util.logging.Level;
import java.util.logging.Logger;
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 =
"Report service is temporarily unavailable. Please try again later.";
"报表服务暂时不可用,请稍后重试。";
private ReportServiceCheck() {
}
@@ -20,9 +20,9 @@ import java.util.logging.Level;
import java.util.logging.Logger;
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 =
"System log service is temporarily unavailable. Please try again later.";
"系统日志服务暂时不可用,请稍后重试。";
private SystemLogServiceCheck() {
}
@@ -59,10 +59,10 @@ public final class SystemLogServiceCheck {
SystemLog orphanedOperator = log(99L, "user.update", "Updated orphaned operator account");
orphanedOperator.setOperatorUsername("");
orphanedOperator.setOperatorDisplayName("");
require("User #1".equals(orphanedOperator.getOperatorLabel()),
require("用户 #1".equals(orphanedOperator.getOperatorLabel()),
"operator id should still render when joined user names are unavailable");
require("administrator".equals(orphanedOperator.getOperatorMetaText()),
"operator meta should preserve role when names are unavailable");
require("管理员".equals(orphanedOperator.getOperatorMetaText()),
"operator meta should display role when names are unavailable");
SystemLog unsafeStatus = log(100L, "user.update", "Unsafe status check");
unsafeStatus.setResultStatus("success\" onclick=\"x");
@@ -26,9 +26,9 @@ import java.util.logging.Logger;
import java.util.stream.Collectors;
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 =
"User management service is temporarily unavailable. Please try again later.";
"用户管理服务暂时不可用,请稍后重试。";
private UserAccountServiceCheck() {
}