diff --git a/.trellis/spec/frontend/component-guidelines.md b/.trellis/spec/frontend/component-guidelines.md index 579dd1a..77b5caf 100644 --- a/.trellis/spec/frontend/component-guidelines.md +++ b/.trellis/spec/frontend/component-guidelines.md @@ -16,8 +16,14 @@ the reusable UI units. - Use shared fragments for repeated layout pieces such as header, navigation, sidebar, footer, pagination, and message banners. -- Prefer `.jspf` includes or JSP tag files once the project chooses one - pattern; document the actual paths after implementation. +- Use `.jspf` includes for the current JSP presentation layer. The authenticated + application frame lives in `src/main/webapp/WEB-INF/jsp/common/header.jspf` + and owns the dark sidebar, top utility bar, role workbench links, module + navigation, global search, user display, and logout link. +- Preserve role-conditioned navigation in that shared frame: administrator-only + links stay inside `sessionScope.userRole == 'administrator'`; staff links stay + inside `administrator or librarian`; reader-only links stay inside + `sessionScope.userRole == 'reader'`. - Keep fragments presentation-focused. They should not open database connections or call DAOs. diff --git a/.trellis/tasks/04-28-frontend-reference-redesign/check.jsonl b/.trellis/tasks/04-28-frontend-reference-redesign/check.jsonl new file mode 100644 index 0000000..caad0b2 --- /dev/null +++ b/.trellis/tasks/04-28-frontend-reference-redesign/check.jsonl @@ -0,0 +1,6 @@ +{"file": ".trellis/spec/frontend/index.md", "reason": "Frontend JSP/CSS stack and checklist for review."} +{"file": ".trellis/spec/frontend/component-guidelines.md", "reason": "Review shared JSP fragments, forms, tables, navigation, and Chinese copy."} +{"file": ".trellis/spec/frontend/quality-guidelines.md", "reason": "Review visual fidelity to image-first workflow and forbidden patterns."} +{"file": ".trellis/spec/frontend/state-management.md", "reason": "Verify server-rendered state and existing data flow remain intact."} +{"file": ".trellis/spec/frontend/type-safety.md", "reason": "Verify display contracts and validation handling remain safe."} +{"file": ".trellis/tasks/04-28-frontend-reference-redesign/research/reference-dashboard-visual-notes.md", "reason": "Compare implementation against extracted visual requirements."} diff --git a/.trellis/tasks/04-28-frontend-reference-redesign/implement.jsonl b/.trellis/tasks/04-28-frontend-reference-redesign/implement.jsonl new file mode 100644 index 0000000..8634d1f --- /dev/null +++ b/.trellis/tasks/04-28-frontend-reference-redesign/implement.jsonl @@ -0,0 +1,8 @@ +{"file": ".trellis/spec/frontend/index.md", "reason": "Frontend JSP/CSS stack and pre-development checklist for this redesign."} +{"file": ".trellis/spec/frontend/directory-structure.md", "reason": "JSP and static asset organization constraints."} +{"file": ".trellis/spec/frontend/component-guidelines.md", "reason": "Shared JSP fragment, form, table, navigation, and Chinese copy conventions."} +{"file": ".trellis/spec/frontend/quality-guidelines.md", "reason": "Image-to-JSP restoration quality bar and forbidden patterns."} +{"file": ".trellis/spec/frontend/hook-guidelines.md", "reason": "Confirms no React/Vue hook conventions should be introduced."} +{"file": ".trellis/spec/frontend/state-management.md", "reason": "Server-rendered request/session/form state conventions to preserve."} +{"file": ".trellis/spec/frontend/type-safety.md", "reason": "JSP/Servlet validation and JavaBean display contract constraints."} +{"file": ".trellis/tasks/04-28-frontend-reference-redesign/research/reference-dashboard-visual-notes.md", "reason": "Visual requirements extracted from the provided reference image."} diff --git a/.trellis/tasks/04-28-frontend-reference-redesign/prd.md b/.trellis/tasks/04-28-frontend-reference-redesign/prd.md new file mode 100644 index 0000000..0786cad --- /dev/null +++ b/.trellis/tasks/04-28-frontend-reference-redesign/prd.md @@ -0,0 +1,71 @@ +# brainstorm: Redesign Frontend From Reference Image + +## Goal + +Refactor the JSP/CSS frontend so the library-management application visually matches the provided dashboard reference as closely as practical while preserving existing Servlet/JSP behavior, routes, role-based navigation, forms, tables, and Simplified Chinese interface copy. + +## What I Already Know + +* The user wants the frontend rebuilt to imitate the attached reference image as closely as possible. +* The reference image is a Chinese library management dashboard with a dark left sidebar, white top bar, dense white card panels, blue primary actions, rounded statistics cards, table sections, and operational shortcut tiles. +* The application is a JSP + Servlet + Maven WAR project, not a React/Vue SPA. +* Existing frontend files live under `src/main/webapp/WEB-INF/jsp/` with shared CSS in `src/main/webapp/static/css/app.css`. +* Current shared header fragment is `src/main/webapp/WEB-INF/jsp/common/header.jspf`. +* Existing pages include dashboard, login, catalog, book management, reader management, borrowing, reports, system logs, and user management JSPs. +* Frontend spec requires image-first implementation and Simplified Chinese display copy. + +## Assumptions + +* "Frontend" means the shared visual system across JSP pages, with the dashboard receiving the closest match because the reference image is a dashboard screenshot. +* The redesign should keep current endpoints, request parameter names, JSTL conditions, and server-rendered data contracts unchanged. +* New CSS classes and JSP structure are allowed when they improve visual fidelity, but no new frontend framework should be introduced. +* The reference image should be stored with the task for implementation/check agents. + +## Open Questions + +* Confirm scope: apply the reference style across the whole JSP frontend, not only the dashboard page. + +## Requirements + +* Build a left dark sidebar similar to the reference, including brand/title, role workbench buttons, module navigation, and compact footer/menu area. +* Build a top utility bar similar to the reference, including breadcrumb/location text, search field, notification/avatar/user controls where appropriate. +* Restyle the dashboard as a dense admin workspace with: + * Large white dashboard shell. + * Four metric cards with colored icon blocks and month-over-month text. + * Search/filter panel. + * Ranking/chart-like panel matching the screenshot's simple blue bar chart look. + * Recent borrowing table and overdue table. + * Book-management table. + * Shortcut cards for reader management, report center, borrowing circulation, and system logs. +* Restyle shared tables, forms, buttons, badges, panels, empty states, and navigation so secondary pages feel consistent with the reference. +* Preserve existing JSP/Servlet behavior and role-based visibility. +* Keep user-visible copy in Simplified Chinese. +* Keep responsive behavior usable on narrower screens. + +## Acceptance Criteria + +* [ ] Dashboard layout visibly matches the reference image's structure, spacing, palette, and density. +* [ ] Shared navigation changes are reflected across existing JSP pages without breaking role-based links. +* [ ] Existing forms and tables remain functional and readable after the redesign. +* [ ] No React/Vue/SPA tooling is introduced. +* [ ] Maven build succeeds. + +## Definition Of Done + +* Tests/build run where available. +* JSP/CSS changes reviewed against frontend specs and the reference image. +* Task context files are curated for implement/check agents. +* Any reusable convention learned during the work is considered for spec update. + +## Out Of Scope + +* Backend behavior changes. +* Database schema changes. +* Replacing JSP with a JavaScript framework. +* Exact live charting libraries unless needed; a CSS/HTML approximation is acceptable for this visual refactor. + +## Technical Notes + +* Reference image copied to `.trellis/tasks/04-28-frontend-reference-redesign/research/reference-dashboard.png`. +* Visual notes are recorded in `.trellis/tasks/04-28-frontend-reference-redesign/research/reference-dashboard-visual-notes.md`. +* Relevant spec index: `.trellis/spec/frontend/index.md`. diff --git a/.trellis/tasks/04-28-frontend-reference-redesign/research/reference-dashboard-visual-notes.md b/.trellis/tasks/04-28-frontend-reference-redesign/research/reference-dashboard-visual-notes.md new file mode 100644 index 0000000..b68fe85 --- /dev/null +++ b/.trellis/tasks/04-28-frontend-reference-redesign/research/reference-dashboard-visual-notes.md @@ -0,0 +1,53 @@ +# Reference Dashboard Visual Notes + +## Source + +Reference image: `.trellis/tasks/04-28-frontend-reference-redesign/research/reference-dashboard.png` + +Original accessible path during planning: `/mnt/d/qq/聊天文件/2535624881/nt_qq/nt_data/Pic/2026-04/Ori/ab6d1035bac12c469acaffea0e6db1c8.png` + +## Overall Layout + +* Full application frame with a fixed-width dark navy sidebar on the left and a light gray workspace on the right. +* Sidebar width is about 250 px in the reference. It contains the system name at top, role workbench buttons, module navigation links, and a compact menu icon near the bottom. +* Main area has a white top bar with breadcrumb text on the left and search, notification, avatar, role label, and dropdown affordance on the right. +* Content area uses a light gray page background with white cards, small border radius, subtle shadows, and dense spacing. + +## Palette And Typography + +* Sidebar: very dark navy gradient or solid dark blue-black. +* Primary action blue: medium royal blue. +* Secondary accent colors: teal, orange, purple, red, and green for icon/stat/status accents. +* Cards: white with light gray borders and soft shadows. +* Text: dark slate/near black for headings, gray for helper copy and metadata. +* Typography is compact, Chinese UI oriented, and dashboard-like rather than marketing-like. + +## Dashboard Structure + +* Top hero panel starts with "管理员工作台" heading and short explanatory copy. +* Four statistic cards in one row: + * 馆藏总量 + * 在借数量 + * 逾期数量 + * 读者总数 +* Middle area: + * Left: 馆藏检索 form with two-column labels/inputs/select and blue search button plus reset button. + * Right: 热门图书排行 bar chart with blue vertical bars and small labels. +* Table area: + * 借阅流通 recent records table. + * 逾期列表 pending overdue table. + * 图书管理 book list table. +* Bottom/right shortcut tiles: + * 读者管理 + * 报表中心 + * 借阅流通 + * 系统日志 + +## Interaction And Reuse Targets + +* Preserve existing links and routes in navigation. +* Sidebar active/hover states should use blue filled pills. +* Role workbench entries should be prominent colored pills near the top of the sidebar. +* Tables should be compact with subtle row separators and badge-like statuses. +* Forms should use compact inputs with borders and clear focus states. +* Existing JSP pages can reuse shared classes for panels, toolbar forms, tables, badges, action links, and cards. diff --git a/.trellis/tasks/04-28-frontend-reference-redesign/research/reference-dashboard.png b/.trellis/tasks/04-28-frontend-reference-redesign/research/reference-dashboard.png new file mode 100644 index 0000000..e7d2044 Binary files /dev/null and b/.trellis/tasks/04-28-frontend-reference-redesign/research/reference-dashboard.png differ diff --git a/.trellis/tasks/04-28-frontend-reference-redesign/task.json b/.trellis/tasks/04-28-frontend-reference-redesign/task.json new file mode 100644 index 0000000..287bbad --- /dev/null +++ b/.trellis/tasks/04-28-frontend-reference-redesign/task.json @@ -0,0 +1,26 @@ +{ + "id": "frontend-reference-redesign", + "name": "frontend-reference-redesign", + "title": "brainstorm: 仿照参考图重构前端", + "description": "", + "status": "in_progress", + "dev_type": null, + "scope": null, + "package": null, + "priority": "P2", + "creator": "Zzzz", + "assignee": "Zzzz", + "createdAt": "2026-04-28", + "completedAt": null, + "branch": null, + "base_branch": "master", + "worktree_path": null, + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [], + "parent": null, + "relatedFiles": [], + "notes": "", + "meta": {} +} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/common/header.jspf b/src/main/webapp/WEB-INF/jsp/common/header.jspf index 05b9225..0c04cc7 100644 --- a/src/main/webapp/WEB-INF/jsp/common/header.jspf +++ b/src/main/webapp/WEB-INF/jsp/common/header.jspf @@ -1,31 +1,120 @@ <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
- MZH 图书馆 - - - + + + + + +
+ + +
+ ! + + + + + + + +
+
+
+ + MZH 图书馆 + +
diff --git a/src/main/webapp/WEB-INF/jsp/dashboard.jsp b/src/main/webapp/WEB-INF/jsp/dashboard.jsp index dde32f9..a9632fe 100644 --- a/src/main/webapp/WEB-INF/jsp/dashboard.jsp +++ b/src/main/webapp/WEB-INF/jsp/dashboard.jsp @@ -10,94 +10,300 @@ <%@ include file="/WEB-INF/jsp/common/header.jspf" %> -
-
-

