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
@@ -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