第八章:实战项目案例

第八章:实战项目案例

8.1 项目一:Todo 应用(Vue 3 + Pinia)

项目初始化

bash 复制代码
npm create vite@latest todo-app -- --template vue
cd todo-app
npm install pinia
npm install -D @vitejs/plugin-vue

项目结构

复制代码
todo-app/
├── src/
│   ├── components/
│   │   ├── TodoList.vue
│   │   ├── TodoItem.vue
│   │   └── TodoForm.vue
│   ├── stores/
│   │   └── todo.js
│   ├── App.vue
│   └── main.js
└── vite.config.js

Store 实现

javascript 复制代码
// src/stores/todo.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useTodoStore = defineStore('todo', () => {
  const todos = ref([
    { id: 1, text: '学习 Vite', completed: false },
    { id: 2, text: '学习 Pinia', completed: false }
  ])
  
  const addTodo = (text) => {
    todos.value.push({
      id: Date.now(),
      text,
      completed: false
    })
  }
  
  const toggleTodo = (id) => {
    const todo = todos.value.find(t => t.id === id)
    if (todo) todo.completed = !todo.completed
  }
  
  const deleteTodo = (id) => {
    todos.value = todos.value.filter(t => t.id !== id)
  }
  
  const completedCount = computed(() => 
    todos.value.filter(t => t.completed).length
  )
  
  const pendingCount = computed(() => 
    todos.value.length - completedCount.value
  )
  
  return { todos, addTodo, toggleTodo, deleteTodo, completedCount, pendingCount }
})

组件实现

vue 复制代码
<!-- src/components/TodoList.vue -->
<template>
  <div class="todo-list">
    <div class="stats">
      <span>总计: {{ todos.length }}</span>
      <span>已完成: {{ completedCount }}</span>
      <span>未完成: {{ pendingCount }}</span>
    </div>
    
    <div v-for="todo in todos" :key="todo.id" class="todo-item">
      <input 
        type="checkbox" 
        :checked="todo.completed"
        @change="toggleTodo(todo.id)"
      />
      <span :class="{ completed: todo.completed }">{{ todo.text }}</span>
      <button @click="deleteTodo(todo.id)">删除</button>
    </div>
  </div>
</template>

<script setup>
import { useTodoStore } from '../stores/todo'
import { storeToRefs } from 'pinia'

const todoStore = useTodoStore()
const { todos, completedCount, pendingCount } = storeToRefs(todoStore)
const { toggleTodo, deleteTodo } = todoStore
</script>

<style scoped>
.todo-list {
  max-width: 500px;
  margin: 0 auto;
}

.stats {
  display: flex;
  gap: 20px;
  margin-bottom: 20px;
  padding: 10px;
  background: #f5f5f5;
  border-radius: 8px;
}

.todo-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px;
  border-bottom: 1px solid #eee;
}

.completed {
  text-decoration: line-through;
  color: #999;
}

button {
  margin-left: auto;
  padding: 4px 12px;
  background: #ff4444;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
</style>

8.2 项目二:博客系统(React + React Router)

项目初始化

bash 复制代码
npm create vite@latest blog -- --template react
cd blog
npm install react-router-dom
npm install -D @vitejs/plugin-react

路由配置

javascript 复制代码
// src/router.jsx
import { createBrowserRouter } from 'react-router-dom'
import Layout from './Layout'
import Home from './pages/Home'
import Post from './pages/Post'
import About from './pages/About'

export const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    children: [
      { index: true, element: <Home /> },
      { path: 'post/:id', element: <Post /> },
      { path: 'about', element: <About /> }
    ]
  }
])

使用 Vite 的 import.meta.glob

javascript 复制代码
// src/pages/Home.jsx
import { useState, useEffect } from 'react'

// 动态导入所有 Markdown 文件
const modules = import.meta.glob('../posts/*.md', { 
  as: 'raw',
  eager: true 
})

function Home() {
  const [posts, setPosts] = useState([])
  
  useEffect(() => {
    const postsData = Object.entries(modules).map(([path, content]) => ({
      id: path.split('/').pop().replace('.md', ''),
      title: extractTitle(content),
      excerpt: content.slice(0, 200) + '...'
    }))
    setPosts(postsData)
  }, [])
  
  function extractTitle(content) {
    const match = content.match(/^# (.+)$/m)
    return match ? match[1] : '无标题'
  }
  
  return (
    <div>
      <h1>我的博客</h1>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.excerpt}</p>
          <a href={`/post/${post.id}`}>阅读更多</a>
        </article>
      ))}
    </div>
  )
}

export default Home

8.3 项目三:组件库开发

项目结构

复制代码
my-ui/
├── src/
│   ├── components/
│   │   ├── Button/
│   │   │   ├── index.js
│   │   │   ├── Button.vue
│   │   │   └── style.css
│   │   └── Input/
│   │       ├── index.js
│   │       └── Input.vue
│   └── index.js
├── vite.config.js
└── package.json

Vite 配置(库模式)