- -

-

控制台

-

当前登录:

+
+
+
+

+ +

+

+ + 管理员工作台 + 馆员工作台 + 读者工作台 + +

+

登录后进入 Dashboard,会话仅保存安全的 AuthenticatedUser 快照、角色代码与权限代码集合。

+
+
+ 当前登录 + +
-
- -
-

系统管理

-

账户、角色、权限和系统维护入口。

- 打开 -
+
+
+ +
+

馆藏总量

+

12,586

+

较上月 ↑ 5.2%

+
+
+
+ +
+

在借数量

+

1,258

+

较上月 ↑ 3.1%

+
+
+
+ +
+

逾期数量

+

87

+

较上月 ↓ 12.4%

+
+
+
+ +
+

读者总数

+

3,682

+

较上月 ↑ 4.8%

+
+
+
-
-

用户管理

-

创建、更新、停用和查看登录账户。

- 打开 -
- -
-

系统日志

-

查看账户与维护操作的只读审计记录。

- 打开 -
-
- - -
-

馆员工作台

-

图书、读者、借阅、归还、续借和逾期处理入口。

- 打开 -
- -
-

图书管理

-

创建、更新、删除和查看图书库存记录。

- 打开 -
- -
-

分类维护

-

维护图书记录和检索筛选使用的馆藏分类。

