初始化开发环境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>&copy; 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 类型化 props
  • v-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 的装饰器),可以在这个骨架上继续扩展。

posted @ 2026-10-10 15:01  华腾智算  阅读(4)  评论(0)    收藏  举报
https://damo.alibaba.com/ https://tianchi.aliyun.com/course?spm=5176.21206777.J_3941670930.5.87dc17c9BZNvLL