前端
This commit is contained in:
@@ -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": {}
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user