- 打开 -
- -
-

读者管理

-

创建、更新、停用和查看读者借阅资格记录。

- 打开 -
- -
-

借阅管理

-

创建借阅、处理归还、续借有效记录并查看逾期项目。

- 打开 -
- -
-

报表中心

-

查看库存状况、借阅统计、逾期记录和热门图书。

- 打开 -
-
- -
+
+

馆藏检索

-

按书名、作者、分类或图书编号检索图书。

- 检索 +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + 重置 +
+
-
-

读者中心

-

读者自助访问馆藏和借阅历史的入口。

- 打开 +
+
+

热门图书排行

+ 借阅次数TOP10 +
+
+
230活着
+
198三体
+
175百年孤独
+
164围城
+
150平凡的世界
+
138解忧杂货店
+
120红楼梦
+
112白夜行
+
98追风筝的人
+
85小王子
+
- - -
-

我的借阅历史

-

查看您的在借、已还和逾期借阅记录。

- 打开 -
-
+ + +
+
+

借阅流通 最新记录

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
流水号读者姓名图书编号书名借阅日期应还日期状态库存联动
L20240521001张晓明B001245活着2024-05-212024-06-04在借库存-1
L20240521002李华B001026三体2024-05-202024-06-03在借库存-1
L20240521003王丽B002031百年孤独2024-05-182024-06-01已归还库存+1
L20240521004陈强B001895围城2024-05-102024-05-24逾期库存-1
L20240521005刘洋B002119解忧杂货店2024-05-122024-05-26逾期库存-1
+
+
+ +
+

逾期列表 待处理

+
+ + + + + + + + + + + + + + + + + +
读者姓名图书编号书名应还日期逾期天数
陈强B001895围城2024-05-247天
赵敏B001122平凡的世界2024-05-2011天
孙涛B002003红楼梦2024-05-1813天
周雨B000987追风筝的人2024-05-1714天
吴迪B001776白夜行2024-05-1516天
+
+
+ +
+
+

图书管理 馆藏列表