javascript 复制代码
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default defineConfig({
  plugins: [vue()],
  build: {
    lib: {
      entry: path.resolve(__dirname, 'src/index.js'),
      name: 'MyUI',
      formats: ['es', 'umd'],
      fileName: (format) => `my-ui.${format}.js`
    },
    rollupOptions: {
      external: ['vue'],
      output: {
        globals: {
          vue: 'Vue'
        },
        assetFileNames: (assetInfo) => {
          if (assetInfo.name === 'style.css') return 'my-ui.css'
          return assetInfo.name
        }
      }
    }
  },
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "./src/styles/variables.scss";`
      }
    }
  }
})

组件实现

vue 复制代码
<!-- src/components/Button/Button.vue -->
<template>
  <button 
    :class="['btn', `btn-${type}`]"
    :disabled="disabled"
    @click="$emit('click')"
  >
    <slot />
  </button>
</template>

<script setup>
defineProps({
  type: {
    type: String,
    default: 'primary',
    validator: (v) => ['primary', 'success', 'danger'].includes(v)
  },
  disabled: Boolean
})

defineEmits(['click'])
</script>

<style scoped>
.btn {
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
}

.btn-primary {
  background: #42b983;
  color: white;
}

.btn-success {
  background: #67c23a;
  color: white;
}

.btn-danger {
  background: #f56c6c;
  color: white;
}

.btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}
</style>

8.4 项目四:PWA 应用

配置 PWA

bash 复制代码
npm install -D vite-plugin-pwa
javascript 复制代码
// vite.config.js
import { VitePWA } from 'vite-plugin-pwa'

export default defineConfig({
  plugins: [
    VitePWA({
      registerType: 'autoUpdate',
      includeAssets: ['favicon.ico', 'apple-touch-icon.png'],
      manifest: {
        name: 'My PWA App',
        short_name: 'PWA',
        description: 'A Vite PWA App',
        theme_color: '#ffffff',
        icons: [
          {
            src: 'pwa-192x192.png',
            sizes: '192x192',
            type: 'image/png'
          },
          {
            src: 'pwa-512x512.png',
            sizes: '512x512',
            type: 'image/png'
          }
        ]
      },
      workbox: {
        globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
        runtimeCaching: [
          {
            urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
            handler: 'CacheFirst',
            options: {
              cacheName: 'google-fonts-cache',
              expiration: {
                maxEntries: 10,
                maxAgeSeconds: 60 * 60 * 24 * 365
              }
            }
          }
        ]
      }
    })
  ]
})

更新通知

javascript 复制代码
// src/main.js
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.addEventListener('controllerchange', () => {
    window.location.reload()
  })
}

8.5 项目五:Chrome 扩展

项目结构

复制代码
chrome-extension/
├── public/
│   ├── manifest.json
│   └── icon.png
├── src/
│   ├── popup/
│   │   ├── popup.html
│   │   └── popup.js
│   ├── background/
│   │   └── background.js
│   └── content/
│       └── content.js
└── vite.config.js

Vite 配置

javascript 复制代码
// vite.config.js
import { defineConfig } from 'vite'
import { resolve } from 'path'

export default defineConfig({
  build: {
    outDir: 'dist',
    rollupOptions: {
      input: {
        popup: resolve(__dirname, 'src/popup/popup.html'),
        background: resolve(__dirname, 'src/background/background.js'),
        content: resolve(__dirname, 'src/content/content.js')
      },
      output: {
        entryFileNames: '[name].js',
        assetFileNames: '[name].[ext]'
      }
    }
  }
})

Manifest

json 复制代码
{
  "manifest_version": 3,
  "name": "My Extension",
  "version": "1.0.0",
  "action": {
    "default_popup": "popup.html"
  },
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ],
  "permissions": ["storage", "activeTab"]
}

本章小结

通过五个实战项目,覆盖了:

  • ✅ Vue 3 + Pinia 状态管理
  • ✅ React + Router 路由系统
  • ✅ 组件库开发与发布
  • ✅ PWA 离线应用
  • ✅ Chrome 扩展开发

每个项目都是真实可运行的,可以作为学习和参考的基础。

相关推荐
We་ct2 小时前
JS手撕:性能优化、渲染技巧与定时器实现
开发语言·前端·javascript·面试·性能优化·定时器·性能
taWSw5OjU2 小时前
vue对接海康摄像头-H5player
开发语言·前端·javascript
huwuhang2 小时前
跨平台电子书阅读器 | Readest最新版 安卓版+PC版全平台
android·前端·javascript
C澒2 小时前
AI 生码:RAG 检索优化实现可评估、可回溯工程化
前端·ai编程
条tiao条2 小时前
不止语法糖:TypeScript Set 与 Map 深度解析
前端·javascript·typescript
freewlt2 小时前
React Server Components 深度解析:从原理到实战的完整指南
前端·javascript·react.js
ZC跨境爬虫3 小时前
Playwright进阶操作:鼠标拖拽与各类点击实战(含自定义拖拽实例)
前端·爬虫·python·ui
小江的记录本3 小时前
【RabbitMQ】RabbitMQ核心知识体系全解(5大核心模块:Exchange类型、消息确认机制、死信队列、延迟队列、镜像队列)
java·前端·分布式·后端·spring·rabbitmq·mvc
心静财富之门3 小时前
《前端零基础入门:HTML + CSS + JavaScript 全套速查表(详细版 + 实例)》
前端·javascript·python