Files
struts2-demo/web/WEB-INF/views/user/dashboard.jsp

342 lines
17 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录后仪表盘 - Struts2 学习实验台</title>
<style>
:root {
--bg: #eff7f5;
--panel: rgba(255,255,255,0.95);
--line: #d5ebe3;
--text: #123028;
--muted: #4a655c;
--brand: #0d8f7c;
--soft: #e8f8f2;
--shadow: 0 24px 60px rgba(0,0,0,0.18);
}
body {
margin: 0;
min-height: 100vh;
padding: 24px;
font-family: "Aptos", "Segoe UI", "Microsoft YaHei", sans-serif;
background:
radial-gradient(circle at top right, rgba(13, 143, 124, 0.15), transparent 24%),
radial-gradient(circle at bottom left, rgba(49, 196, 141, 0.12), transparent 22%),
var(--bg);
color: var(--text);
}
.shell {
max-width: 1320px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 18px;
}
.card {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 28px;
padding: 28px;
box-shadow: var(--shadow);
}
.hero-head {
display: flex;
justify-content: space-between;
gap: 16px;
align-items: flex-start;
}
.eyebrow {
font-size: 12px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--brand);
font-weight: 800;
}
h1, h2, h3 { margin: 10px 0 12px; }
p { margin: 0; color: var(--muted); line-height: 1.85; }
.actions, .links {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.btn, .btn-soft, .btn-ghost, .link-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 10px 14px;
border-radius: 999px;
font-weight: 700;
border: 1px solid var(--line);
text-decoration: none;
cursor: pointer;
}
.btn { background: linear-gradient(135deg, var(--brand), #31c48d); color: white; border: 0; }
.btn-soft { background: var(--soft); color: var(--brand); }
.btn-ghost, .link-btn { background: white; color: var(--text); }
.stats, .grid, .steps {
display: grid;
gap: 14px;
margin-top: 18px;
}
.stats { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.steps { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.stat, .panel, .step {
padding: 16px;
border-radius: 20px;
border: 1px solid var(--line);
background: rgba(255,255,255,0.68);
}
.stat span { display: block; font-size: 12px; color: var(--muted); margin-bottom: 6px; }
.stat strong { font-size: 22px; }
.tag-list {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 12px;
}
.tag {
display: inline-flex;
padding: 6px 10px;
border-radius: 999px;
background: var(--soft);
color: var(--brand);
font-size: 12px;
font-weight: 700;
}
.panel ul {
margin: 0;
padding-left: 18px;
color: var(--muted);
line-height: 1.85;
}
.step strong {
display: block;
margin-bottom: 8px;
}
.step p { margin: 0; }
@media (max-width: 960px) {
.stats, .grid, .steps { grid-template-columns: 1fr; }
.hero-head { flex-direction: column; }
}
</style>
</head>
<body>
<div class="shell">
<section class="card">
<div class="hero-head">
<div>
<div class="eyebrow" id="heroEyebrow">登录后仪表盘</div>
<h1 id="heroTitle">Session 已建立,后续实验页现在通过拦截器受保护</h1>
<p id="heroText">这一页的作用不是展示“登录成功”四个字,而是把你接下来的学习路线展开:继续看用户表单、校验、上传,或者回到门户解释这条 Session 鉴权链路是如何工作的。</p>
</div>
<div class="actions">
<button class="btn-ghost" type="button" id="languageBtn">EN</button>
<a class="btn" href="../userFormPage.action" id="primaryAction">进入用户表单</a>
<a class="btn-soft" href="../logout.action" id="logoutAction">退出登录</a>
</div>
</div>
<div class="stats">
<div class="stat">
<span id="statUserLabel">当前用户</span>
<strong><s:property value="displayName"/></strong>
</div>
<div class="stat">
<span id="statRoleLabel">当前角色</span>
<strong id="roleValue" data-role-code="<s:property value='role'/>"><s:property value="role"/></strong>
</div>
<div class="stat">
<span id="statTimeLabel">登录时间</span>
<strong><s:property value="loginTime"/></strong>
</div>
</div>
</section>
<section class="card">
<div class="eyebrow" id="secureEyebrow">受保护实验</div>
<h2 id="secureTitle">现在你可以进入这些需要登录的页面</h2>
<div class="grid">
<article class="panel">
<div class="tag-list">
<span class="tag" id="tagUser">字段绑定</span>
<span class="tag" id="tagUserSecure">受保护</span>
</div>
<h3 id="panelUserTitle">用户资料表单</h3>
<p id="panelUserText">继续看请求参数如何绑定到 Action 属性,并在成功页生成一份汇总结果。</p>
<div class="links" style="margin-top: 14px;">
<a class="link-btn" href="../userFormPage.action" id="panelUserLink">打开用户表单</a>
</div>
</article>
<article class="panel">
<div class="tag-list">
<span class="tag" id="tagValidation">validate()</span>
<span class="tag" id="tagValidationSecure">受保护</span>
</div>
<h3 id="panelValidationTitle">字段校验页</h3>
<p id="panelValidationText">对比校验失败和成功状态,理解为什么 Struts2 会在业务逻辑前先执行校验方法。</p>
<div class="links" style="margin-top: 14px;">
<a class="link-btn" href="../validationPage.action" id="panelValidationLink">打开校验页</a>
</div>
</article>
<article class="panel">
<div class="tag-list">
<span class="tag" id="tagUpload">multipart</span>
<span class="tag" id="tagUploadSecure">受保护</span>
</div>
<h3 id="panelUploadTitle">上传元数据页</h3>
<p id="panelUploadText">这里保留文件上传的教学价值,但不把文件真正写入磁盘,适合安全演示。</p>
<div class="links" style="margin-top: 14px;">
<a class="link-btn" href="../uploadPage.action" id="panelUploadLink">打开上传页</a>
</div>
</article>
<article class="panel">
<div class="tag-list">
<span class="tag" id="tagPortal">解释链路</span>
<span class="tag" id="tagPortalTrace">回看入口</span>
</div>
<h3 id="panelPortalTitle">回门户讲完整链路</h3>
<p id="panelPortalText">回到入口页可以从公开入口、登录动作、Session 写入、拦截器保护这条路线整体复盘。</p>
<div class="links" style="margin-top: 14px;">
<a class="link-btn" href="../index.action" id="panelPortalLink">返回门户</a>
</div>
</article>
</div>
</section>
<section class="card">
<div class="eyebrow" id="flowEyebrow">鉴权流程</div>
<h2 id="flowTitle">这次你实际走过的 Struts2 登录链路</h2>
<div class="steps">
<div class="step">
<strong id="flowStep1Title">1. 表单提交到 login Action</strong>
<p id="flowStep1Text">用户名和密码先进入 Action校验失败会直接回到登录页。</p>
</div>
<div class="step">
<strong id="flowStep2Title">2. Action 写入 Session</strong>
<p id="flowStep2Text">账号通过后,用户、角色和登录时间被放进 Session。</p>
</div>
<div class="step">
<strong id="flowStep3Title">3. 拦截器保护页面动作</strong>
<p id="flowStep3Text">用户表单、校验页、上传页和仪表盘都先经过 AuthInterceptor。</p>
</div>
<div class="step">
<strong id="flowStep4Title">4. 未登录会被打回登录页</strong>
<p id="flowStep4Text">如果没有 Session安全动作不会继续执行而是直接跳回登录入口。</p>
</div>
</div>
</section>
</div>
<script src="../assets/struts-lab.js"></script>
<script>
strutsLab.mount({
messages: {
zh: {
pageTitle: "登录后仪表盘 - Struts2 学习实验台",
text: {
heroEyebrow: "登录后仪表盘",
heroTitle: "Session 已建立,后续实验页现在通过拦截器受保护",
heroText: "这一页的作用不是展示“登录成功”四个字,而是把你接下来的学习路线展开:继续看用户表单、校验、上传,或者回到门户解释这条 Session 鉴权链路是如何工作的。",
primaryAction: "进入用户表单",
logoutAction: "退出登录",
statUserLabel: "当前用户",
statRoleLabel: "当前角色",
statTimeLabel: "登录时间",
secureEyebrow: "受保护实验",
secureTitle: "现在你可以进入这些需要登录的页面",
tagUser: "字段绑定",
tagUserSecure: "受保护",
panelUserTitle: "用户资料表单",
panelUserText: "继续看请求参数如何绑定到 Action 属性,并在成功页生成一份汇总结果。",
panelUserLink: "打开用户表单",
tagValidation: "validate()",
tagValidationSecure: "受保护",
panelValidationTitle: "字段校验页",
panelValidationText: "对比校验失败和成功状态,理解为什么 Struts2 会在业务逻辑前先执行校验方法。",
panelValidationLink: "打开校验页",
tagUpload: "multipart",
tagUploadSecure: "受保护",
panelUploadTitle: "上传元数据页",
panelUploadText: "这里保留文件上传的教学价值,但不把文件真正写入磁盘,适合安全演示。",
panelUploadLink: "打开上传页",
tagPortal: "解释链路",
tagPortalTrace: "回看入口",
panelPortalTitle: "回门户讲完整链路",
panelPortalText: "回到入口页可以从公开入口、登录动作、Session 写入、拦截器保护这条路线整体复盘。",
panelPortalLink: "返回门户",
flowEyebrow: "鉴权流程",
flowTitle: "这次你实际走过的 Struts2 登录链路",
flowStep1Title: "1. 表单提交到 login Action",
flowStep1Text: "用户名和密码先进入 Action校验失败会直接回到登录页。",
flowStep2Title: "2. Action 写入 Session",
flowStep2Text: "账号通过后,用户、角色和登录时间被放进 Session。",
flowStep3Title: "3. 拦截器保护页面动作",
flowStep3Text: "用户表单、校验页、上传页和仪表盘都先经过 AuthInterceptor。",
flowStep4Title: "4. 未登录会被打回登录页",
flowStep4Text: "如果没有 Session安全动作不会继续执行而是直接跳回登录入口。"
}
},
en: {
pageTitle: "Post-login Dashboard - Struts2 Learning Lab",
text: {
heroEyebrow: "Post-login dashboard",
heroTitle: "A session now exists, and later lab pages are protected by an interceptor",
heroText: "This page is not just a success notice. It expands the next learning route: continue to user forms, validation, and upload, or go back to the portal and explain the full session-auth path.",
primaryAction: "Open user form",
logoutAction: "Log out",
statUserLabel: "Current user",
statRoleLabel: "Current role",
statTimeLabel: "Login time",
secureEyebrow: "Protected labs",
secureTitle: "These pages now require a valid login session",
tagUser: "Binding",
tagUserSecure: "Protected",
panelUserTitle: "User profile form",
panelUserText: "Continue with request parameter binding into action properties and a structured success summary.",
panelUserLink: "Open user form",
tagValidation: "validate()",
tagValidationSecure: "Protected",
panelValidationTitle: "Validation page",
panelValidationText: "Compare invalid and successful states and explain why Struts2 runs validate() before business output.",
panelValidationLink: "Open validation page",
tagUpload: "multipart",
tagUploadSecure: "Protected",
panelUploadTitle: "Upload metadata page",
panelUploadText: "Keeps the teaching value of file upload while avoiding real disk writes, which is safer for demo environments.",
panelUploadLink: "Open upload page",
tagPortal: "Trace the path",
tagPortalTrace: "Back to entry",
panelPortalTitle: "Return to the portal",
panelPortalText: "Go back and explain the full route from public entry to login action, session write, and interceptor protection.",
panelPortalLink: "Back to portal",
flowEyebrow: "Auth flow",
flowTitle: "The Struts2 login path you just completed",
flowStep1Title: "1. Form submits to the login action",
flowStep1Text: "Username and password reach the action first; validation failure returns to the login page.",
flowStep2Title: "2. The action writes into session",
flowStep2Text: "After success, user, role, and login time are stored in session.",
flowStep3Title: "3. The interceptor protects page actions",
flowStep3Text: "The user form, validation page, upload page, and dashboard all pass through AuthInterceptor first.",
flowStep4Title: "4. Unauthenticated access goes back to login",
flowStep4Text: "If no session exists, secure actions stop immediately and redirect to the login entry."
}
}
},
render: function (ui) {
const roleNode = document.getElementById("roleValue");
if (roleNode && roleNode.dataset.roleCode === "admin") {
roleNode.textContent = ui.language === "zh" ? "演示管理员" : "Demo administrator";
}
}
});
</script>
</body>
</html>