342 lines
17 KiB
Plaintext
342 lines
17 KiB
Plaintext
<%@ 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>
|