Files
front-end-example/06-typescript/src/main.ts
charlie 3435848495 feat: add Vue2 exercises for dynamic styles, lifecycle methods, component communication, and course management dashboard
- Implement dynamic styles and event handling in Vue2 with a card component.
- Create lifecycle methods exercise to simulate async data loading and instance destruction.
- Develop a component communication exercise with props, events, and slots.
- Build a comprehensive course management dashboard with filtering, statistics, and component interactions.
2026-03-23 10:09:29 +08:00

113 lines
3.2 KiB
TypeScript

import "./style.css";
import { lessons } from "./lessons";
function getRequiredElement<T extends Element>(selector: string, parent: ParentNode = document): T {
const element = parent.querySelector<T>(selector);
if (!element) {
throw new Error(`Required element was not found: ${selector}`);
}
return element;
}
const app = getRequiredElement<HTMLDivElement>("#app");
app.innerHTML = `
<div class="shell">
<aside class="sidebar">
<div class="brand">
<p class="eyebrow">Vite + TypeScript</p>
<h1>TypeScript Learning Lab</h1>
<p class="intro">
保留原来的 8 个练习目录,同时给这一章补上一个可直接运行的学习面板。
</p>
</div>
<nav class="lesson-nav" aria-label="TypeScript lessons"></nav>
</aside>
<main class="content">
<section class="hero card">
<p class="badge">06-typescript</p>
<h2 id="lesson-title"></h2>
<p id="lesson-focus" class="focus"></p>
<p id="lesson-summary" class="summary"></p>
</section>
<section >
<article class="card">
<div class="card-head">
<h3>知识点</h3>
</div>
<ul id="lesson-points" class="point-list"></ul>
</article>
</section>
<section >
<article class="card code-card">
<div class="card-head">
<h3>Starter</h3>
</div>
<pre><code id="starter-code"></code></pre>
</article>
</section>
</main>
</div>
`;
const nav = getRequiredElement<HTMLElement>(".lesson-nav");
const title = getRequiredElement<HTMLElement>("#lesson-title");
const focus = getRequiredElement<HTMLElement>("#lesson-focus");
const summary = getRequiredElement<HTMLElement>("#lesson-summary");
const pointList = getRequiredElement<HTMLElement>("#lesson-points");
const starterCode = getRequiredElement<HTMLElement>("#starter-code");
let activeLessonId = lessons[0]?.id ?? "";
function renderLessonList(): void {
nav.innerHTML = lessons
.map((lesson) => {
const isActive = lesson.id === activeLessonId;
return `
<button
class="lesson-link ${isActive ? "is-active" : ""}"
type="button"
data-id="${lesson.id}"
>
<span class="lesson-index">${lesson.id}</span>
<span class="lesson-meta">
<strong>${lesson.title}</strong>
<small>${lesson.focus}</small>
</span>
</button>
`;
})
.join("");
}
function renderActiveLesson(): void {
const lesson = lessons.find((item) => item.id === activeLessonId);
if (!lesson) {
return;
}
title.textContent = `${lesson.id}. ${lesson.title}`;
focus.textContent = lesson.focus;
summary.textContent = lesson.summary;
pointList.innerHTML = lesson.keyPoints.map((item) => `<li>${item}</li>`).join("");
starterCode.textContent = lesson.starterCode;
renderLessonList();
}
nav.addEventListener("click", (event) => {
const target = event.target as HTMLElement;
const button = target.closest<HTMLButtonElement>("[data-id]");
if (!button) {
return;
}
activeLessonId = button.dataset.id ?? activeLessonId;
renderActiveLesson();
});
renderActiveLesson();