This commit is contained in:
Zzzz
2026-04-28 22:08:36 +08:00
parent d1f32b9d52
commit a37d37945b
7 changed files with 118 additions and 130 deletions
+10 -11
View File
@@ -36,9 +36,8 @@ rendering.
- Request fields consumed by `LoginServlet`: `username`, `password`, and - Request fields consumed by `LoginServlet`: `username`, `password`, and
optional `redirect`. optional `redirect`.
- Presentation-only login controls may submit auxiliary fields such as - Presentation-only login controls may submit auxiliary fields such as
`loginRole` and `rememberUsername`; these must not participate in `rememberUsername`; these must not participate in authentication or
authentication or authorization unless the Servlet/service contract is authorization unless the Servlet/service contract is deliberately changed.
deliberately changed.
- Login JSP request attributes: `errorMessage`, `username`, and `redirect`. - Login JSP request attributes: `errorMessage`, `username`, and `redirect`.
- Dashboard/role JSP session attributes: `authenticatedUser`, `userRole`, and - Dashboard/role JSP session attributes: `authenticatedUser`, `userRole`, and
`userPermissions`. `userPermissions`.
@@ -52,9 +51,9 @@ rendering.
attribute or session attribute. attribute or session attribute.
- `redirect` must be a same-application path beginning with one `/`; invalid - `redirect` must be a same-application path beginning with one `/`; invalid
values are ignored. values are ignored.
- `loginRole` is only a login-intent hint in the JSP. The authenticated role is - Login pages must not include a client-side role selector. The authenticated
determined by the `users.role_code` row returned through `AuthService`, not by role is determined by the `users.role_code` row returned through
a client-side radio selection. `AuthService`, not by client-submitted form state.
- Remember-me behavior may persist only the username in browser storage. It must - Remember-me behavior may persist only the username in browser storage. It must
never persist passwords, password hashes, redirects, permission state, or never persist passwords, password hashes, redirects, permission state, or
extend the server session. extend the server session.
@@ -78,12 +77,12 @@ rendering.
- Good: failed login keeps the escaped username and never redisplays the - Good: failed login keeps the escaped username and never redisplays the
password. password.
- Good: selecting a role radio option or checking remember-me does not change - Good: checking remember-me does not change the server-side authentication
the server-side authentication decision. decision.
- Base: dashboard reads `sessionScope.authenticatedUser.displayName` and - Base: dashboard reads `sessionScope.authenticatedUser.displayName` and
`sessionScope.userRole` only for display/navigation. `sessionScope.userRole` only for display/navigation.
- Bad: JSP, JavaScript, or Servlet code trusts `loginRole` to grant a role or - Bad: JSP, JavaScript, or Servlet code trusts a client-submitted role field to
stores the password in browser storage. grant a role or stores the password in browser storage.
### 6. Tests Required ### 6. Tests Required
@@ -102,7 +101,7 @@ rendering.
```jsp ```jsp
<%-- JSP checks request.getParameter("password") or runs SQL directly. --%> <%-- JSP checks request.getParameter("password") or runs SQL directly. --%>
<%-- JavaScript stores the password or LoginServlet trusts loginRole. --%> <%-- JavaScript stores the password or LoginServlet trusts a submitted role. --%>
``` ```
#### Correct #### Correct
@@ -0,0 +1,4 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
{"file": ".trellis/spec/frontend/index.md", "reason": "Frontend checklist for reviewing login page UI changes"}
{"file": ".trellis/spec/frontend/type-safety.md", "reason": "Verify login form contract remains unchanged"}
{"file": ".trellis/spec/frontend/quality-guidelines.md", "reason": "Verify UI layout quality after removal"}
@@ -0,0 +1,7 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
{"file": ".trellis/spec/frontend/index.md", "reason": "Frontend JSP/CSS guidelines for login page UI changes"}
{"file": ".trellis/spec/frontend/directory-structure.md", "reason": "JSP and static asset placement conventions"}
{"file": ".trellis/spec/frontend/component-guidelines.md", "reason": "Form and page component conventions"}
{"file": ".trellis/spec/frontend/state-management.md", "reason": "Server-rendered form state conventions"}
{"file": ".trellis/spec/frontend/type-safety.md", "reason": "Login form request contract and loginRole behavior"}
{"file": ".trellis/spec/frontend/quality-guidelines.md", "reason": "UI quality checks for JSP/CSS changes"}
@@ -0,0 +1,52 @@
# 调整登录页登录选项与布局
## Goal
简化登录界面:移除登录身份单选项和标题旁的图书图标,并微调表单布局,让登录卡片在元素减少后仍保持居中、紧凑和视觉平衡。
## What I Already Know
* 用户要求删除登录界面中的“登录身份”选项。
* 用户要求删除登录界面中的图书图标。
* 登录页 JSP 位于 `src/main/webapp/WEB-INF/jsp/auth/login.jsp`
* 登录页样式位于 `src/main/webapp/static/css/app.css`
* 登录页脚本位于 `src/main/webapp/static/js/login.js`,当前主要处理记住用户名、密码显示切换和忘记密码提示。
* 前端规范说明登录页不应包含客户端角色选择,认证后的角色由 `AuthService` 返回的用户角色决定。
## Assumptions
* “图书的图标”指登录页标题旁内联 SVG 的 `login-brand-mark`,不是背景插画 `static/images/library-login.svg`
* “微调布局”指因移除图标和登录身份单选后,调整标题区域、表单间距和卡片留白,不做整页视觉重设计。
## Requirements
* 移除登录页的登录身份单选区域,包括“登录身份”“管理员”“馆员”“读者”选项。
* 移除登录页标题旁的图书图标。
* 保留用户名、密码、记住我、忘记密码提示和登录提交功能。
* 表单提交仍只依赖后端已消费的 `username``password`、可选 `redirect`,不改变认证/授权逻辑。
* 调整登录页布局,使标题、副标题、输入框、选项行和按钮在桌面与移动端都保持合理间距。
## Acceptance Criteria
* [x] 登录页不再渲染“登录身份”文案和角色单选按钮。
* [x] 登录页标题旁不再渲染图书 SVG 图标。
* [x] 登录页在桌面和移动端没有明显空洞、错位或文本重叠。
* [x] 用户名/密码登录表单仍可提交到 `POST /login`
* [x] 项目可通过 Maven 构建或等价检查。
## Definition of Done
* JSP/CSS 改动范围聚焦在登录页 UI。
* Lint/typecheck/build 可用检查已运行;如无法运行,记录原因。
* 不修改后端认证授权逻辑。
## Out of Scope
* 不重做整套登录页视觉风格。
* 不修改用户角色、权限、认证服务或数据库。
* 不删除登录页背景插画,除非代码检查证明它就是用户所指图标。
## Technical Notes
* 前端规范入口: `.trellis/spec/frontend/index.md`
* 相关规范: `.trellis/spec/frontend/type-safety.md` 中说明 `LoginServlet` 消费 `username``password` 和可选 `redirect`,登录角色不由客户端表单状态决定。
@@ -0,0 +1,26 @@
{
"id": "login-page-simplify-layout",
"name": "login-page-simplify-layout",
"title": "调整登录页登录选项与布局",
"description": "",
"status": "in_progress",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "Zzzz",
"assignee": "Zzzz",
"createdAt": "2026-04-28",
"completedAt": null,
"branch": null,
"base_branch": "master",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}
+1 -28
View File
@@ -13,16 +13,7 @@
<main class="auth-shell"> <main class="auth-shell">
<section class="login-panel" aria-labelledby="login-title"> <section class="login-panel" aria-labelledby="login-title">
<div class="login-card-head"> <div class="login-card-head">
<div class="login-brand-row"> <h1 id="login-title">图书管理系统</h1>
<span class="login-brand-mark" aria-hidden="true">
<svg viewBox="0 0 48 48" focusable="false">
<path d="M8 11.5c0-2 1.6-3.5 3.5-3.5H22c1.5 0 2.8.6 3.8 1.6V39c-1-.8-2.3-1.2-3.8-1.2H11.5A3.5 3.5 0 0 1 8 34.3V11.5Z" fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round"/>
<path d="M40 11.5c0-2-1.6-3.5-3.5-3.5H26c-1.5 0-2.8.6-3.8 1.6V39c1-.8 2.3-1.2 3.8-1.2h10.5a3.5 3.5 0 0 0 3.5-3.5V11.5Z" fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round"/>
<path d="M14 15.5h7M14 22h7M27 15.5h7M27 22h7" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
</svg>
</span>
<h1 id="login-title">图书管理系统</h1>
</div>
<p class="login-subtitle">欢迎登录图书管理平台</p> <p class="login-subtitle">欢迎登录图书管理平台</p>
</div> </div>
@@ -84,24 +75,6 @@
</div> </div>
</div> </div>
<div class="login-role-group" role="radiogroup" aria-labelledby="login-role-label">
<span class="login-role-title" id="login-role-label">登录身份</span>
<div class="login-role-options">
<label>
<input type="radio" name="loginRole" value="administrator" checked>
<span>管理员</span>
</label>
<label>
<input type="radio" name="loginRole" value="librarian">
<span>馆员</span>
</label>
<label>
<input type="radio" name="loginRole" value="reader">
<span>读者</span>
</label>
</div>
</div>
<div class="login-options-row"> <div class="login-options-row">
<label class="login-check"> <label class="login-check">
<input type="checkbox" name="rememberUsername" value="true" data-remember-username> <input type="checkbox" name="rememberUsername" value="true" data-remember-username>
+18 -91
View File
@@ -381,8 +381,8 @@ body:not(.auth-page) .dashboard-shell {
} }
.login-panel { .login-panel {
width: min(540px, 100%); width: min(500px, 100%);
padding: 44px 64px 56px; padding: 42px 56px 50px;
} }
.auth-page .login-panel { .auth-page .login-panel {
@@ -394,46 +394,24 @@ body:not(.auth-page) .dashboard-shell {
.login-card-head { .login-card-head {
display: grid; display: grid;
gap: 12px; gap: 8px;
justify-items: center; justify-items: center;
margin-bottom: 34px; margin-bottom: 30px;
text-align: center; text-align: center;
} }
.login-brand-row {
display: flex;
align-items: center;
justify-content: center;
gap: 22px;
}
.login-brand-mark {
width: 58px;
height: 58px;
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
color: #1372e8;
}
.login-brand-mark svg {
width: 100%;
height: 100%;
}
.login-card-head h1 { .login-card-head h1 {
margin: 0; margin: 0;
color: #0f2546; color: #0f2546;
font-size: 34px; font-size: 32px;
font-weight: 900; font-weight: 900;
line-height: 1.14; line-height: 1.16;
} }
.login-subtitle { .login-subtitle {
margin: 0; margin: 0;
color: #6f7b8a; color: #6f7b8a;
font-size: 18px; font-size: 17px;
line-height: 1.4; line-height: 1.4;
} }
@@ -469,7 +447,7 @@ h2 {
.login-form { .login-form {
display: grid; display: grid;
gap: 20px; gap: 18px;
} }
.login-form label, .login-form label,
@@ -605,34 +583,6 @@ h2 {
outline: 0; outline: 0;
} }
.login-role-group {
min-width: 0;
display: flex;
align-items: center;
gap: 26px;
margin: 4px 0 0;
padding: 0;
border: 0;
}
.login-role-title {
flex: 0 0 auto;
padding: 0;
color: #1f2937;
font-size: 16px;
font-weight: 800;
}
.login-role-options {
min-width: 0;
display: flex;
align-items: center;
justify-content: space-between;
gap: 26px;
flex: 1 1 auto;
}
.login-role-options label,
.login-check { .login-check {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -644,7 +594,6 @@ h2 {
cursor: pointer; cursor: pointer;
} }
.login-role-options input,
.login-check input { .login-check input {
width: 18px; width: 18px;
height: 18px; height: 18px;
@@ -658,7 +607,8 @@ h2 {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 16px; gap: 16px;
margin-top: 2px; margin-top: 0;
padding: 0 2px;
} }
.forgot-password-link { .forgot-password-link {
@@ -678,7 +628,7 @@ h2 {
} }
.login-help-message { .login-help-message {
margin: -10px 0 0; margin: -8px 0 0;
padding: 9px 12px; padding: 9px 12px;
border: 1px solid rgba(20, 120, 200, 0.16); border: 1px solid rgba(20, 120, 200, 0.16);
border-radius: 8px; border-radius: 8px;
@@ -737,16 +687,12 @@ h2 {
opacity: 0.58; opacity: 0.58;
} }
.login-form .button-primary {
margin-top: 12px;
}
.login-form .login-submit { .login-form .login-submit {
width: 100%; width: 100%;
min-height: 58px; min-height: 56px;
margin-top: 4px; margin-top: 2px;
border-radius: 8px; border-radius: 8px;
font-size: 21px; font-size: 20px;
box-shadow: 0 10px 22px rgba(20, 104, 234, 0.26); box-shadow: 0 10px 22px rgba(20, 104, 234, 0.26);
} }
@@ -1381,20 +1327,11 @@ h2 {
.login-card-head { .login-card-head {
gap: 8px; gap: 8px;
margin-bottom: 26px; margin-bottom: 24px;
}
.login-brand-row {
gap: 12px;
}
.login-brand-mark {
width: 42px;
height: 42px;
} }
.login-card-head h1 { .login-card-head h1 {
font-size: 26px; font-size: 25px;
} }
.login-subtitle { .login-subtitle {
@@ -1416,20 +1353,10 @@ h2 {
min-height: 50px; min-height: 50px;
} }
.login-role-group { .login-form {
align-items: flex-start; gap: 16px;
flex-direction: column;
gap: 12px;
} }
.login-role-options {
width: 100%;
justify-content: space-between;
gap: 10px;
flex-wrap: wrap;
}
.login-role-options label,
.login-check, .login-check,
.forgot-password-link { .forgot-password-link {
font-size: 14px; font-size: 14px;