feat: add struts learning tracker

This commit is contained in:
Codex
2026-03-19 13:53:49 +08:00
parent fba7b0497f
commit 5466fb1ffd
3 changed files with 146 additions and 0 deletions

View File

@@ -199,6 +199,28 @@
color: var(--muted); color: var(--muted);
text-align: center; text-align: center;
} }
.tracker {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 14px;
}
.tracker-item {
display: flex;
gap: 10px;
align-items: flex-start;
padding: 12px 14px;
border-radius: 16px;
border: 1px solid var(--line);
background: rgba(255,255,255,0.5);
}
.tracker-item input {
margin-top: 3px;
}
.tracker-item strong {
display: block;
margin-bottom: 4px;
}
@media (max-width: 1100px) { @media (max-width: 1100px) {
.layout { grid-template-columns: 1fr; } .layout { grid-template-columns: 1fr; }
.grid, .pipeline, .metrics { grid-template-columns: 1fr 1fr; } .grid, .pipeline, .metrics { grid-template-columns: 1fr 1fr; }
@@ -229,6 +251,8 @@
<div class="metric"><span>Guide pages</span><strong>4</strong></div> <div class="metric"><span>Guide pages</span><strong>4</strong></div>
<div class="metric"><span>Demo forms</span><strong>3</strong></div> <div class="metric"><span>Demo forms</span><strong>3</strong></div>
<div class="metric"><span>JSON endpoints</span><strong>2</strong></div> <div class="metric"><span>JSON endpoints</span><strong>2</strong></div>
<div class="metric"><span>Core lab track</span><strong>4</strong></div>
<div class="metric"><span>Completed labs</span><strong id="completedLabs">0</strong></div>
</div> </div>
</section> </section>
@@ -257,9 +281,67 @@
<a class="link-btn" href="api/users">JSON endpoint</a> <a class="link-btn" href="api/users">JSON endpoint</a>
</div> </div>
</section> </section>
<section class="card">
<div class="eyebrow">Lab tracker</div>
<h2>Mark the demos you have finished</h2>
<div class="tracker">
<label class="tracker-item">
<input type="checkbox" data-track="hello">
<span>
<strong>Hello action</strong>
See the smallest request -> action -> result path.
</span>
</label>
<label class="tracker-item">
<input type="checkbox" data-track="login">
<span>
<strong>Login flow</strong>
Follow validation, action errors, and success routing.
</span>
</label>
<label class="tracker-item">
<input type="checkbox" data-track="validation">
<span>
<strong>Validation flow</strong>
Compare invalid and valid submissions.
</span>
</label>
<label class="tracker-item">
<input type="checkbox" data-track="json">
<span>
<strong>JSON and AJAX</strong>
Finish by explaining how Struts can return API-style payloads.
</span>
</label>
</div>
</section>
</aside> </aside>
<main class="content"> <main class="content">
<section class="card">
<div class="eyebrow">Request lifecycle</div>
<h2>How to explain Struts2 with one mental model</h2>
<div class="pipeline">
<div class="step">
<strong>1. Request enters</strong>
<p>The browser sends a URL and optional form fields or query params.</p>
</div>
<div class="step">
<strong>2. Action mapping</strong>
<p><code>struts.xml</code> resolves the action name and target class.</p>
</div>
<div class="step">
<strong>3. Parameter binding</strong>
<p>Struts populates action properties before <code>execute()</code> runs.</p>
</div>
<div class="step">
<strong>4. Result routing</strong>
<p>The returned result name decides which JSP or JSON renderer will respond.</p>
</div>
</div>
</section>
<section class="card"> <section class="card">
<div class="eyebrow">Catalog</div> <div class="eyebrow">Catalog</div>
<h2>Interactive demos and guides</h2> <h2>Interactive demos and guides</h2>
@@ -385,6 +467,33 @@
const searchInput = document.getElementById('searchInput'); const searchInput = document.getElementById('searchInput');
const cards = Array.from(document.querySelectorAll('.demo-card')); const cards = Array.from(document.querySelectorAll('.demo-card'));
const emptyState = document.getElementById('emptyState'); const emptyState = document.getElementById('emptyState');
const trackerItems = Array.from(document.querySelectorAll('[data-track]'));
const trackerKey = 'struts_demo_tracker';
function getTrackerState() {
return JSON.parse(localStorage.getItem(trackerKey) || '{}');
}
function renderTracker() {
const state = getTrackerState();
let completed = 0;
trackerItems.forEach((item) => {
item.checked = Boolean(state[item.dataset.track]);
if (item.checked) {
completed += 1;
}
});
document.getElementById('completedLabs').textContent = completed;
}
trackerItems.forEach((item) => {
item.addEventListener('change', () => {
const state = getTrackerState();
state[item.dataset.track] = item.checked;
localStorage.setItem(trackerKey, JSON.stringify(state));
renderTracker();
});
});
searchInput.addEventListener('input', () => { searchInput.addEventListener('input', () => {
const keyword = searchInput.value.trim().toLowerCase(); const keyword = searchInput.value.trim().toLowerCase();
@@ -398,6 +507,8 @@
}); });
emptyState.style.display = visible ? 'none' : 'block'; emptyState.style.display = visible ? 'none' : 'block';
}); });
renderTracker();
</script> </script>
</body> </body>
</html> </html>

View File

@@ -106,6 +106,28 @@
<a class="link-btn" href="../upload/index.jsp">Open upload demo</a> <a class="link-btn" href="../upload/index.jsp">Open upload demo</a>
</div> </div>
</section> </section>
<section class="card">
<div class="eyebrow">What happened</div>
<h2>Map this result back to the Struts stack</h2>
<div class="stats">
<div class="stat">
<span>Action mapping</span>
<strong>login</strong>
<p>The request matched the <code>login</code> action in <code>struts.xml</code>.</p>
</div>
<div class="stat">
<span>Execute result</span>
<strong>SUCCESS</strong>
<p>The action returned the success result instead of redisplaying the form.</p>
</div>
<div class="stat">
<span>Rendered view</span>
<strong>/user/success.jsp</strong>
<p>The result mapping selected this JSP to present the post-action summary.</p>
</div>
</div>
</section>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -81,6 +81,19 @@
<a class="link-btn" href="form.jsp">Try validation again</a> <a class="link-btn" href="form.jsp">Try validation again</a>
<a class="link-btn" href="../index.jsp">Back to portal</a> <a class="link-btn" href="../index.jsp">Back to portal</a>
</div> </div>
<div class="stats">
<div class="stat">
<span>Rule path</span>
<strong>validate()</strong>
The action accepted every field, so execution continued to the success result.
</div>
<div class="stat">
<span>Teaching focus</span>
<strong>Input before logic</strong>
Compare this page with the form error state to explain why validation runs before business output.
</div>
</div>
</div> </div>
</body> </body>
</html> </html>