初始化开发环境4
下面用一个「待办事项 + 用户卡片」的完整小项目,把 HTML5、Sass、TypeScript、Vue3 的核心知识点串起来复习。你可以直接在本机新建文件夹,按下面的结构创建文件,用 VS Code 打开即可运行(Vue3 部分建议用 Vite 创建项目,后面会说明)。
npm create vite@latest . -- --template vue-ts
一、项目结构
my-app/
├── index.html # HTML5 页面
├── styles/
│ ├── _variables.scss # Sass 变量
│ ├── _mixins.scss # Sass 混合
│ └── main.scss # Sass 主文件
├── src/
│ ├── types.ts # TS 类型定义
│ ├── api.ts # TS 模拟接口
│ ├── store.ts # TS 状态逻辑
│ ├── components/
│ │ ├── UserCard.vue
│ │ └── TodoList.vue
│ └── App.vue
└── main.ts
Vue3 项目建议用
npm create vite@latest my-app -- --template vue-ts创建,然后把下面的代码替换进去。HTML5 + Sass 部分可以独立运行,方便你先复习基础。
二、HTML5 知识点复习
index.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="HTML5 + Sass + TS + Vue3 综合示例" />
<title>综合复习示例</title>
</head>
<body>
<!-- 语义化标签:header / nav / main / section / article / aside / footer -->
<header class="site-header">
<h1>我的待办</h1>
<nav>
<ul>
<li><a href="#todo">待办</a></li>
<li><a href="#user">用户</a></li>
</ul>
</nav>
</header>
<main>
<!-- section 用于主题分组 -->
<section id="todo">
<h2>待办列表</h2>
<!-- 表单元素:input / button / label -->
<form id="todo-form">
<label for="todo-input">新任务:</label>
<input
id="todo-input"
type="text"
placeholder="输入任务内容"
required
autocomplete="off"
/>
<button type="submit">添加</button>
</form>
<!-- 动态列表容器 -->
<ul id="todo-list"></ul>
</section>
<section id="user">
<h2>用户信息</h2>
<!-- 自定义数据属性 data-* -->
<div id="user-card" data-user-id="1"></div>
</section>
</main>
<footer class="site-footer">
<p>© 2025 综合复习示例</p>
</footer>
<!-- 引入编译后的 CSS(由 Sass 编译生成) -->
<link rel="stylesheet" href="./styles/main.css" />
<!-- 引入 TS 编译后的 JS -->
<script type="module" src="./dist/main.js"></script>
</body>
</html>
HTML5 关键点:
<!DOCTYPE html>声明- 语义化标签:
header、nav、main、section、article、aside、footer - 表单增强:
type="text"、required、placeholder、autocomplete - 自定义数据属性:
data-user-id - 多媒体(本例未用,但可扩展):
<video>、<audio>、<canvas> - 本地存储:
localStorage、sessionStorage(下面 TS 部分会用到)
三、Sass 知识点复习
1. _variables.scss — 变量与嵌套
// 变量:颜色、间距、字体
$primary-color: #42b883;
$danger-color: #e74c3c;
$text-color: #2c3e50;
$bg-color: #f9f9f9;
$spacing-sm: 8px;
$spacing-md: 16px;
$spacing-lg: 24px;
$radius: 8px;
$font-stack: 'Helvetica Neue', Arial, sans-serif;
2. _mixins.scss — 混合与函数
@use 'variables' as *;
// 混合:可复用样式块
@mixin flex-center($direction: row) {
display: flex;
flex-direction: $direction;
align-items: center;
justify-content: center;
}
// 带参数的混合
@mixin card($padding: $spacing-md) {
background: #fff;
border-radius: $radius;
padding: $padding;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
// 函数:返回计算值
@function rem($px) {
@return calc($px / 16) * 1rem;
}
3. main.scss — 嵌套、继承、运算、导入
@use 'variables' as *;
@use 'mixins' as *;
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: $font-stack;
color: $text-color;
background: $bg-color;
line-height: 1.6;
}
// 嵌套写法
.site-header {
@include flex-center(column);
padding: $spacing-lg;
background: $primary-color;
color: #fff;
h1 {
font-size: rem(24);
}
nav ul {
list-style: none;
display: flex;
gap: $spacing-md;
// & 表示父选择器
li a {
color: #fff;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}
main {
max-width: 800px;
margin: 0 auto;
padding: $spacing-lg;
section {
margin-bottom: $spacing-lg;
h2 {
margin-bottom: $spacing-md;
color: $primary-color;
}
}
}
#todo-form {
display: flex;
gap: $spacing-sm;
margin-bottom: $spacing-md;
input {
flex: 1;
padding: $spacing-sm;
border: 1px solid #ddd;
border-radius: $radius;
&:focus {
outline: none;
border-color: $primary-color;
}
}
button {
padding: $spacing-sm $spacing-md;
background: $primary-color;
color: #fff;
border: none;
border-radius: $radius;
cursor: pointer;
&:hover {
background: darken($primary-color, 10%);
}
}
}
#todo-list {
list-style: none;
li {
@include card();
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $spacing-sm;
&.done {
text-decoration: line-through;
opacity: 0.6;
}
button {
background: $danger-color;
color: #fff;
border: none;
padding: 4px 8px;
border-radius: $radius;
cursor: pointer;
}
}
}
.site-footer {
text-align: center;
padding: $spacing-md;
font-size: rem(14);
color: #888;
}
Sass 关键点:
- 变量
$var - 嵌套选择器、
&父选择器 - 混合
@mixin/@include,带默认参数 - 函数
@function @use模块化(替代旧版@import)- 颜色函数
darken() - 运算与
calc()结合
编译命令(安装 sass 后):
npx sass styles/main.scss styles/main.css --watch
四、TypeScript 知识点复习
1. types.ts — 接口、类型别名、泛型、枚举
// 接口:定义对象结构
export interface Todo {
id: number;
text: string;
done: boolean;
createdAt: Date;
}
export interface User {
id: number;
name: string;
email: string;
avatar?: string; // 可选属性
}
// 类型别名
export type TodoFilter = 'all' | 'active' | 'done';
// 枚举
export enum Status {
Idle = 'idle',
Loading = 'loading',
Success = 'success',
Error = 'error',
}
// 泛型函数
export function first<T>(arr: T[]): T | undefined {
return arr[0];
}
// 泛型接口
export interface ApiResponse<T> {
code: number;
data: T;
message: string;
}
2. api.ts — 异步、Promise、泛型、模拟接口
import type { Todo, User, ApiResponse } from './types';
// 模拟异步请求
export function fetchTodos(): Promise<ApiResponse<Todo[]>> {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
code: 200,
data: [
{ id: 1, text: '学习 HTML5', done: true, createdAt: new Date() },
{ id: 2, text: '学习 Sass', done: false, createdAt: new Date() },
{ id: 3, text: '学习 TypeScript', done: false, createdAt: new Date() },
],
message: 'ok',
});
}, 300);
});
}
export async function fetchUser(id: number): Promise<User> {
// 模拟网络延迟
await new Promise((r) => setTimeout(r, 200));
return { id, name: '张三', email: 'zhangsan@example.com' };
}
3. store.ts — 类、泛型、localStorage、类型守卫
import type { Todo, TodoFilter } from './types';
// 类 + 泛型 + 访问修饰符
export class TodoStore {
private todos: Todo[] = [];
private filter: TodoFilter = 'all';
private nextId = 1;
constructor(initial: Todo[] = []) {
this.todos = initial;
this.nextId = initial.length ? Math.max(...initial.map((t) => t.id)) + 1 : 1;
this.loadFromStorage();
}
add(text: string): void {
if (!text.trim()) return;
this.todos.push({
id: this.nextId++,
text: text.trim(),
done: false,
createdAt: new Date(),
});
this.saveToStorage();
}
remove(id: number): void {
this.todos = this.todos.filter((t) => t.id !== id);
this.saveToStorage();
}
toggle(id: number): void {
const todo = this.todos.find((t) => t.id === id);
if (todo) {
todo.done = !todo.done;
this.saveToStorage();
}
}
setFilter(filter: TodoFilter): void {
this.filter = filter;
}
// getter
get visibleTodos(): Todo[] {
switch (this.filter) {
case 'active':
return this.todos.filter((t) => !t.done);
case 'done':
return this.todos.filter((t) => t.done);
default:
return this.todos;
}
}
// 类型守卫
private isTodoArray(value: unknown): value is Todo[] {
return Array.isArray(value);
}
private saveToStorage(): void {
localStorage.setItem('todos', JSON.stringify(this.todos));
}
private loadFromStorage(): void {
const raw = localStorage.getItem('todos');
if (!raw) return;
try {
const parsed: unknown = JSON.parse(raw);
if (this.isTodoArray(parsed)) {
this.todos = parsed;
}
} catch {
console.warn('解析本地存储失败');
}
}
}
TypeScript 关键点:
interface/type/enum- 可选属性
? - 泛型函数、泛型接口
Promise、async/await- 类、
private/public、getter - 类型守卫
value is Todo[] unknown与any的区别- 模块导入导出
import type
五、Vue3 知识点复习
1. App.vue — 组合式 API、ref、reactive、computed、watch、生命周期
<script setup lang="ts">
import { ref, reactive, computed, watch, onMounted } from 'vue';
import UserCard from './components/UserCard.vue';
import TodoList from './components/TodoList.vue';
import { fetchUser } from './api';
import type { User } from './types';
// ref:基本类型响应式
const title = ref('我的待办');
const count = ref(0);
// reactive:对象响应式
const state = reactive({
loading: false,
user: null as User | null,
});
// computed:计算属性
const doubleCount = computed(() => count.value * 2);
// watch:侦听器
watch(count, (newVal, oldVal) => {
console.log(`count 从 ${oldVal} 变为 ${newVal}`);
});
// 生命周期
onMounted(async () => {
state.loading = true;
try {
state.user = await fetchUser(1);
} finally {
state.loading = false;
}
});
function increment() {
count.value++;
}
</script>
<template>
<div class="app">
<h1>{{ title }}</h1>
<p>计数:{{ count }},双倍:{{ doubleCount }}</p>
<button @click="increment">+1</button>
<p v-if="state.loading">加载中...</p>
<UserCard v-else-if="state.user" :user="state.user" />
<TodoList />
</div>
</template>
<style scoped lang="scss">
.app {
padding: 16px;
h1 {
color: #42b883;
}
}
</style>
2. components/UserCard.vue — props、defineProps、类型化 props
<script setup lang="ts">
import type { User } from '../types';
// 类型化 props
const props = defineProps<{
user: User;
}>();
// 解构(Vue 3.5+ 可直接解构,低版本用 props.user)
const { user } = props;
</script>
<template>
<div class="user-card">
<img v-if="user.avatar" :src="user.avatar" :alt="user.name" />
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
</template>
<style scoped lang="scss">
.user-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
margin: 16px 0;
img {
width: 48px;
height: 48px;
border-radius: 50%;
}
}
</style>
3. components/TodoList.vue — v-model、v-for、v-if、事件、emit、插槽
<script setup lang="ts">
import { ref, computed } from 'vue';
import { TodoStore } from '../store';
import type { TodoFilter } from '../types';
const store = new TodoStore();
const newText = ref('');
const filter = ref<TodoFilter>('all');
// 用 ref 触发响应式更新(store 内部是普通数组)
const version = ref(0);
const todos = computed(() => {
version.value; // 依赖收集
store.setFilter(filter.value);
return store.visibleTodos;
});
function addTodo() {
store.add(newText.value);
newText.value = '';
version.value++;
}
function removeTodo(id: number) {
store.remove(id);
version.value++;
}
function toggleTodo(id: number) {
store.toggle(id);
version.value++;
}
</script>
<template>
<section class="todo-list">
<h2>待办列表</h2>
<form @submit.prevent="addTodo">
<input v-model="newText" placeholder="输入任务" />
<button type="submit">添加</button>
</form>
<!-- 过滤器 -->
<div class="filters">
<button
v-for="f in (['all', 'active', 'done'] as TodoFilter[])"
:key="f"
:class="{ active: filter === f }"
@click="filter = f"
>
{{ f }}
</button>
</div>
<!-- 列表渲染 -->
<ul>
<li v-for="todo in todos" :key="todo.id" :class="{ done: todo.done }">
<input
type="checkbox"
:checked="todo.done"
@change="toggleTodo(todo.id)"
/>
<span>{{ todo.text }}</span>
<button @click="removeTodo(todo.id)">删除</button>
</li>
</ul>
<p v-if="todos.length === 0">暂无任务</p>
</section>
</template>
<style scoped lang="scss">
.todo-list {
.filters {
display: flex;
gap: 8px;
margin: 8px 0;
button.active {
background: #42b883;
color: #fff;
}
}
li.done span {
text-decoration: line-through;
opacity: 0.6;
}
}
</style>
4. main.ts — 应用入口
import { createApp } from 'vue';
import App from './App.vue';
import './styles/main.scss'; // 引入全局 Sass
createApp(App).mount('#app');
Vue3 关键点:
<script setup>语法糖ref/reactive响应式computed/watch- 生命周期
onMounted defineProps类型化 propsv-model双向绑定v-for/v-if/v-else-if- 事件
@click、@submit.prevent - 动态 class
:class <style scoped lang="scss">createApp应用实例
六、运行方式
方式一:纯 HTML + Sass + TS(不依赖 Vue)
# 1. 安装依赖
npm init -y
npm install -D sass typescript
# 2. 编译 Sass
npx sass styles/main.scss styles/main.css --watch
# 3. 编译 TS
npx tsc src/*.ts --outDir dist --module esnext --target es2020
# 4. 用 Live Server 打开 index.html
方式二:Vite + Vue3 + TS + Sass(推荐)
npm create vite@latest my-app -- --template vue-ts
cd my-app
npm install
npm install -D sass
npm run dev
然后把上面的 App.vue、components/、types.ts、api.ts、store.ts 按结构放入 src/,在 main.ts 中引入 Sass 即可。
七、知识点总览表
| 技术 | 覆盖知识点 |
|---|---|
| HTML5 | DOCTYPE、语义化标签、表单增强、data-*、localStorage |
| Sass | 变量、嵌套、&、mixin、function、@use、颜色函数、运算 |
| TypeScript | interface、type、enum、泛型、Promise、async/await、类、访问修饰符、类型守卫、unknown |
| Vue3 | script setup、ref、reactive、computed、watch、onMounted、defineProps、v-model、v-for、v-if、事件、动态 class、scoped style |
这套示例把四者串成了一个可运行的小项目,你可以边跑边改,逐个验证每个知识点。如果某个部分想深入(比如 Vue3 的 provide/inject、Sass 的 @each、TS 的装饰器),可以在这个骨架上继续扩展。

浙公网安备 33010602011771号