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

342 lines
17 KiB
Plaintext
Raw Normal View History

<%@ 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>