+ 进入管理 +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
图书编号书名作者分类出版日期库存状态馆藏地操作
B001245活着余华文学 > 小说2012-08-01可借(15)二楼文学区管理
B001026三体刘慈欣文学 > 科幻2008-01-01可借(8)三楼科幻区管理
B002031百年孤独加西亚·马尔克斯文学 > 外国文学2011-06-01可借(6)二楼文学区管理
B001895围城钱钟书文学 > 小说2008-05-01可借(4)二楼文学区管理
B002119解忧杂货店东野圭吾文学 > 小说2014-07-01可借(10)二楼文学区管理
+
+
+ + +
+
+ + +
+ + + 我的借阅 + 查看在借、已还、续借次数和逾期状态 + + + + 馆藏检索 + 按书名、作者、分类或图书编号查找馆藏 + +
+
diff --git a/src/main/webapp/static/css/app.css b/src/main/webapp/static/css/app.css index ca8e3d1..d002526 100644 --- a/src/main/webapp/static/css/app.css +++ b/src/main/webapp/static/css/app.css @@ -1,102 +1,410 @@ :root { color-scheme: light; - --color-ink: #202124; - --color-muted: #5f6368; - --color-border: #d9dde3; + --color-ink: #111827; + --color-muted: #64748b; + --color-subtle: #94a3b8; + --color-border: #e2e8f0; --color-panel: #ffffff; - --color-page: #f5f7fb; - --color-primary: #256f6c; - --color-primary-strong: #1b5654; - --color-accent: #b54238; - --color-success: #2f6f3e; - --color-warning: #8a5a00; - --shadow-panel: 0 18px 45px rgba(28, 39, 49, 0.12); + --color-page: #eef3f8; + --color-sidebar: #101a2b; + --color-sidebar-soft: #1c2a41; + --color-primary: #2869e8; + --color-primary-strong: #1d4fd7; + --color-primary-soft: #eaf1ff; + --color-accent: #dc4c4c; + --color-success: #1f9d68; + --color-warning: #f08a24; + --color-purple: #7c6ee6; + --shadow-panel: 0 10px 28px rgba(15, 23, 42, 0.09); + --shadow-soft: 0 4px 14px rgba(15, 23, 42, 0.06); + --sidebar-width: 248px; + --topbar-height: 64px; } * { box-sizing: border-box; } +html { + min-width: 320px; +} + body { margin: 0; min-height: 100vh; color: var(--color-ink); background: var(--color-page); font-family: Arial, "Microsoft YaHei", sans-serif; - line-height: 1.5; + font-size: 14px; + line-height: 1.45; } a { color: inherit; } +button, +input, +select, +textarea { + font: inherit; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + .app-header { + min-height: 0; +} + +.auth-page .app-header { min-height: 64px; display: flex; align-items: center; - justify-content: space-between; - gap: 24px; padding: 0 32px; - border-bottom: 1px solid var(--color-border); - background: rgba(255, 255, 255, 0.96); + border-bottom: 1px solid rgba(226, 232, 240, 0.8); + background: rgba(255, 255, 255, 0.9); } -.brand { - color: var(--color-primary-strong); - font-weight: 700; - text-decoration: none; -} - -.top-nav { +.app-header:has(.auth-brand) { + min-height: 64px; display: flex; align-items: center; - gap: 14px; - flex-wrap: wrap; - color: var(--color-muted); - font-size: 14px; + padding: 0 32px; + border-bottom: 1px solid rgba(226, 232, 240, 0.8); + background: rgba(255, 255, 255, 0.94); } -.top-nav a { +.app-header:has(.auth-brand) + .page-shell { + width: min(1120px, calc(100% - 32px)); + margin: 0 auto; + padding: 36px 0 56px; +} + +.auth-brand { + color: var(--color-primary-strong); + font-size: 17px; + font-weight: 800; text-decoration: none; } +.app-sidebar { + position: fixed; + inset: 0 auto 0 0; + z-index: 30; + width: var(--sidebar-width); + display: flex; + flex-direction: column; + padding: 22px 14px 16px; + color: #cbd5e1; + background: + radial-gradient(circle at 25% 8%, rgba(59, 130, 246, 0.16), transparent 34%), + linear-gradient(180deg, #101a2b 0%, #172235 46%, #0f1726 100%); + box-shadow: 12px 0 30px rgba(15, 23, 42, 0.18); +} + +.sidebar-brand { + display: flex; + align-items: center; + gap: 10px; + min-height: 42px; + padding: 0 12px; + color: #ffffff; + font-size: 17px; + font-weight: 800; + text-decoration: none; +} + +.brand-mark { + width: 28px; + height: 28px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 7px; + color: #102033; + background: #ffffff; + font-size: 15px; +} + +.role-workbench { + margin-top: 24px; + padding: 0 0 14px; +} + +.sidebar-section-title { + margin: 0 0 10px; + padding: 0 12px; + color: #91a2bd; + font-size: 13px; + font-weight: 700; +} + +.role-chip { + min-height: 38px; + display: grid; + grid-template-columns: 28px 1fr; + grid-template-rows: auto auto; + gap: 0 9px; + align-items: center; + margin: 8px 0; + padding: 8px 11px; + border-radius: 7px; + color: #ffffff; + text-decoration: none; + box-shadow: 0 8px 18px rgba(15, 23, 42, 0.16); +} + +.role-chip span { + grid-row: 1 / 3; + width: 26px; + height: 26px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 999px; + background: rgba(255, 255, 255, 0.24); + font-size: 13px; + font-weight: 800; +} + +.role-chip strong { + line-height: 1.1; +} + +.role-chip small { + color: rgba(255, 255, 255, 0.82); + font-size: 11px; +} + +.role-chip-admin { + background: linear-gradient(135deg, #316cf4, #1f57d8); +} + +.role-chip-librarian { + background: linear-gradient(135deg, #4db7ad, #278f87); +} + +.role-chip-reader { + background: linear-gradient(135deg, #ffac48, #f08a24); +} + +.side-nav { + display: grid; + gap: 5px; + margin-top: 4px; +} + +.side-nav-link { + min-height: 40px; + display: flex; + align-items: center; + gap: 11px; + padding: 9px 11px; + border-radius: 7px; + color: #c8d2df; + text-decoration: none; +} + +.side-nav-link span { + width: 22px; + display: inline-flex; + justify-content: center; + color: #9aa9bd; + font-size: 13px; + font-weight: 800; +} + +.side-nav-link:hover, +.side-nav-link.is-active { + color: #ffffff; + background: linear-gradient(135deg, #2f6df3, #1f58d8); + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08); +} + +.side-nav-link:hover span, +.side-nav-link.is-active span { + color: #ffffff; +} + +.sidebar-footer { + min-height: 34px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + margin-top: auto; + padding: 10px 12px 0; + border-top: 1px solid rgba(148, 163, 184, 0.14); + color: #9aa9bd; +} + +.sidebar-footer a { + color: #cbd5e1; + font-size: 13px; + text-decoration: none; +} + +.sidebar-menu-dot { + color: #91a2bd; + font-size: 20px; +} + +.app-topbar { + position: fixed; + inset: 0 0 auto var(--sidebar-width); + z-index: 25; + height: var(--topbar-height); + display: flex; + align-items: center; + gap: 18px; + padding: 0 28px; + border-bottom: 1px solid var(--color-border); + background: rgba(255, 255, 255, 0.96); + box-shadow: 0 4px 18px rgba(15, 23, 42, 0.05); +} + +.breadcrumb { + flex: 1; + min-width: 160px; + color: #475569; + font-size: 13px; + font-weight: 700; +} + +.breadcrumb span { + margin: 0 10px; + color: #cbd5e1; +} + +.global-search { + width: min(360px, 34vw); + min-width: 240px; + height: 38px; + display: flex; + align-items: center; + border: 1px solid var(--color-border); + border-radius: 7px; + background: #ffffff; +} + +.global-search input { + width: 100%; + height: 100%; + padding: 0 12px; + border: 0; + outline: 0; + color: var(--color-ink); + background: transparent; +} + +.global-search button { + width: 40px; + height: 100%; + border: 0; + color: #64748b; + background: transparent; + cursor: pointer; +} + +.topbar-actions { + display: flex; + align-items: center; + gap: 11px; + color: #334155; + white-space: nowrap; +} + +.notification-dot { + width: 22px; + height: 22px; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid var(--color-border); + border-radius: 999px; + color: #ffffff; + background: #ef4444; + font-size: 12px; + font-weight: 800; +} + +.avatar { + width: 32px; + height: 32px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 999px; + color: #102033; + background: linear-gradient(135deg, #dbeafe, #ffffff); + font-weight: 800; + box-shadow: inset 0 0 0 1px #bfdbfe; +} + +.user-pill, +.role-label { + max-width: 160px; + overflow: hidden; + text-overflow: ellipsis; +} + .user-pill { - max-width: 220px; - padding: 6px 10px; - overflow: hidden; - color: var(--color-ink); - border: 1px solid var(--color-border); - border-radius: 6px; - text-overflow: ellipsis; - white-space: nowrap; + font-weight: 700; +} + +.role-label { + color: #64748b; + font-size: 13px; } .auth-page { background: - linear-gradient(rgba(245, 247, 251, 0.86), rgba(245, 247, 251, 0.92)), + linear-gradient(rgba(245, 247, 251, 0.86), rgba(245, 247, 251, 0.94)), url("../images/library-login.svg") center / cover no-repeat; } -.auth-shell, -.page-shell { - width: min(1120px, calc(100% - 32px)); - margin: 0 auto; -} - .auth-shell { + width: min(1120px, calc(100% - 32px)); min-height: calc(100vh - 64px); display: grid; align-items: center; + margin: 0 auto; padding: 48px 0; } +.page-shell { + width: min(1280px, calc(100% - 32px)); + margin: 0 auto; + padding: 34px 0 56px; +} + +body:not(.auth-page) .page-shell { + width: auto; + max-width: none; + margin-left: var(--sidebar-width); + padding: calc(var(--topbar-height) + 18px) 24px 44px; +} + .login-panel, .notice-panel, .dashboard-hero, .workspace-card, .toolbar-panel, .table-panel, -.form-panel { +.form-panel, +.report-card, +.dashboard-panel, +.metric-card, +.shortcut-card { border: 1px solid var(--color-border); border-radius: 8px; background: var(--color-panel); @@ -111,10 +419,9 @@ a { .eyebrow { margin: 0 0 6px; color: var(--color-primary); - font-size: 13px; - font-weight: 700; + font-size: 12px; + font-weight: 800; letter-spacing: 0; - text-transform: uppercase; } h1, @@ -124,14 +431,15 @@ p { } h1 { - margin-bottom: 18px; - font-size: 32px; - line-height: 1.15; + margin-bottom: 10px; + font-size: 27px; + line-height: 1.18; } h2 { margin-bottom: 10px; - font-size: 20px; + font-size: 18px; + line-height: 1.25; } .login-form { @@ -139,44 +447,76 @@ h2 { gap: 10px; } -.login-form label { - color: var(--color-muted); - font-size: 14px; - font-weight: 700; +.login-form label, +.search-form label, +.dashboard-search-form label, +.form-field label { + color: #475569; + font-size: 13px; + font-weight: 800; } -.login-form input { +.login-form input, +.search-form input, +.search-form select, +.dashboard-search-form input, +.dashboard-search-form select, +.book-form input, +.book-form select, +.category-form input, +.category-form textarea, +.reader-form input, +.reader-form select, +.user-form input, +.user-form select, +.borrow-form input { width: 100%; - min-height: 44px; - padding: 10px 12px; + min-height: 38px; + padding: 8px 11px; border: 1px solid var(--color-border); border-radius: 6px; - font: inherit; + color: var(--color-ink); + background: #ffffff; + outline: 0; } -.login-form input:focus { - outline: 3px solid rgba(37, 111, 108, 0.18); +.login-form input:focus, +.search-form input:focus, +.search-form select:focus, +.dashboard-search-form input:focus, +.dashboard-search-form select:focus, +.book-form input:focus, +.book-form select:focus, +.category-form input:focus, +.category-form textarea:focus, +.reader-form input:focus, +.reader-form select:focus, +.user-form input:focus, +.user-form select:focus, +.borrow-form input:focus, +.global-search:focus-within { border-color: var(--color-primary); + box-shadow: 0 0 0 3px rgba(40, 105, 232, 0.14); } .button { + min-height: 36px; display: inline-flex; - min-height: 40px; align-items: center; justify-content: center; - padding: 9px 14px; + padding: 8px 14px; border: 1px solid transparent; border-radius: 6px; - font: inherit; - font-weight: 700; + font-weight: 800; text-decoration: none; cursor: pointer; + white-space: nowrap; } .button-primary { - margin-top: 12px; color: #ffffff; background: var(--color-primary); + box-shadow: 0 6px 14px rgba(40, 105, 232, 0.22); } .button-primary:hover { @@ -184,18 +524,24 @@ h2 { } .button-secondary { - color: var(--color-primary-strong); - border-color: rgba(37, 111, 108, 0.35); + color: #475569; + border-color: var(--color-border); background: #ffffff; } +.button-secondary:hover { + color: var(--color-primary-strong); + border-color: #bcd0ff; + background: #f8fbff; +} + .button-danger { color: #ffffff; background: var(--color-accent); } .button-danger:hover { - background: #8f3028; + background: #b93737; } .button:disabled { @@ -203,59 +549,358 @@ h2 { opacity: 0.58; } +.login-form .button-primary { + margin-top: 12px; +} + .message { margin-bottom: 16px; padding: 10px 12px; border-radius: 6px; - font-size: 14px; + font-size: 13px; } .message-error { - color: #7a211a; - border: 1px solid rgba(181, 66, 56, 0.3); - background: #fff0ee; + color: #9f1d1d; + border: 1px solid rgba(220, 76, 76, 0.26); + background: #fff1f1; } .message-success { - color: #1f572e; - border: 1px solid rgba(47, 111, 62, 0.3); - background: #effaf1; -} - -.page-shell { - padding: 36px 0 56px; + color: #11633f; + border: 1px solid rgba(31, 157, 104, 0.26); + background: #ecfdf5; } .dashboard-hero { - padding: 28px; - margin-bottom: 24px; + padding: 24px; + margin-bottom: 16px; } -.dashboard-hero p:last-child, +.dashboard-welcome { + display: flex; + align-items: center; + justify-content: space-between; + gap: 18px; +} + +.dashboard-welcome p:last-child, .workspace-card p:last-child, .notice-panel p:last-child { margin-bottom: 0; } +.welcome-user { + min-width: 150px; + display: grid; + gap: 4px; + padding: 11px 14px; + border: 1px solid #dbeafe; + border-radius: 8px; + background: var(--color-primary-soft); +} + +.welcome-user span { + color: #64748b; + font-size: 12px; + font-weight: 700; +} + +.welcome-user strong { + color: var(--color-primary-strong); +} + +.dashboard-metrics { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 16px; + margin-bottom: 16px; +} + +.metric-card { + min-height: 98px; + display: flex; + align-items: center; + gap: 18px; + padding: 18px 20px; +} + +.metric-card h2 { + margin-bottom: 5px; + color: #475569; + font-size: 13px; +} + +.metric-icon { + width: 52px; + height: 52px; + display: inline-flex; + flex: 0 0 52px; + align-items: center; + justify-content: center; + border-radius: 999px; + color: #ffffff; + font-size: 18px; + font-weight: 800; +} + +.metric-blue { + background: linear-gradient(135deg, #4b83f3, #2869e8); +} + +.metric-green { + background: linear-gradient(135deg, #5ccaae, #1f9d68); +} + +.metric-orange { + background: linear-gradient(135deg, #ffb25c, #f08a24); +} + +.metric-purple { + background: linear-gradient(135deg, #9187f1, #6f60e5); +} + +.metric-value { + margin-bottom: 4px; + font-size: 24px; + font-weight: 900; + line-height: 1; +} + +.metric-value small { + font-size: 13px; + font-weight: 800; +} + +.metric-trend { + margin-bottom: 0; + color: #64748b; + font-size: 12px; +} + +.trend-up { + color: #e05252; +} + +.trend-down { + color: #1f9d68; +} + +.dashboard-grid { + display: grid; + grid-template-columns: minmax(320px, 0.96fr) minmax(420px, 1.34fr); + gap: 16px; + margin-bottom: 16px; +} + +.dashboard-panel { + padding: 16px; +} + +.dashboard-panel h2 { + margin-bottom: 14px; + font-size: 16px; +} + +.dashboard-search-form { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 13px 22px; + align-items: end; +} + +.search-field { + display: grid; + gap: 7px; +} + +.dashboard-form-actions { + grid-column: 1 / -1; + display: flex; + justify-content: flex-end; + gap: 10px; +} + +.panel-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.panel-heading h2 { + margin-bottom: 0; +} + +.panel-heading span, +.table-panel-compact h2 span { + color: #64748b; + font-size: 12px; + font-weight: 700; +} + +.panel-heading a, +.text-link { + color: var(--color-primary); + font-weight: 800; + text-decoration: none; +} + +.rank-chart { + height: 162px; + display: grid; + grid-template-columns: repeat(10, minmax(36px, 1fr)); + align-items: end; + gap: 12px; + padding: 6px 4px 0; + border-top: 1px solid #eef2f7; + background: + linear-gradient(to top, rgba(226, 232, 240, 0.62) 1px, transparent 1px) 0 0 / 100% 32px; +} + +.rank-item { + height: 138px; + display: grid; + grid-template-rows: 18px 1fr 22px; + justify-items: center; + align-items: end; + min-width: 0; +} + +.rank-value { + color: #475569; + font-size: 11px; + font-weight: 800; +} + +.rank-bar { + width: 24px; + height: var(--bar-height); + min-height: 18px; + border-radius: 4px 4px 0 0; + background: linear-gradient(180deg, #4b83f3, #2869e8); + box-shadow: 0 4px 10px rgba(40, 105, 232, 0.18); +} + +.rank-item small { + max-width: 68px; + overflow: hidden; + color: #475569; + font-size: 11px; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dashboard-table-grid { + display: grid; + grid-template-columns: minmax(0, 1.8fr) minmax(300px, 0.9fr); + gap: 16px; +} + +.table-panel-wide { + grid-column: span 1; +} + +.table-panel-compact { + min-width: 0; + padding: 14px 16px; +} + +.table-panel-compact h2 { + margin-bottom: 12px; + font-size: 16px; +} + +.shortcut-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px; +} + +.reader-shortcut-grid { + margin-top: 16px; +} + +.shortcut-card { + min-height: 100px; + display: grid; + grid-template-columns: 46px 1fr 14px; + grid-template-rows: auto auto; + gap: 5px 13px; + align-items: center; + padding: 16px; + text-decoration: none; +} + +.shortcut-card::after { + content: "›"; + grid-row: 1 / 3; + grid-column: 3; + color: #94a3b8; + font-size: 24px; +} + +.shortcut-card strong { + align-self: end; +} + +.shortcut-card small { + align-self: start; + color: #64748b; + line-height: 1.35; +} + +.shortcut-icon { + grid-row: 1 / 3; + width: 42px; + height: 42px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 999px; + font-weight: 900; +} + +.shortcut-blue { + color: #2454cb; + background: #eaf1ff; +} + +.shortcut-green { + color: #16825a; + background: #dcfce7; +} + +.shortcut-orange { + color: #c46615; + background: #fff3df; +} + +.shortcut-purple { + color: #6254d6; + background: #eeeaff; +} + .card-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); - gap: 18px; + grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); + gap: 16px; } .report-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 16px; - margin-bottom: 24px; + margin-bottom: 18px; } .workspace-card { - min-height: 190px; + min-height: 156px; display: flex; flex-direction: column; align-items: flex-start; - padding: 24px; + padding: 20px; } .workspace-card p { @@ -267,28 +912,24 @@ h2 { } .report-card { - min-height: 150px; - padding: 20px; - border: 1px solid var(--color-border); - border-radius: 8px; - background: var(--color-panel); - box-shadow: var(--shadow-panel); + min-height: 136px; + padding: 18px; } .report-card h2 { color: var(--color-muted); - font-size: 14px; + font-size: 13px; } .report-card-alert { - border-color: rgba(181, 66, 56, 0.28); + border-color: rgba(220, 76, 76, 0.28); } .report-metric { margin-bottom: 0; color: var(--color-primary-strong); - font-size: 34px; - font-weight: 700; + font-size: 32px; + font-weight: 900; line-height: 1; } @@ -298,7 +939,7 @@ h2 { } .role-actions { - margin-top: 24px; + margin-top: 18px; } .catalog-hero { @@ -322,69 +963,23 @@ h2 { .toolbar-panel, .table-panel, .form-panel { - padding: 24px; - margin-bottom: 24px; + padding: 20px; + margin-bottom: 18px; } .search-form { display: grid; - grid-template-columns: repeat(4, minmax(120px, 1fr)) auto auto auto; + grid-template-columns: repeat(4, minmax(130px, 1fr)) auto auto auto; gap: 10px; align-items: end; } .borrowing-search-form { - grid-template-columns: repeat(3, minmax(120px, 1fr)) auto auto; + grid-template-columns: repeat(3, minmax(130px, 1fr)) auto auto; } .system-log-search-form { - grid-template-columns: repeat(4, minmax(120px, 1fr)) auto auto; -} - -.search-field { - display: grid; - gap: 6px; -} - -.search-form label { - color: var(--color-muted); - font-size: 13px; - font-weight: 700; -} - -.search-form input, -.search-form select, -.book-form input, -.book-form select, -.category-form input, -.category-form textarea, -.reader-form input, -.reader-form select, -.user-form input, -.user-form select, -.borrow-form input { - width: 100%; - min-height: 42px; - padding: 9px 11px; - border: 1px solid var(--color-border); - border-radius: 6px; - background: #ffffff; - font: inherit; -} - -.search-form input:focus, -.search-form select:focus, -.book-form input:focus, -.book-form select:focus, -.category-form input:focus, -.category-form textarea:focus, -.reader-form input:focus, -.reader-form select:focus, -.user-form input:focus, -.user-form select:focus, -.borrow-form input:focus { - outline: 3px solid rgba(37, 111, 108, 0.18); - border-color: var(--color-primary); + grid-template-columns: repeat(4, minmax(130px, 1fr)) auto auto; } .table-scroll { @@ -396,7 +991,12 @@ h2 { width: 100%; min-width: 760px; border-collapse: collapse; - font-size: 14px; + font-size: 13px; +} + +.dashboard-data-table { + min-width: 720px; + font-size: 12px; } .borrowing-table { @@ -411,92 +1011,90 @@ h2 { .data-table th, .data-table td { - padding: 12px 10px; + padding: 10px 9px; text-align: left; border-bottom: 1px solid var(--color-border); vertical-align: middle; } +.dashboard-data-table th, +.dashboard-data-table td { + padding: 7px 8px; +} + .data-table th { - color: var(--color-muted); - font-size: 13px; - font-weight: 700; + color: #475569; + font-size: 12px; + font-weight: 900; background: #f8fafc; } +.data-table tbody tr:hover { + background: #f8fbff; +} + .empty-state { margin-bottom: 0; color: var(--color-muted); } .status-pill { + min-height: 24px; display: inline-flex; - min-height: 28px; align-items: center; - padding: 4px 9px; + justify-content: center; + padding: 3px 9px; border-radius: 999px; font-size: 12px; - font-weight: 700; -} - -.status-available { - color: var(--color-success); - background: #edf8ef; -} - -.status-unavailable { - color: var(--color-warning); - background: #fff7e5; -} - -.status-archived { - color: var(--color-muted); - background: #eef1f5; -} - -.status-active { - color: var(--color-success); - background: #edf8ef; -} - -.status-suspended { - color: var(--color-warning); - background: #fff7e5; -} - -.status-inactive { - color: var(--color-muted); - background: #eef1f5; -} - -.status-returned { - color: var(--color-muted); - background: #eef1f5; + font-weight: 800; + white-space: nowrap; } +.status-available, +.status-active, .status-success { - color: var(--color-success); - background: #edf8ef; + color: #148158; + background: #dcfce7; } -.status-failure { - color: #7a211a; - background: #fff0ee; +.status-unavailable, +.status-suspended { + color: #b45309; + background: #fff3df; } +.status-archived, +.status-inactive, +.status-returned, .status-unknown { - color: var(--color-muted); - background: #eef1f5; + color: #64748b; + background: #eef2f7; } +.status-failure, .status-overdue { - color: #7a211a; - background: #fff0ee; + color: #c24141; + background: #fee2e2; +} + +.stock-plus { + color: #16a34a; + font-weight: 800; +} + +.stock-return { + color: #1d4ed8; + font-weight: 800; +} + +.overdue-days { + color: #dc2626; + font-weight: 900; } .muted-text { color: var(--color-muted); - font-size: 13px; + font-size: 12px; } .table-actions { @@ -511,7 +1109,7 @@ h2 { } .form-panel { - max-width: 860px; + max-width: 900px; } .book-form, @@ -543,14 +1141,8 @@ h2 { resize: vertical; } -.form-field label { - color: var(--color-muted); - font-size: 14px; - font-weight: 700; -} - .field-error { - color: #7a211a; + color: #b93737; font-size: 13px; } @@ -560,19 +1152,83 @@ h2 { flex-wrap: wrap; } -@media (max-width: 720px) { - .app-header { - align-items: flex-start; - flex-direction: column; - padding: 16px; +@media (max-width: 1180px) { + .dashboard-metrics { + grid-template-columns: repeat(2, minmax(0, 1fr)); } - .top-nav { + .dashboard-grid, + .dashboard-table-grid { + grid-template-columns: 1fr; + } + + .shortcut-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 960px) { + .app-sidebar, + .app-topbar { + position: static; + width: auto; + inset: auto; + } + + .app-sidebar { + min-height: auto; + border-radius: 0; + } + + .app-topbar { + height: auto; + flex-wrap: wrap; + padding: 14px 16px; + } + + body:not(.auth-page) .page-shell { + width: min(1280px, calc(100% - 24px)); + margin: 0 auto; + padding: 18px 0 40px; + } + + .global-search { width: 100%; } +} + +@media (max-width: 720px) { + .auth-page .app-header { + padding: 0 16px; + } h1 { - font-size: 28px; + font-size: 24px; + } + + .dashboard-welcome, + .catalog-hero { + align-items: stretch; + flex-direction: column; + } + + .dashboard-metrics, + .dashboard-search-form, + .shortcut-grid, + .search-form, + .borrowing-search-form, + .system-log-search-form, + .form-grid { + grid-template-columns: 1fr; + } + + .rank-chart { + grid-template-columns: repeat(5, minmax(36px, 1fr)); + height: auto; + } + + .rank-item { + height: 132px; } .login-panel, @@ -582,12 +1238,10 @@ h2 { .report-card, .toolbar-panel, .table-panel, - .form-panel { + .form-panel, + .dashboard-panel, + .metric-card, + .shortcut-card { box-shadow: none; } - - .search-form, - .form-grid { - grid-template-columns: 1fr; - } }