Files
struts2-demo/web/WEB-INF/views/index.jsp

287 lines
12 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">
2026-03-24 16:00:44 +08:00
<title>Struts2 学习门户</title>
<style>
:root {
2026-03-24 16:00:44 +08:00
--bg: #eff2f5;
--panel: rgba(255,255,255,0.97);
--line: #d4d9e6;
--text: #1f2b3d;
--muted: #52607a;
--brand: #1464c7;
2026-03-24 16:00:44 +08:00
--accent: #0d9488;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
2026-03-24 16:00:44 +08:00
font-family: "Microsoft YaHei", "Segoe UI", sans-serif;
background: linear-gradient(135deg, #e3ebff 0%, #f1f5f9 55%, #effaf5 100%);
color: var(--text);
}
2026-03-24 16:00:44 +08:00
.shell {
width: min(1200px, 100%);
margin: 0 auto;
padding: 24px;
}
2026-03-24 16:00:44 +08:00
.card {
background: var(--panel);
border-radius: 26px;
padding: 28px;
2026-03-24 16:00:44 +08:00
box-shadow: 0 25px 50px rgba(15, 23, 42, 0.1);
border: 1px solid var(--line);
margin-bottom: 18px;
}
.eyebrow {
font-size: 12px;
letter-spacing: 0.12em;
text-transform: uppercase;
2026-03-24 16:00:44 +08:00
color: var(--accent);
margin-bottom: 6px;
font-weight: 700;
}
2026-03-24 16:00:44 +08:00
h1, h2, h3 {
margin: 8px 0 12px;
font-weight: 700;
}
2026-03-24 16:00:44 +08:00
p {
margin: 0;
color: var(--muted);
2026-03-24 16:00:44 +08:00
line-height: 1.7;
}
.hero-card {
display: flex;
flex-direction: column;
gap: 12px;
}
.hero-actions {
display: flex;
2026-03-24 16:00:44 +08:00
gap: 12px;
flex-wrap: wrap;
}
.btn, .btn-soft, .btn-ghost {
padding: 12px 18px;
2026-03-24 16:00:44 +08:00
border-radius: 999px;
font-weight: 700;
2026-03-24 16:00:44 +08:00
border: none;
cursor: pointer;
}
2026-03-24 16:00:44 +08:00
.btn {
background: linear-gradient(135deg, var(--brand), #3d6fe7);
color: white;
}
2026-03-24 16:00:44 +08:00
.btn-soft {
background: #f4f7ff;
color: var(--brand);
border: 1px solid var(--line);
}
2026-03-24 16:00:44 +08:00
.btn-ghost {
border: 1px solid var(--line);
background: transparent;
color: var(--text);
}
2026-03-24 16:00:44 +08:00
.lang-warning {
font-size: 14px;
color: var(--brand);
}
2026-03-24 16:00:44 +08:00
.portal-grid {
display: grid;
2026-03-24 16:00:44 +08:00
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
gap: 16px;
2026-03-24 16:00:44 +08:00
margin-top: 16px;
}
2026-03-24 16:00:44 +08:00
.portal-card {
border: 1px solid var(--line);
border-radius: 18px;
2026-03-24 16:00:44 +08:00
background: rgba(255,255,255,0.85);
padding: 16px;
display: flex;
flex-direction: column;
gap: 10px;
}
2026-03-24 16:00:44 +08:00
.portal-links {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
2026-03-24 16:00:44 +08:00
.portal-links .link-btn {
padding: 8px 12px;
border-radius: 12px;
border: 1px solid var(--line);
2026-03-24 16:00:44 +08:00
background: #fff;
color: var(--text);
text-decoration: none;
font-weight: 600;
}
.insight-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 16px;
margin-top: 16px;
}
.insight-card {
border: 1px solid var(--line);
2026-03-24 16:00:44 +08:00
border-radius: 18px;
padding: 16px;
background: rgba(255,255,255,0.95);
min-height: 150px;
}
2026-03-24 16:00:44 +08:00
.chain-pill {
display: inline-flex;
border-radius: 999px;
padding: 4px 10px;
border: 1px solid var(--line);
background: #f4f7ff;
margin-right: 6px;
font-size: 12px;
}
2026-03-24 16:00:44 +08:00
.session-note {
font-size: 14px;
color: var(--muted);
}
</style>
</head>
<body>
<div class="shell">
2026-03-24 16:00:44 +08:00
<section class="card hero-card">
<div class="eyebrow" id="heroEyebrow">Struts2 学习门户</div>
<h1 id="heroTitle">登录之后才能继续学习</h1>
<p id="heroDesc">所有课程模块(首页/实验/JSON 等)都由 AuthInterceptor 保护,未登录访问会自动跳转。请先登录再回来。</p>
<div class="lang-warning" id="heroWarning">登录后才能看到完整课程导航与实验入口。</div>
<div class="hero-actions">
<a class="btn" href="dashboard.action" id="heroActionPrimary">进入仪表盘</a>
<a class="btn-soft" href="logout.action" id="heroActionSecondary">退出登录</a>
2026-03-24 16:00:44 +08:00
<button class="btn-ghost" type="button" id="languageBtnHero">EN</button>
</div>
2026-03-24 16:00:44 +08:00
<div class="session-note" id="heroSessionNote">
<s:if test="#session.demoUser != null">
当前:<s:property value="#session.demoUser"/><s:property value="#session.demoRole"/>
</s:if>
<s:else>
当前尚未登录,请先完成登录再浏览实验。
</s:else>
</div>
</section>
2026-03-24 16:00:44 +08:00
<section class="card portal-section">
<div class="eyebrow" id="portalEyebrow">学习模块导航</div>
<h2 id="portalTitle">分步骤理解 Struts2</h2>
<p id="portalDesc">每个模块都通过 `.action` 路由触达,由统一的 Session 鉴权链路保护。</p>
<div class="portal-grid">
<article class="portal-card">
<h3 id="portalCard1Title">请求生命周期</h3>
<p id="portalCard1Desc">Dispatcher → Action → Interceptor → Result严格按链路走向。</p>
<div>
<span class="chain-pill">Dispatcher</span>
<span class="chain-pill">Action</span>
<span class="chain-pill">Interceptors</span>
<span class="chain-pill">Result</span>
</div>
2026-03-24 16:00:44 +08:00
</article>
<article class="portal-card">
<h3 id="portalCard2Title">登录保护</h3>
<p id="portalCard2Desc">`LoginAction` 写入 Session`AuthInterceptor` 阻止任何未登录访问。</p>
<div class="portal-links">
<a class="link-btn" href="loginPage.action">登录页</a>
<a class="link-btn" href="dashboard.action">仪表盘</a>
</div>
2026-03-24 16:00:44 +08:00
</article>
<article class="portal-card">
<h3 id="portalCard3Title">表单与校验</h3>
<p id="portalCard3Desc">表单字段直接绑定到 ActionValidationAction 负责完整校验流程。</p>
<div class="portal-links">
<a class="link-btn" href="userFormPage.action">表单</a>
<a class="link-btn" href="validationPage.action">校验</a>
</div>
2026-03-24 16:00:44 +08:00
</article>
<article class="portal-card">
<h3 id="portalCard4Title">AJAX / REST 对照</h3>
<p id="portalCard4Desc">对比 `ajax.action` 与 `api/users.action` 的 JSON 输出。</p>
<div class="portal-links">
<a class="link-btn" href="ajax.action">AJAX</a>
<a class="link-btn" href="api/users.action">REST</a>
</div>
2026-03-24 16:00:44 +08:00
</article>
</div>
</section>
2026-03-24 16:00:44 +08:00
<section class="card insight-section">
<div class="eyebrow">Insight</div>
<h2>安全学习链路速览</h2>
<div class="insight-grid">
<article class="insight-card">
<h3>登录才能使用</h3>
<p>未登录访问 `dashboard.action` 等任何端点都会被 AuthInterceptor 重定向至 `loginPage.action`。</p>
</article>
<article class="insight-card">
<h3>表单绑定观察</h3>
<p>`UserAction` 按字段绑定,`ValidationAction` 造成的字段错误会带回当前 JSP。</p>
</article>
<article class="insight-card">
<h3>AJAX vs REST</h3>
<p>`ajax.action` 返回对话式 JSON`api/users.action` 呈现 REST 样式,便于教学对照。</p>
</article>
</div>
</section>
</div>
2026-03-24 16:00:44 +08:00
<script src="../assets/struts-lab.js"></script>
<script>
2026-03-24 16:00:44 +08:00
strutsLab.mount({
buttonId: "languageBtnHero",
messages: {
zh: {
2026-03-24 16:00:44 +08:00
pageTitle: "Struts2 学习门户",
text: {
2026-03-24 16:00:44 +08:00
heroEyebrow: "Struts2 学习门户",
heroTitle: "登录之后才能继续学习",
heroDesc: "所有入口都由 Session + AuthInterceptor 保护,未登录将自动跳回登录页;登录成功后会先回到这个统一门户。",
heroWarning: "当前门户本身也在登录保护之下,适合用来总览各实验模块。",
2026-03-24 16:00:44 +08:00
heroActionPrimary: "进入仪表盘",
heroActionSecondary: "退出登录",
2026-03-24 16:00:44 +08:00
portalEyebrow: "学习模块导航",
portalTitle: "分步骤理解 Struts2",
portalDesc: "每个 `.action` 路径都在登录后才能访问Session 鉴权串联整个体验。",
portalCard1Title: "请求生命周期",
portalCard1Desc: "Dispatcher → Action → Interceptor → Result典型执行路径。",
portalCard2Title: "登录保护",
portalCard2Desc: "`LoginAction` 写入 Session`AuthInterceptor` 拦截未登录。",
portalCard3Title: "表单与校验",
portalCard3Desc: "Action 字段绑定 + ValidationAction 校验,成功后进入汇总。",
portalCard4Title: "AJAX / REST 对照",
portalCard4Desc: "对比 `ajax.action`AJAX与 `api/users.action`REST。"
}
},
en: {
2026-03-24 16:00:44 +08:00
pageTitle: "Struts2 Learning Portal",
text: {
2026-03-24 16:00:44 +08:00
heroEyebrow: "Struts2 Learning Portal",
heroTitle: "Login before you explore",
heroDesc: "All modules are protected by Session + AuthInterceptor. After login, this protected portal becomes the unified entry.",
heroWarning: "The portal itself is protected and serves as the overview for every lab module.",
2026-03-24 16:00:44 +08:00
heroActionPrimary: "Go to dashboard",
heroActionSecondary: "Log out",
2026-03-24 16:00:44 +08:00
portalEyebrow: "Module guide",
portalTitle: "Step-by-step Struts2",
portalDesc: "Every `.action` route sits behind login; the session chain secures the experience.",
portalCard1Title: "Request lifecycle",
portalCard1Desc: "Dispatcher → Action → Interceptor → Result shows the execution path.",
portalCard2Title: "Login protection",
portalCard2Desc: "`LoginAction` writes Session, `AuthInterceptor` blocks unauthenticated hits.",
portalCard3Title: "Forms & validation",
portalCard3Desc: "Field binding plus `ValidationAction` determine success/failure views.",
portalCard4Title: "AJAX / REST contrast",
portalCard4Desc: "Compare `ajax.action` (AJAX) with `api/users.action` (REST) outputs."
}
}
}
});
</script>
</body>
</html>