Files
struts2-demo/web/WEB-INF/views/index.jsp
2026-03-24 17:07:40 +08:00

287 lines
12 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: #eff2f5;
--panel: rgba(255,255,255,0.97);
--line: #d4d9e6;
--text: #1f2b3d;
--muted: #52607a;
--brand: #1464c7;
--accent: #0d9488;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
font-family: "Microsoft YaHei", "Segoe UI", sans-serif;
background: linear-gradient(135deg, #e3ebff 0%, #f1f5f9 55%, #effaf5 100%);
color: var(--text);
}
.shell {
width: min(1200px, 100%);
margin: 0 auto;
padding: 24px;
}
.card {
background: var(--panel);
border-radius: 26px;
padding: 28px;
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;
color: var(--accent);
margin-bottom: 6px;
font-weight: 700;
}
h1, h2, h3 {
margin: 8px 0 12px;
font-weight: 700;
}
p {
margin: 0;
color: var(--muted);
line-height: 1.7;
}
.hero-card {
display: flex;
flex-direction: column;
gap: 12px;
}
.hero-actions {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.btn, .btn-soft, .btn-ghost {
padding: 12px 18px;
border-radius: 999px;
font-weight: 700;
border: none;
cursor: pointer;
}
.btn {
background: linear-gradient(135deg, var(--brand), #3d6fe7);
color: white;
}
.btn-soft {
background: #f4f7ff;
color: var(--brand);
border: 1px solid var(--line);
}
.btn-ghost {
border: 1px solid var(--line);
background: transparent;
color: var(--text);
}
.lang-warning {
font-size: 14px;
color: var(--brand);
}
.portal-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
gap: 16px;
margin-top: 16px;
}
.portal-card {
border: 1px solid var(--line);
border-radius: 18px;
background: rgba(255,255,255,0.85);
padding: 16px;
display: flex;
flex-direction: column;
gap: 10px;
}
.portal-links {
display: flex;
gap: 6px;
flex-wrap: wrap;
}
.portal-links .link-btn {
padding: 8px 12px;
border-radius: 12px;
border: 1px solid var(--line);
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);
border-radius: 18px;
padding: 16px;
background: rgba(255,255,255,0.95);
min-height: 150px;
}
.chain-pill {
display: inline-flex;
border-radius: 999px;
padding: 4px 10px;
border: 1px solid var(--line);
background: #f4f7ff;
margin-right: 6px;
font-size: 12px;
}
.session-note {
font-size: 14px;
color: var(--muted);
}
</style>
</head>
<body>
<div class="shell">
<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>
<button class="btn-ghost" type="button" id="languageBtnHero">EN</button>
</div>
<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>
<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>
</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>
</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>
</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>
</article>
</div>
</section>
<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>
<script src="../assets/struts-lab.js"></script>
<script>
strutsLab.mount({
buttonId: "languageBtnHero",
messages: {
zh: {
pageTitle: "Struts2 学习门户",
text: {
heroEyebrow: "Struts2 学习门户",
heroTitle: "登录之后才能继续学习",
heroDesc: "所有入口都由 Session + AuthInterceptor 保护,未登录将自动跳回登录页;登录成功后会先回到这个统一门户。",
heroWarning: "当前门户本身也在登录保护之下,适合用来总览各实验模块。",
heroActionPrimary: "进入仪表盘",
heroActionSecondary: "退出登录",
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: {
pageTitle: "Struts2 Learning Portal",
text: {
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.",
heroActionPrimary: "Go to dashboard",
heroActionSecondary: "Log out",
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>