课程简介
本课程专为零基础学员设计,涵盖从 Nuxt.js 的基本概念到高级应用的全面内容。通过理论讲解与实际项目相结合,学员将逐步掌握开发高性能、SEO 优化的现代 Web 应用的能力。课程遵循认知负荷理论,优化学习流程,确保学员高效吸收和应用知识。
教学原则依据认知负荷理论
- 分段学习:将复杂内容拆分为小模块,逐步深入,避免一次性输入过多信息。
- 多样化教学:结合视频、文字、代码示例和实践练习,满足不同学习风格。
- 即时反馈:通过练习和项目提供即时反馈,帮助学员巩固知识,及时纠正错误。
- 减少干扰:聚焦核心内容,避免不必要的信息,降低认知负荷。
- 促进内在加工:通过实际操作和项目应用,增强理解和记忆,促进知识的内在化。
模块一:课程导论与开发环境搭建
学习目标
- 理解课程结构和学习资源
- 掌握 Nuxt.js 的基本概念和优势
- 完成开发环境的搭建
详细内容
1.1 课程介绍
-
课程目标与结构
- 介绍课程的整体目标和各模块内容,帮助学员了解学习路径和预期成果。
- 案例:展示一个成功完成课程的学员项目示例。
-
学习资源与参考资料
- 推荐书籍、网站和社区资源,指导学员如何利用官方文档和在线资源辅助学习。
- 实战:如何高效使用 Nuxt.js 官方文档查找信息。
1.2 Nuxt.js 概述
-
什么是 Nuxt.js?
- Nuxt.js 的定义、起源及其与 Vue.js 的关系。
- 案例分析:对比使用纯 Vue.js 和使用 Nuxt.js 开发同一个应用的差异。
// 使用纯 Vue.js 创建一个简单页面 import Vue from 'vue'; import App from './App.vue'; new Vue({ render: h => h(App), }).$mount('#app');
// 使用 Nuxt.js 创建相同的页面 // pages/index.vue <template> <div> <h1>Hello, Nuxt.js!</h1> </div> </template> <script> export default { name: 'HomePage', }; </script>
-
Nuxt.js 的优势与应用场景
- 服务端渲染(SSR)与静态生成(SSG)的优势。
- SEO 优化、性能提升和开发体验的具体体现。
- 实战案例:分析一个使用 Nuxt.js 实现的高流量电商网站。
1.3 开发环境搭建
-
Node.js 和 npm 的安装
- 下载与安装 Node.js(含 npm),验证安装成功的方法。
- 实战:编写一个简单的 Node.js 脚本,确保环境配置正确。
# 检查 Node.js 和 npm 是否安装成功 node -v npm -v
// 简单的 Node.js 脚本 // hello.js console.log('Hello, Node.js!');
# 运行脚本 node hello.js
-
创建第一个 Nuxt.js 应用
- 使用
npx create-nuxt-app
创建项目,运行开发服务器并查看初始页面。 - 案例:解读初始项目结构,理解各个文件的作用。
npx create-nuxt-app my-nuxt-app cd my-nuxt-app npm run dev
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js!</h1> <p>This is your first Nuxt.js application.</p> </div> </template> <script> export default { name: 'HomePage', }; </script>
- 使用
-
代码编辑器推荐及基本配置
- 推荐使用 VS Code,安装必要的扩展(如 ESLint、Prettier)。
- 实战:配置 VS Code 的开发环境,设置代码格式化和 lint 规则。
// .eslintrc.js module.exports = { extends: ['@nuxtjs', 'plugin:prettier/recommended'], rules: {}, };
// .prettierrc { "semi": true, "singleQuote": true, "trailingComma": "es5" }
# 安装 ESLint 和 Prettier npm install --save-dev eslint prettier eslint-config-prettier eslint-plugin-prettier
学习活动
- 视频讲解:逐步演示环境搭建过程,确保每一步学员都能跟上。
- 动手练习:学员自行搭建开发环境并创建第一个 Nuxt.js 项目。
- 检查点:完成项目运行截图上传,导师进行反馈和指导。
资源与参考
模块二:Vue.js 基础回顾
学习目标
- 回顾并巩固 Vue.js 的核心概念
- 理解组件、Props、State 和生命周期
- 掌握 Vue Hooks 的基本使用
详细内容
2.1 Vue.js 基础
-
组件与模板
- 组件的定义与使用,理解模板语法及其与 JavaScript 的关系。
- 案例:创建一个可复用的按钮组件,并在不同页面中使用。
<!-- components/Button.vue --> <template> <button @click="handleClick">{{ label }}</button> </template> <script> export default { name: 'Button', props: { label: { type: String, required: true, }, }, methods: { handleClick() { this.$emit('click'); }, }, }; </script> <style scoped> button { padding: 0.5rem 1rem; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; } button:hover { background-color: #0056b3; } </style>
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js!</h1> <Button label="Click Me" @click="handleClick" /> </div> </template> <script> import Button from '~/components/Button.vue'; export default { components: { Button, }, methods: { handleClick() { alert('Button Clicked!'); }, }, }; </script>
-
Props 和 Data
- Props 的传递与使用,Data 的定义与管理,理解 Props 与 Data 的区别与联系。
- 实战:开发一个带有动态数据的用户卡片组件。
<!-- components/UserCard.vue --> <template> <div class="card"> <h2>{{ user.name }}</h2> <p>Email: {{ user.email }}</p> <button @click="like">Like ({{ likes }})</button> </div> </template> <script> export default { name: 'UserCard', props: { user: { type: Object, required: true, }, }, data() { return { likes: 0, }; }, methods: { like() { this.likes += 1; }, }, }; </script> <style scoped> .card { border: 1px solid #ccc; padding: 1rem; margin: 1rem 0; border-radius: 5px; background-color: white; } button { padding: 0.3rem 0.6rem; background-color: #28a745; color: white; border: none; border-radius: 3px; cursor: pointer; } button:hover { background-color: #218838; } </style>
<!-- pages/index.vue --> <template> <div> <h1>User Information</h1> <UserCard :user="user" /> </div> </template> <script> import UserCard from '~/components/UserCard.vue'; export default { components: { UserCard, }, data() { return { user: { name: 'John Doe', email: '[email protected]' }, }; }, }; </script>
-
生命周期钩子
- 组件的生命周期概述,常用生命周期钩子介绍(如
created
、mounted
、destroyed
)。 - 案例:实现一个计时器组件,展示生命周期钩子的应用。
<!-- components/Timer.vue --> <template> <div>Timer: {{ count }} seconds</div> </template> <script> export default { name: 'Timer', data() { return { count: 0, timer: null, }; }, mounted() { this.timer = setInterval(() => { this.count += 1; }, 1000); }, beforeDestroy() { clearInterval(this.timer); }, }; </script>
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js!</h1> <Timer /> </div> </template> <script> import Timer from '~/components/Timer.vue'; export default { components: { Timer, }, }; </script>
- 组件的生命周期概述,常用生命周期钩子介绍(如
2.2 Composition API 入门
-
useState 和 useEffect 等组合式 API
ref
和reactive
的定义与使用场景,watch
和computed
的定义与副作用管理。- 实战:创建一个计数器应用,使用 Composition API 管理状态和副作用。
<!-- components/Counter.vue --> <template> <div> <p>Current Count: {{ count }}</p> <button @click="increment">Increment</button> <button @click="decrement">Decrement</button> </div> </template> <script setup> import { ref, watch } from 'vue'; const count = ref(0); const increment = () => { count.value += 1; }; const decrement = () => { count.value -= 1; }; watch(count, (newVal) => { document.title = `Count: ${newVal}`; }); </script> <style scoped> button { padding: 0.3rem 0.6rem; margin-right: 0.5rem; background-color: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer; } button:hover { background-color: #0056b3; } </style>
<!-- pages/index.vue --> <template> <div> <h1>Counter Example</h1> <Counter /> </div> </template> <script setup> import Counter from '~/components/Counter.vue'; </script>
-
自定义 Composables
- 自定义 Composable 的概念与优势,创建和使用自定义 Composables 示例。
- 案例:开发一个用于表单输入管理的自定义 Composable。
// composables/useForm.js import { reactive } from 'vue'; export default function useForm(initialValues) { const values = reactive({ ...initialValues }); const handleChange = (e) => { const { name, value } = e.target; values[name] = value; }; const resetForm = () => { Object.assign(values, initialValues); }; return { values, handleChange, resetForm }; }
<!-- components/LoginForm.vue --> <template> <form @submit.prevent="handleSubmit"> <div> <label>Email:</label> <input name="email" v-model="values.email" @input="handleChange" required /> </div> <div> <label>Password:</label> <input name="password" type="password" v-model="values.password" @input="handleChange" required /> </div> <button type="submit">Login</button> </form> </template> <script setup> import useForm from '~/composables/useForm'; const { values, handleChange, resetForm } = useForm({ email: '', password: '' }); const handleSubmit = () => { console.log('Form Submitted:', values); resetForm(); }; </script> <style scoped> form { display: flex; flex-direction: column; max-width: 300px; } div { margin-bottom: 1rem; } label { margin-bottom: 0.3rem; } input { padding: 0.5rem; border: 1px solid #ccc; border-radius: 3px; } button { padding: 0.5rem; background-color: #28a745; color: white; border: none; border-radius: 3px; cursor: pointer; } button:hover { background-color: #218838; } </style>
学习活动
- 视频讲解:详细解释每个概念并演示代码示例,确保学员理解理论知识。
- 动手练习:创建简单的 Vue 组件,使用 Props 和 Data 实现交互。
- 练习项目:构建一个简单的计数器应用,应用 Composition API 管理状态和副作用。
资源与参考
模块三:Nuxt.js 基础
学习目标
- 理解 Nuxt.js 的页面和路由机制
- 掌握静态页面生成(SSG)与服务端渲染(SSR)的使用
- 学会创建和使用 API 路由
详细内容
3.1 页面与路由
-
文件系统路由机制
- 基于文件结构的路由配置,页面组件的创建与组织。
- 案例:创建多个页面(首页、关于页、联系页)并理解其路由路径。
<!-- pages/index.vue --> <template> <div> <h1>Home Page</h1> </div> </template> <script> export default { name: 'HomePage', }; </script>
<!-- pages/about.vue --> <template> <div> <h1>About Page</h1> </div> </template> <script> export default { name: 'AboutPage', }; </script>
<!-- pages/contact.vue --> <template> <div> <h1>Contact Page</h1> </div> </template> <script> export default { name: 'ContactPage', }; </script>
-
动态路由与嵌套路由
- 创建动态路由(如
_id.vue
),嵌套路由的实现方法。 - 实战:开发一个动态博客文章页面,路径如
/posts/_id.vue
。
<!-- pages/posts/_id.vue --> <template> <div> <h1>Post ID: {{ id }}</h1> </div> </template> <script> export default { async asyncData({ params }) { return { id: params.id }; }, }; </script>
<!-- pages/posts/index.vue --> <template> <div> <h1>Posts</h1> <ul> <li v-for="post in posts" :key="post.id"> <nuxt-link :to="`/posts/${post.id}`">{{ post.title }}</nuxt-link> </li> </ul> </div> </template> <script> export default { async asyncData() { const posts = [ { id: 1, title: 'First Post' }, { id: 2, title: 'Second Post' }, ]; return { posts }; }, }; </script>
- 创建动态路由(如
-
NuxtLink 组件的使用
- 客户端导航与预加载,NuxtLink 组件的属性和最佳实践。
- 案例:在多个页面之间实现无刷新导航,提升用户体验。
<!-- components/NavBar.vue --> <template> <nav> <ul> <li> <NuxtLink to="/">Home</NuxtLink> </li> <li> <NuxtLink to="/about">About</NuxtLink> </li> <li> <NuxtLink to="/contact">Contact</NuxtLink> </li> <li> <NuxtLink to="/posts">Posts</NuxtLink> </li> </ul> </nav> </template> <style scoped> nav ul { list-style: none; display: flex; gap: 1rem; padding: 1rem; background-color: #f8f9fa; } nav a { text-decoration: none; color: #333; } nav a:hover { color: #007bff; } </style>
<!-- layouts/default.vue --> <template> <div> <NavBar /> <nuxt /> </div> </template> <script> import NavBar from '~/components/NavBar.vue'; export default { components: { NavBar, }, }; </script>
3.2 静态页面生成(SSG)与服务端渲染(SSR)
-
asyncData 和 fetch 方法
- 静态生成与服务端渲染的数据获取方法,使用
asyncData
获取静态数据,使用fetch
实现实时数据渲染。 - 实战:为博客应用生成静态文章页面,提升加载速度。
<!-- pages/posts/_id.vue --> <template> <div> <h1>{{ post.title }}</h1> <p>{{ post.content }}</p> </div> </template> <script> export default { async asyncData({ params }) { const post = { id: params.id, title: `Post ${params.id}`, content: 'This is the post content.', }; return { post }; }, }; </script>
- 静态生成与服务端渲染的数据获取方法,使用
-
fetch 方法
- 服务端渲染的概念与应用场景,使用
fetch
实现实时数据渲染。 - 案例:开发一个实时天气信息页面,展示服务端渲染的优势。
<!-- pages/weather.vue --> <template> <div> <h1>Weather in {{ weather.location.name }}</h1> <p>Temperature: {{ weather.current.temp_c }}°C</p> <p>Condition: {{ weather.current.condition.text }}</p> </div> </template> <script> export default { async fetch() { const res = await fetch('https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q=London'); const data = await res.json(); this.weather = data; }, data() { return { weather: {}, }; }, }; </script>
- 服务端渲染的概念与应用场景,使用
3.3 API 路由
-
创建 API 接口
- 在
server/api
目录下创建 API 路由,处理不同的 HTTP 请求方法(GET、POST 等)。 - 实战:开发一个简单的留言板 API,实现留言的获取和提交。
// server/api/messages.js let messages = []; export default function handler(req, res) { if (req.method === 'GET') { res.status(200).json(messages); } else if (req.method === 'POST') { const { name, message } = req.body; messages.push({ name, message, id: messages.length + 1 }); res.status(201).json({ message: 'Message added successfully' }); } else { res.setHeader('Allow', ['GET', 'POST']); res.status(405).end(`Method ${req.method} Not Allowed`); } }
<!-- components/MessageBoard.vue --> <template> <div> <h2>Message Board</h2> <form @submit.prevent="handleSubmit"> <input name="name" v-model="form.name" placeholder="Your Name" required /> <textarea name="message" v-model="form.message" placeholder="Your Message" required></textarea> <button type="submit">Send</button> </form> <ul> <li v-for="msg in messages" :key="msg.id"> <strong>{{ msg.name }}:</strong> {{ msg.message }} </li> </ul> </div> </template> <script> export default { data() { return { messages: [], form: { name: '', message: '' }, }; }, async mounted() { await this.fetchMessages(); }, methods: { async fetchMessages() { const res = await fetch('/api/messages'); const data = await res.json(); this.messages = data; }, async handleSubmit() { await fetch('/api/messages', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(this.form), }); this.form = { name: '', message: '' }; await this.fetchMessages(); }, }, }; </script> <style scoped> form { display: flex; flex-direction: column; max-width: 400px; } input, textarea { margin-bottom: 1rem; padding: 0.5rem; border: 1px solid #ccc; border-radius: 3px; } button { padding: 0.5rem; background-color: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer; } button:hover { background-color: #0056b3; } ul { list-style: none; padding: 0; } li { padding: 0.5rem; border-bottom: 1px solid #ccc; } </style>
注意:以上 API 路由示例中的数据存储在内存中,适用于教学和测试目的。在实际应用中,应使用数据库或其他持久化存储方式。
- 在
学习活动
- 视频讲解:详细讲解页面与路由机制,演示静态与服务端渲染的实现。
- 动手练习:创建多个页面,设置动态路由并使用 NuxtLink 组件导航。
- 练习项目:构建一个简单的博客,使用 SSG 生成文章页面,使用 API 路由处理评论提交。
资源与参考
模块四:样式与布局
学习目标
- 掌握在 Nuxt.js 中应用 CSS 的多种方法
- 学会使用 CSS Modules 实现模块化样式
- 理解并应用 Scoped CSS 进行内联样式
- 了解并集成流行的 CSS 框架
详细内容
4.1 CSS 基础
-
全局样式与组件级样式
- 在
assets/css
中应用全局样式,在组件中应用局部样式。 - 案例:为整个应用设置统一的字体和颜色主题。
/* assets/css/global.css */ body { font-family: Arial, sans-serif; background-color: #f0f0f0; margin: 0; padding: 0; } nav ul { list-style: none; display: flex; gap: 1rem; } nav a { text-decoration: none; color: #333; } nav a:hover { color: #0070f3; }
// nuxt.config.js export default { css: ['~/assets/css/global.css'], // 其他配置... };
<!-- components/NavBar.vue --> <template> <nav> <ul> <li> <NuxtLink to="/">Home</NuxtLink> </li> <li> <NuxtLink to="/about">About</NuxtLink> </li> <li> <NuxtLink to="/contact">Contact</NuxtLink> </li> <li> <NuxtLink to="/posts">Posts</NuxtLink> </li> </ul> </nav> </template> <style scoped> nav ul { list-style: none; display: flex; gap: 1rem; padding: 1rem; background-color: #f8f9fa; } nav a { text-decoration: none; color: #333; } nav a:hover { color: #0070f3; } </style>
- 在
-
CSS 文件的导入与使用
- 使用
@import
导入 CSS 文件,优化 CSS 加载顺序。 - 实战:为不同组件应用不同的 CSS 文件,确保样式不冲突。
/* assets/css/Button.css */ .button { padding: 0.5rem 1rem; background-color: #0070f3; color: white; border: none; border-radius: 5px; cursor: pointer; } .button:hover { background-color: #005bb5; }
<!-- components/Button.vue --> <template> <button class="button" @click="handleClick">{{ label }}</button> </template> <script> export default { name: 'Button', props: { label: { type: String, required: true, }, }, methods: { handleClick() { this.$emit('click'); }, }, }; </script> <style src="~/assets/css/Button.css" scoped></style>
- 使用
4.2 CSS Modules
-
使用 CSS Modules 实现模块化样式
- CSS Modules 的概念与优势,创建和使用 CSS Module 文件(如
Component.module.css
)。 - 案例:为按钮组件创建独立的 CSS Module,实现样式隔离。
/* components/Button.module.css */ .button { padding: 0.5rem 1rem; background-color: #28a745; color: white; border: none; border-radius: 5px; cursor: pointer; } .button:hover { background-color: #218838; }
<!-- components/Button.vue --> <template> <button :class="$style.button" @click="handleClick">{{ label }}</button> </template> <script> export default { name: 'Button', props: { label: { type: String, required: true, }, }, methods: { handleClick() { this.$emit('click'); }, }, }; </script> <style module> /* 可以在这里添加局部样式 */ </style>
- CSS Modules 的概念与优势,创建和使用 CSS Module 文件(如
-
类名命名与样式应用
- 避免样式冲突的方法,动态应用类名。
- 实战:使用动态类名实现主题切换功能。
/* components/Card.module.css */ .card { padding: 1rem; border: 1px solid #ccc; border-radius: 5px; background-color: white; } .dark { background-color: #333; color: white; border-color: #555; }
<!-- components/Card.vue --> <template> <div :class="[$style.card, isDark ? $style.dark : '']"> <slot></slot> </div> </template> <script> export default { name: 'Card', props: { isDark: { type: Boolean, default: false, }, }, }; </script> <style module> /* 可以在这里添加局部样式 */ </style>
<!-- pages/index.vue --> <template> <div> <button @click="toggleTheme">Toggle Theme</button> <Card :isDark="isDark"> <h2>Card Title</h2> <p>This is a card component.</p> </Card> </div> </template> <script> import Card from '~/components/Card.vue'; import { ref } from '@vue/composition-api'; export default { components: { Card, }, setup() { const isDark = ref(false); const toggleTheme = () => { isDark.value = !isDark.value; }; return { isDark, toggleTheme }; }, }; </script> <style scoped> button { padding: 0.5rem 1rem; margin-bottom: 1rem; background-color: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer; } button:hover { background-color: #0056b3; } </style>
4.3 Scoped CSS
-
内联样式与动态样式
- 使用 Scoped CSS 在组件中编写内联样式,动态样式的实现方法。
- 案例:为动态颜色的卡片组件添加内联样式。
<!-- components/DynamicCard.vue --> <template> <div class="card"> <h2>{{ title }}</h2> <p>{{ content }}</p> <style scoped> .card { padding: 1rem; border: 1px solid #ccc; border-radius: 5px; background-color: {{ bgColor }}; color: {{ textColor }}; } </style> </div> </template> <script> export default { name: 'DynamicCard', props: { title: { type: String, required: true, }, content: { type: String, required: true, }, bgColor: { type: String, required: true, }, }, computed: { textColor() { return this.bgColor === '#333' ? 'white' : 'black'; }, }, }; </script>
<!-- pages/index.vue --> <template> <div> <DynamicCard title="Dynamic Card" content="This card has dynamic styles." bgColor="#f9c74f" /> <DynamicCard title="Dark Card" content="This card has dark background." bgColor="#333" /> </div> </template> <script> import DynamicCard from '~/components/DynamicCard.vue'; export default { components: { DynamicCard, }, }; </script>
-
样式作用域与优先级
- 确保样式仅应用于特定组件,管理样式优先级的问题。
- 实战:解决样式冲突,确保组件样式独立性。
<!-- components/StyledButton.vue --> <template> <button class="btn"> {{ label }} <style scoped> .btn { padding: 0.5rem 1rem; background-color: #0070f3; color: white; border: none; border-radius: 5px; cursor: pointer; } .btn:hover { background-color: #005bb5; } </style> </button> </template> <script> export default { name: 'StyledButton', props: { label: { type: String, required: true, }, }, }; </script>
<!-- pages/index.vue --> <template> <div> <StyledButton label="Styled Button" /> <button class="btn">Regular Button</button> <style scoped> .btn { padding: 0.5rem 1rem; background-color: #28a745; color: white; border: none; border-radius: 5px; cursor: pointer; } .btn:hover { background-color: #218838; } </style> </div> </template> <script> import StyledButton from '~/components/StyledButton.vue'; export default { components: { StyledButton, }, }; </script>
4.4 集成 CSS 框架
-
使用 Tailwind CSS
- Tailwind CSS 的安装与配置,使用 Tailwind 工具类进行快速样式开发。
- 案例:使用 Tailwind CSS 构建一个响应式导航栏。
# 安装 Tailwind CSS npm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p
// tailwind.config.js module.exports = { content: ['./pages/**/*.{vue,js}', './components/**/*.{vue,js}'], theme: { extend: {}, }, plugins: [], };
/* assets/css/tailwind.css */ @tailwind base; @tailwind components; @tailwind utilities;
// nuxt.config.js export default { css: ['~/assets/css/tailwind.css'], build: { postcss: { plugins: { tailwindcss: {}, autoprefixer: {}, }, }, }, // 其他配置... };
<!-- components/NavBarTailwind.vue --> <template> <nav class="bg-blue-500 p-4"> <ul class="flex space-x-4"> <li> <NuxtLink to="/" class="text-white hover:bg-blue-700 px-3 py-2 rounded">Home</NuxtLink> </li> <li> <NuxtLink to="/about" class="text-white hover:bg-blue-700 px-3 py-2 rounded">About</NuxtLink> </li> <li> <NuxtLink to="/contact" class="text-white hover:bg-blue-700 px-3 py-2 rounded">Contact</NuxtLink> </li> </ul> </nav> </template> <style scoped> /* 可以添加额外的样式 */ </style>
<!-- layouts/default.vue --> <template> <div> <NavBarTailwind /> <Nuxt /> </div> </template> <script> import NavBarTailwind from '~/components/NavBarTailwind.vue'; export default { components: { NavBarTailwind, }, }; </script>
-
使用 Bootstrap 或其他流行框架
- Bootstrap 的集成方法,其他框架的选择与使用(如 Vuetify)。
- 实战:在项目中集成 Vuetify,实现现代化界面设计。
# 安装 Vuetify npm install vuetify
// plugins/vuetify.js import Vue from 'vue'; import Vuetify from 'vuetify'; import 'vuetify/dist/vuetify.min.css'; Vue.use(Vuetify); export default new Vuetify({});
// nuxt.config.js export default { plugins: ['~/plugins/vuetify.js'], css: ['vuetify/dist/vuetify.min.css'], build: { transpile: ['vuetify/lib'], }, // 其他配置... };
<!-- components/VuetifyButton.vue --> <template> <v-btn color="primary">{{ label }}</v-btn> </template> <script> export default { name: 'VuetifyButton', props: { label: { type: String, required: true, }, }, }; </script>
<!-- pages/index.vue --> <template> <div> <h1>Vuetify Button</h1> <VuetifyButton label="Click Me" /> </div> </template> <script> import VuetifyButton from '~/components/VuetifyButton.vue'; export default { components: { VuetifyButton, }, }; </script>
学习活动
- 视频讲解:演示不同的样式应用方法,展示 CSS Modules 和 Scoped CSS 的使用。
- 动手练习:为现有项目添加全局样式和组件级样式,使用 CSS Modules 重构样式。
- 练习项目:设计一个响应式布局,集成 Tailwind CSS 并应用于项目中。
资源与参考
模块五:数据获取与管理
学习目标
- 掌握在 Nuxt.js 中进行数据获取的方法
- 理解数据预取与缓存策略
- 学会管理应用状态,使用 Vuex
详细内容
5.1 数据获取
-
Fetch API 与 Axios
- 使用 Fetch API 进行数据请求,使用 Axios 的优势与基本用法。
- 实战:在项目中集成 Axios,替代 Fetch API 进行数据请求。
# 安装 Axios npm install axios
// plugins/axios.js import axios from 'axios'; export default ({ app }, inject) => { const instance = axios.create({ baseURL: 'http://localhost:3000/api', }); inject('axios', instance); };
// nuxt.config.js export default { plugins: ['~/plugins/axios.js'], // 其他配置... };
<!-- components/AxiosExample.vue --> <template> <div> <h2>Messages (Fetched with Axios)</h2> <ul> <li v-for="msg in messages" :key="msg.id"> <strong>{{ msg.name }}:</strong> {{ msg.message }} </li> </ul> </div> </template> <script> export default { data() { return { messages: [], }; }, async mounted() { try { const res = await this.$axios.get('/messages'); this.messages = res.data; } catch (error) { console.error(error); } }, }; </script>
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js!</h1> <AxiosExample /> </div> </template> <script> import AxiosExample from '~/components/AxiosExample.vue'; export default { components: { AxiosExample, }, }; </script>
-
数据预取与缓存策略
- 数据预取的概念与实现,缓存策略的选择与应用(如 SWR、vue-query)。
- 案例:使用 SWR 实现数据预取和缓存,提高应用性能。
# 安装 SWR npm install swr
<!-- components/SWRExample.vue --> <template> <div> <h2>Messages (Fetched with SWR)</h2> <ul> <li v-for="msg in data" :key="msg.id"> <strong>{{ msg.name }}:</strong> {{ msg.message }} </li> </ul> </div> </template> <script> import useSWR from 'swr'; import axios from 'axios'; export default { setup() { const fetcher = (url) => axios.get(url).then(res => res.data); const { data, error } = useSWR('/messages', fetcher); return { data, error }; }, }; </script>
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js!</h1> <SWRExample /> </div> </template> <script> import SWRExample from '~/components/SWRExample.vue'; export default { components: { SWRExample, }, }; </script>
5.2 状态管理
-
使用 Vuex
- Vuex 的定义与使用场景,创建和使用 Vuex 仓库的基础方法。
- 实战:开发一个全局主题切换功能,使用 Vuex 管理主题状态。
// store/index.js export const state = () => ({ theme: 'light', }); export const mutations = { toggleTheme(state) { state.theme = state.theme === 'light' ? 'dark' : 'light'; }, };
<!-- components/NavBar.vue --> <template> <nav :class="themeClass"> <ul> <li> <NuxtLink to="/">Home</NuxtLink> </li> <li> <NuxtLink to="/about">About</NuxtLink> </li> <li> <NuxtLink to="/contact">Contact</NuxtLink> </li> <li> <button @click="toggleTheme"> Switch to {{ theme === 'light' ? 'Dark' : 'Light' }} Theme </button> </li> </ul> </nav> </template> <script> import { mapState, mapMutations } from 'vuex'; export default { computed: { ...mapState(['theme']), themeClass() { return this.theme; }, }, methods: { ...mapMutations(['toggleTheme']), }, }; </script> <style scoped> nav.light { background-color: #ffffff; color: #000000; } nav.dark { background-color: #333333; color: #ffffff; } nav ul { list-style: none; display: flex; gap: 1rem; padding: 1rem; } nav button { padding: 0.5rem 1rem; cursor: pointer; } </style>
-
集成 Vuex
- Vuex 的基本概念与配置,使用 Vuex 模块化管理复杂状态。
- 案例:在项目中集成 Vuex,管理全局消息状态。
// store/messages.js export const state = () => ({ messages: [], }); export const mutations = { setMessages(state, messages) { state.messages = messages; }, addMessage(state, message) { state.messages.push(message); }, }; export const actions = { async fetchMessages({ commit }) { try { const res = await this.$axios.get('/messages'); commit('setMessages', res.data); } catch (error) { console.error(error); } }, async submitMessage({ commit }, form) { try { await this.$axios.post('/messages', form); commit('addMessage', { ...form, id: this.state.messages.length + 1 }); } catch (error) { console.error(error); } }, };
<!-- components/VuexMessageBoard.vue --> <template> <div> <h2>Vuex Message Board</h2> <form @submit.prevent="handleSubmit"> <input name="name" v-model="form.name" placeholder="Your Name" required /> <textarea name="message" v-model="form.message" placeholder="Your Message" required></textarea> <button type="submit">Send</button> </form> <ul> <li v-for="msg in messages" :key="msg.id"> <strong>{{ msg.name }}:</strong> {{ msg.message }} </li> </ul> </div> </template> <script> import { mapState, mapActions } from 'vuex'; export default { data() { return { form: { name: '', message: '' }, }; }, computed: { ...mapState(['messages']), }, methods: { ...mapActions(['submitMessage', 'fetchMessages']), async handleSubmit() { await this.submitMessage(this.form); this.form = { name: '', message: '' }; }, }, async mounted() { await this.fetchMessages(); }, }; </script> <style scoped> form { display: flex; flex-direction: column; max-width: 400px; } input, textarea { margin-bottom: 1rem; padding: 0.5rem; border: 1px solid #ccc; border-radius: 3px; } button { padding: 0.5rem; background-color: #28a745; color: white; border: none; border-radius: 3px; cursor: pointer; } button:hover { background-color: #218838; } ul { list-style: none; padding: 0; } li { padding: 0.5rem; border-bottom: 1px solid #ccc; } </style>
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js!</h1> <VuexMessageBoard /> </div> </template> <script> import VuexMessageBoard from '~/components/VuexMessageBoard.vue'; export default { components: { VuexMessageBoard, }, }; </script>
5.3 数据更新与同步
-
实时数据更新
- 使用 WebSockets 或其他实时技术,实现数据的实时更新。
- 实战:集成 Socket.io,实现实时消息更新。
# 安装 Socket.io npm install socket.io socket.io-client
// server/socket.js const { Server } = require('socket.io'); let io; module.exports = function (server) { if (!io) { io = new Server(server, { cors: { origin: '*', methods: ['GET', 'POST'], }, }); io.on('connection', (socket) => { console.log('New client connected'); socket.on('sendMessage', (msg) => { io.emit('newMessage', msg); }); socket.on('disconnect', () => { console.log('Client disconnected'); }); }); } return io; };
// server/index.js const express = require('express'); const http = require('http'); const socketHandler = require('./socket'); const app = express(); const server = http.createServer(app); const io = socketHandler(server); app.use(express.json()); app.get('/api/messages', (req, res) => { res.json([]); }); app.post('/api/messages', (req, res) => { const { name, message } = req.body; io.emit('newMessage', { name, message, id: Date.now() }); res.status(201).json({ message: 'Message added successfully' }); }); server.listen(3000, () => { console.log('Server is running on port 3000'); });
<!-- components/RealTimeMessageBoard.vue --> <template> <div> <h2>Real-Time Message Board</h2> <form @submit.prevent="handleSubmit"> <input name="name" v-model="form.name" placeholder="Your Name" required /> <textarea name="message" v-model="form.message" placeholder="Your Message" required></textarea> <button type="submit">Send</button> </form> <ul> <li v-for="msg in messages" :key="msg.id"> <strong>{{ msg.name }}:</strong> {{ msg.message }} </li> </ul> </div> </template> <script> import io from 'socket.io-client'; import { ref, onMounted, onUnmounted } from '@vue/composition-api'; export default { setup() { const socket = io(); const messages = ref([]); const form = ref({ name: '', message: '' }); const handleSubmit = () => { socket.emit('sendMessage', form.value); form.value = { name: '', message: '' }; }; const addMessage = (msg) => { messages.value.push(msg); }; onMounted(() => { socket.on('newMessage', addMessage); }); onUnmounted(() => { socket.off('newMessage', addMessage); socket.disconnect(); }); return { messages, form, handleSubmit }; }, }; </script> <style scoped> form { display: flex; flex-direction: column; max-width: 400px; } input, textarea { margin-bottom: 1rem; padding: 0.5rem; border: 1px solid #ccc; border-radius: 3px; } button { padding: 0.5rem; background-color: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer; } button:hover { background-color: #0056b3; } ul { list-style: none; padding: 0; } li { padding: 0.5rem; border-bottom: 1px solid #ccc; } </style>
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js!</h1> <RealTimeMessageBoard /> </div> </template> <script> import RealTimeMessageBoard from '~/components/RealTimeMessageBoard.vue'; export default { components: { RealTimeMessageBoard, }, }; </script>
学习活动
- 视频讲解:详细介绍数据获取方法和状态管理工具的使用。
- 动手练习:在项目中集成 Axios 进行数据请求,使用 Vuex 管理简单状态。
- 练习项目:构建一个用户信息展示页面,使用 Vuex 管理全局状态。
资源与参考
模块六:高级功能与优化
学习目标
- 学会使用 Nuxt.js 的图像优化功能
- 实现多语言支持(国际化)
- 掌握 SEO 优化的方法
- 进行性能优化,提升应用效率
详细内容
6.1 图像优化
-
Nuxt Image 组件
- Image 组件的使用方法和优势,配置外部图片源。
- 实战:在项目中使用 Image 组件替代传统的
<img>
标签,提升图片加载性能。
# 安装 @nuxt/image npm install @nuxt/image
// nuxt.config.js export default { modules: ['@nuxt/image'], image: { // 选项 }, // 其他配置... };
<!-- components/OptimizedImage.vue --> <template> <div> <h2>Optimized Image</h2> <NuxtImage src="/images/sample.jpg" alt="Sample" width="500" height="300" /> </div> </template> <script> export default { name: 'OptimizedImage', }; </script>
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js!</h1> <OptimizedImage /> </div> </template> <script> import OptimizedImage from '~/components/OptimizedImage.vue'; export default { components: { OptimizedImage, }, }; </script>
注意:确保在
nuxt.config.js
中配置了外部图片源(如果使用外部 URL)。// nuxt.config.js export default { modules: ['@nuxt/image'], image: { domains: ['example.com'], }, // 其他配置... };
-
图片懒加载与响应式图片
- 实现图片懒加载提升页面性能,使用不同分辨率的图片实现响应式设计。
- 案例:为博客文章添加响应式图片,优化加载时间和用户体验。
<!-- components/ResponsiveImage.vue --> <template> <div> <h2>Responsive Image</h2> <NuxtImage src="/images/responsive.jpg" alt="Responsive Example" layout="responsive" sizes="(max-width: 600px) 480px, (max-width: 1200px) 800px, 1200px" /> </div> </template> <script> export default { name: 'ResponsiveImage', }; </script>
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js!</h1> <ResponsiveImage /> </div> </template> <script> import ResponsiveImage from '~/components/ResponsiveImage.vue'; export default { components: { ResponsiveImage, }, }; </script>
6.2 国际化(i18n)
-
多语言支持
- Nuxt.js 内置国际化支持的配置,管理多语言资源文件。
- 实战:配置 Nuxt.js 应用支持中文和英文两种语言。
# 安装 @nuxtjs/i18n 模块 npm install @nuxtjs/i18n
// nuxt.config.js export default { modules: ['@nuxtjs/i18n'], i18n: { locales: [ { code: 'en', name: 'English' }, { code: 'zh', name: '中文' }, ], defaultLocale: 'en', vueI18n: { fallbackLocale: 'en', messages: { en: { welcome: 'Welcome to Nuxt.js!', description: 'This is an internationalized application.', }, zh: { welcome: '欢迎来到 Nuxt.js!', description: '这是一个国际化的应用程序。', }, }, }, }, // 其他配置... };
<!-- pages/index.vue --> <template> <div> <LanguageSwitcher /> <h1>{{ $t('welcome') }}</h1> <p>{{ $t('description') }}</p> </div> </template> <script> import LanguageSwitcher from '~/components/LanguageSwitcher.vue'; export default { components: { LanguageSwitcher, }, }; </script>
-
本地化配置
- URL 路由中的语言标识,动态内容的本地化处理。
- 案例:开发一个多语言切换功能,动态展示不同语言内容。
<!-- components/LanguageSwitcher.vue --> <template> <div> <span v-for="locale in locales" :key="locale.code" class="mr-2"> <NuxtLink :to="switchLocalePath(locale.code)" :class="{ active: locale.code === currentLocale }"> {{ locale.name }} </NuxtLink> </span> </div> </template> <script> export default { computed: { locales() { return this.$i18n.locales; }, currentLocale() { return this.$i18n.locale; }, }, }; </script> <style scoped> .active { font-weight: bold; text-decoration: underline; } </style>
<!-- pages/index.vue --> <template> <div> <LanguageSwitcher /> <h1>{{ $t('welcome') }}</h1> <p>{{ $t('description') }}</p> </div> </template> <script> import LanguageSwitcher from '~/components/LanguageSwitcher.vue'; export default { components: { LanguageSwitcher, }, }; </script>
6.3 SEO 优化
-
使用 Head 组件
- 配置页面的标题、描述和关键字,动态设置元标签。
- 实战:为每个页面设置独特的 meta 标签,提升搜索引擎排名。
<!-- components/SEO.vue --> <template> <head> <title>{{ title }}</title> <meta name="description" :content="description" /> </head> </template> <script> export default { props: { title: { type: String, required: true, }, description: { type: String, required: true, }, }, }; </script>
<!-- pages/about.vue --> <template> <div> <SEO title="About Us" description="Learn more about our company." /> <h1>About Us</h1> <p>This is the about page.</p> </div> </template> <script> import SEO from '~/components/SEO.vue'; export default { components: { SEO, }, }; </script>
-
元数据管理与结构化数据
- 使用 JSON-LD 添加结构化数据,提升搜索引擎可见性的方法。
- 案例:为博客文章添加结构化数据,增强搜索引擎理解。
<!-- components/BlogSEO.vue --> <template> <head> <title>{{ title }}</title> <meta name="description" :content="description" /> <script type="application/ld+json" :innerHTML="jsonLd"></script> </head> </template> <script> export default { props: { title: { type: String, required: true, }, description: { type: String, required: true, }, author: { type: String, required: true, }, date: { type: String, required: true, }, }, computed: { jsonLd() { return JSON.stringify({ "@context": "https://schema.org", "@type": "BlogPosting", "headline": this.title, "description": this.description, "author": { "@type": "Person", "name": this.author, }, "datePublished": this.date, }); }, }, }; </script>
<!-- pages/posts/_id.vue --> <template> <div> <BlogSEO :title="post.title" :description="post.content" :author="post.author" :date="post.date" /> <h1>{{ post.title }}</h1> <p>{{ post.content }}</p> </div> </template> <script> import BlogSEO from '~/components/BlogSEO.vue'; export default { components: { BlogSEO, }, async asyncData({ params }) { const post = { id: params.id, title: `Post ${params.id}`, content: 'This is the post content.', author: 'John Doe', date: '2024-01-01', }; return { post }; }, }; </script>
6.4 性能优化
-
代码分割与懒加载
- 动态导入实现代码分割,按需加载组件提升性能。
- 实战:优化大型组件的加载方式,减少初始加载时间。
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js!</h1> <LazyComponent /> </div> </template> <script> export default { components: { LazyComponent: () => import('~/components/LazyComponent.vue'), }, }; </script>
<!-- components/LazyComponent.vue --> <template> <div>This is a lazily loaded component.</div> </template> <script> export default { name: 'LazyComponent', }; </script>
-
Bundle 分析与优化
- 使用 Nuxt.js 内置的 bundle 分析工具,优化依赖包和减少体积的方法。
- 案例:通过分析工具识别和移除冗余依赖,提升应用性能。
# 安装 @nuxtjs/webpack-bundle-analyzer npm install --save-dev @nuxtjs/webpack-bundle-analyzer
// nuxt.config.js export default { buildModules: ['@nuxtjs/webpack-bundle-analyzer'], webpackBundleAnalyzer: { analyzerMode: 'static', openAnalyzer: false, }, // 其他配置... };
# 运行分析 npm run build
分析结果:查看生成的分析报告,识别大尺寸的依赖包,考虑替换或优化。
学习活动
- 视频讲解:演示图像优化、国际化和 SEO 优化的实现过程。
- 动手练习:在项目中集成 Image 组件,配置多语言支持。
- 练习项目:优化现有项目的图像加载,添加多语言支持,并进行基本的 SEO 设置。
资源与参考
- Nuxt Image 文档
- Nuxt.js Internationalization 文档
- SEO Best Practices for Nuxt.js
- Nuxt.js Performance Optimization
- JSON-LD 官方指南
- 示例代码仓库(GitHub 链接)
模块七:认证与安全
学习目标
- 实现用户认证功能
- 掌握常见的 Web 安全最佳实践
- 确保应用的数据安全与防护
详细内容
7.1 用户认证
-
基于 JWT 的认证
- JWT 的基本概念与工作原理,在 Nuxt.js 中实现 JWT 认证,存储与管理 JWT(如 HttpOnly Cookies)。
- 实战:开发一个用户登录系统,使用 JWT 进行认证和授权。
# 安装必要的依赖 npm install jsonwebtoken bcryptjs
// server/api/login.js const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const SECRET_KEY = 'your-secret-key'; const users = [ { id: 1, username: 'user1', password: '$2a$10$E9JbFjR7g6JQPOlxLHBM2u3u2fT6mVZ6r4vUQ8Bvc7oGB0uVQqC1e' }, // password: pass123 ]; export default function handler(req, res) { if (req.method === 'POST') { const { username, password } = req.body; const user = users.find((u) => u.username === username); if (user && bcrypt.compareSync(password, user.password)) { const token = jwt.sign({ id: user.id, username: user.username }, SECRET_KEY, { expiresIn: '1h' }); res.setHeader('Set-Cookie', `token=${token}; HttpOnly; Path=/; Max-Age=3600`); res.status(200).json({ message: 'Login successful' }); } else { res.status(401).json({ message: 'Invalid credentials' }); } } else { res.setHeader('Allow', ['POST']); res.status(405).end(`Method ${req.method} Not Allowed`); } }
<!-- components/LoginForm.vue --> <template> <div> <h2>Login</h2> <form @submit.prevent="handleSubmit"> <input name="username" v-model="form.username" placeholder="Username" required /> <input name="password" type="password" v-model="form.password" placeholder="Password" required /> <button type="submit">Login</button> </form> <p v-if="message">{{ message }}</p> </div> </template> <script> export default { data() { return { form: { username: '', password: '' }, message: '', }; }, methods: { async handleSubmit() { try { const res = await this.$axios.post('/login', this.form); this.message = res.data.message; } catch (error) { this.message = error.response.data.message; } }, }, }; </script> <style scoped> form { display: flex; flex-direction: column; max-width: 300px; } input { margin-bottom: 1rem; padding: 0.5rem; border: 1px solid #ccc; border-radius: 3px; } button { padding: 0.5rem; background-color: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer; } button:hover { background-color: #0056b3; } p { color: red; } </style>
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js!</h1> <LoginForm /> </div> </template> <script> import LoginForm from '~/components/LoginForm.vue'; export default { components: { LoginForm, }, }; </script>
-
使用 Nuxt Auth 模块实现社交登录
- Nuxt Auth 模块的安装与配置,集成常见的社交登录提供商(如 Google、GitHub),自定义认证流程与回调。
- 案例:为应用添加 GitHub 登录功能,提升用户注册的便捷性。
# 安装 @nuxtjs/auth-next 和 @nuxtjs/axios npm install @nuxtjs/auth-next @nuxtjs/axios
// nuxt.config.js export default { modules: ['@nuxtjs/axios', '@nuxtjs/auth-next'], auth: { strategies: { github: { clientId: 'your-github-client-id', clientSecret: 'your-github-client-secret', codeChallengeMethod: '', responseType: 'code', }, }, }, axios: { baseURL: 'http://localhost:3000/api', }, // 其他配置... };
<!-- components/GitHubLogin.vue --> <template> <div> <button @click="loginWithGitHub">Sign in with GitHub</button> <button v-if="isAuthenticated" @click="logout">Sign out</button> </div> </template> <script> export default { computed: { isAuthenticated() { return this.$auth.loggedIn; }, }, methods: { loginWithGitHub() { this.$auth.loginWith('github'); }, logout() { this.$auth.logout(); }, }, }; </script> <style scoped> button { padding: 0.5rem 1rem; margin-right: 0.5rem; background-color: #24292e; color: white; border: none; border-radius: 3px; cursor: pointer; } button:hover { background-color: #444d56; } </style>
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js!</h1> <GitHubLogin /> <p v-if="isAuthenticated">Hello, {{ user.name }}!</p> </div> </template> <script> export default { components: { GitHubLogin, }, computed: { isAuthenticated() { return this.$auth.loggedIn; }, user() { return this.$auth.user; }, }, }; </script>
// server/api/auth/[...auth].js import { handleAuth } from '@nuxtjs/auth-next/dist/runtime'; export default handleAuth();
环境变量配置(
.env
):GITHUB_CLIENT_ID=your-github-client-id GITHUB_CLIENT_SECRET=your-github-client-secret AUTH_SECRET=your-auth-secret
7.2 安全最佳实践
-
防范 XSS 与 CSRF 攻击
- 理解 XSS 和 CSRF 的攻击原理,在 Nuxt.js 中实施防护措施(如 Content Security Policy、CSRF Tokens)。
- 实战:在项目中配置 Content Security Policy,防范 XSS 攻击。
// nuxt.config.js export default { head: { meta: [ { 'http-equiv': 'Content-Security-Policy', content: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;" }, ], }, // 其他配置... };
-
数据验证与错误处理
- 输入数据的验证与清洗,统一的错误处理机制,使用库(如 Joi、Yup)进行验证。
- 案例:为用户注册表单添加数据验证,确保数据的安全性和一致性。
# 安装 Yup npm install yup
// server/api/register.js const Yup = require('yup'); const schema = Yup.object().shape({ username: Yup.string().required(), email: Yup.string().email().required(), password: Yup.string().min(6).required(), }); export default async function handler(req, res) { if (req.method === 'POST') { try { const validData = await schema.validate(req.body); // 处理注册逻辑,例如存储用户信息 res.status(201).json({ message: 'User registered successfully', user: validData }); } catch (error) { res.status(400).json({ message: error.errors }); } } else { res.setHeader('Allow', ['POST']); res.status(405).end(`Method ${req.method} Not Allowed`); } }
<!-- components/RegisterForm.vue --> <template> <div> <h2>Register</h2> <form @submit.prevent="handleSubmit"> <input name="username" v-model="form.username" placeholder="Username" required /> <input name="email" type="email" v-model="form.email" placeholder="Email" required /> <input name="password" type="password" v-model="form.password" placeholder="Password" required /> <button type="submit">Register</button> </form> <p v-if="message">{{ message }}</p> </div> </template> <script> export default { data() { return { form: { username: '', email: '', password: '' }, message: '', }; }, methods: { async handleSubmit() { try { const res = await this.$axios.post('/register', this.form); this.message = res.data.message; } catch (error) { this.message = error.response.data.message; } }, }, }; </script> <style scoped> form { display: flex; flex-direction: column; max-width: 300px; } input { margin-bottom: 1rem; padding: 0.5rem; border: 1px solid #ccc; border-radius: 3px; } button { padding: 0.5rem; background-color: #28a745; color: white; border: none; border-radius: 3px; cursor: pointer; } button:hover { background-color: #218838; } p { color: red; } </style>
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js!</h1> <RegisterForm /> </div> </template> <script> import RegisterForm from '~/components/RegisterForm.vue'; export default { components: { RegisterForm, }, }; </script>
学习活动
- 视频讲解:介绍用户认证机制和常见的安全威胁。
- 动手练习:在项目中集成 JWT 认证,配置 Nuxt Auth 模块实现社交登录。
- 练习项目:为博客应用添加用户注册、登录功能,并实现基本的安全防护。
资源与参考
模块八:部署与运维
学习目标
- 掌握将 Nuxt.js 应用部署到 Vercel 的流程
- 了解其他常见的部署平台选项
- 实现持续集成与持续部署(CI/CD)
详细内容
8.1 部署到 Vercel
-
Vercel 平台简介
- Vercel 的特点与优势,Vercel 与 Nuxt.js 的深度集成。
- 案例:分析一个成功部署在 Vercel 上的 Nuxt.js 应用。
-
部署流程与配置
- 连接 GitHub 仓库,配置项目设置(环境变量、构建命令),部署后的管理与监控。
- 实战:将当前项目部署到 Vercel,配置必要的环境变量和设置。
# 确保项目已初始化 Git 并推送到 GitHub git init git add . git commit -m "Initial commit" git branch -M main git remote add origin https://github.com/yourusername/your-repo.git git push -u origin main
在 Vercel 上:
- 登录 Vercel。
- 点击 “New Project”。
- 选择 GitHub 仓库并授权。
- 配置项目设置(环境变量、构建命令)。
- 点击 “Deploy”。
8.2 其他部署选项
-
部署到 Netlify
- Netlify 的安装与配置,部署流程与注意事项。
- 案例:对比 Vercel 和 Netlify 的部署流程,选择适合的部署平台。
# 使用 Netlify CLI 部署 npm install -g netlify-cli netlify login netlify init netlify deploy
-
部署到 AWS、Heroku 等平台
- 使用 AWS Amplify 部署 Nuxt.js 应用,Heroku 的部署步骤与配置,比较不同平台的优缺点。
- 实战:将项目部署到 Heroku,体验不同部署平台的差异。
# 在 Heroku 上部署 heroku create your-app-name git push heroku main heroku open
注意:确保配置环境变量和构建脚本,以适应不同平台的要求。
8.3 持续集成与持续部署(CI/CD)
-
自动化部署流程
- 配置 GitHub Actions 实现自动化构建与部署,使用其他 CI/CD 工具(如 GitLab CI、CircleCI)。
- 案例:设计一个自动化部署流程,确保代码每次提交后自动部署到生产环境。
# .github/workflows/deploy.yml name: Deploy to Vercel on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup Node.js uses: actions/setup-node@v2 with: node-version: '16' - name: Install Dependencies run: npm install - name: Build run: npm run build - name: Deploy uses: amondnet/vercel-action@v20 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} working-directory: ./ env: VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
-
使用 GitHub Actions
- 创建和配置 GitHub Actions 工作流,实现代码测试、构建和部署的自动化。
- 实战:在项目中配置 GitHub Actions,实现代码提交后的自动化测试和部署。
学习活动
- 视频讲解:演示如何将 Nuxt.js 应用部署到 Vercel 和其他平台。
- 动手练习:将项目部署到 Vercel,配置环境变量和部署选项。
- 练习项目:配置 GitHub Actions 实现项目的自动化部署流程。
资源与参考
模块九:TypeScript 与 Nuxt.js
学习目标
- 理解 TypeScript 的基本概念与类型系统
- 学会在 Nuxt.js 项目中集成 TypeScript
- 实现类型安全的开发,提升代码质量
详细内容
9.1 TypeScript 基础
-
类型系统概述
- TypeScript 的优势与基本概念,基本类型、接口和类型别名。
- 案例:为一个简单的 Vue 组件添加类型注解,提升代码的可读性和安全性。
// components/TypedButton.vue <template> <button @click="handleClick">{{ label }}</button> </template> <script lang="ts"> import { defineComponent } from '@nuxtjs/composition-api'; export default defineComponent({ name: 'TypedButton', props: { label: { type: String, required: true, }, }, methods: { handleClick() { this.$emit('click'); }, }, }); </script> <style scoped> button { padding: 0.5rem 1rem; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; } button:hover { background-color: #0056b3; } </style>
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js with TypeScript!</h1> <TypedButton label="Click Me" @click="handleClick" /> </div> </template> <script lang="ts"> import { defineComponent } from '@nuxtjs/composition-api'; import TypedButton from '~/components/TypedButton.vue'; export default defineComponent({ components: { TypedButton, }, methods: { handleClick() { alert('Typed Button Clicked!'); }, }, }); </script>
-
在 Nuxt.js 中集成 TypeScript
- 安装和配置 TypeScript,创建
tsconfig.json
文件,将现有 JavaScript 文件迁移到 TypeScript。 - 实战:将现有项目从 JavaScript 转换为 TypeScript,解决迁移过程中的常见问题。
# 安装 TypeScript 和类型定义 npm install --save-dev typescript @types/node @nuxt/types
# 运行 Nuxt.js,自动生成 tsconfig.json npm run dev
迁移示例:
// components/Button.js -> components/Button.vue(已经是 TypeScript 示例)
- 安装和配置 TypeScript,创建
9.2 类型安全开发
-
定义接口与类型
- 为组件定义 Props 接口,定义 API 数据的类型。
- 案例:为博客文章组件定义类型,确保数据的一致性和安全性。
// types/index.ts export interface Post { id: string; title: string; content: string; author: string; date: string; }
// components/PostCard.vue <template> <div class="card"> <h2>{{ post.title }}</h2> <p>{{ post.content }}</p> <small> By {{ post.author }} on {{ post.date }} </small> </div> </template> <script lang="ts"> import { defineComponent } from '@nuxtjs/composition-api'; import { Post } from '~/types'; export default defineComponent({ name: 'PostCard', props: { post: { type: Object as () => Post, required: true, }, }, }); </script> <style scoped> .card { border: 1px solid #ccc; padding: 1rem; margin: 1rem 0; border-radius: 5px; background-color: white; } </style>
// pages/posts/_id.vue <template> <div> <PostCard :post="post" /> </div> </template> <script lang="ts"> import { defineComponent } from '@nuxtjs/composition-api'; import { Post } from '~/types'; import PostCard from '~/components/PostCard.vue'; export default defineComponent({ components: { PostCard, }, async asyncData({ params }): Promise<{ post: Post }> { const post: Post = { id: params.id as string, title: `Post ${params.id}`, content: 'This is the post content.', author: 'John Doe', date: '2024-01-01', }; return { post }; }, }); </script>
-
使用泛型与高级类型
- 理解泛型的概念与应用,使用高级类型(如联合类型、交叉类型)。
- 实战:开发一个通用的数据请求 Composable,使用泛型提升灵活性和类型安全。
// composables/useFetch.ts import useSWR from 'swr'; import axios from 'axios'; export default function useFetch<T>(url: string) { const fetcher = (url: string): Promise<T> => axios.get(url).then(res => res.data); const { data, error } = useSWR<T>(url, fetcher); return { data, isLoading: !error && !data, isError: error, }; }
<!-- components/PostsList.vue --> <template> <div> <h2>Blog Posts</h2> <ul> <li v-for="post in data" :key="post.id"> <strong>{{ post.title }}</strong> by {{ post.author }} </li> </ul> </div> </template> <script lang="ts"> import { defineComponent } from '@nuxtjs/composition-api'; import useFetch from '~/composables/useFetch'; import { Post } from '~/types'; export default defineComponent({ setup() { const { data, isLoading, isError } = useFetch<Post[]>('/api/messages'); return { data, isLoading, isError }; }, }); </script>
-
与第三方库的类型集成
- 安装和使用类型定义文件(@types),处理自定义类型声明。
- 案例:为一个未提供类型定义的第三方库添加自定义类型声明。
# 安装 Lodash 类型定义文件 npm install --save-dev @types/lodash
// components/LodashExample.vue <template> <div> <h2>Lodash Example</h2> <ul> <li v-for="num in doubled" :key="num">{{ num }}</li> </ul> </div> </template> <script lang="ts"> import { defineComponent } from '@nuxtjs/composition-api'; import _ from 'lodash'; export default defineComponent({ setup() { const numbers = [1, 2, 3, 4, 5]; const doubled = _.map(numbers, (num) => num * 2); return { doubled }; }, }); </script>
// 创建自定义类型声明(如果第三方库没有提供) // types/custom-lib.d.ts declare module 'custom-lib' { export function customFunction(arg: string): string; }
学习活动
- 视频讲解:介绍 TypeScript 的基本概念和在 Nuxt.js 中的集成方法。
- 动手练习:在现有项目中引入 TypeScript,重构组件以使用类型。
- 练习项目:为博客应用添加 TypeScript 支持,定义 API 数据类型和组件 Props 接口。
资源与参考
模块十:实战项目开发
学习目标
- 综合应用所学知识,独立开发一个完整的 Nuxt.js 项目
- 练习项目规划、需求分析、功能实现和部署
- 提升项目管理和问题解决能力
详细内容
10.1 项目规划
-
项目选型与需求分析
- 确定项目类型(如博客、电子商务、社交平台),编写项目需求文档。
- 案例:选择开发一个博客应用,详细分析其功能需求和用户故事。
项目需求文档示例:
- 用户可以注册和登录。
- 用户可以创建、编辑和删除博客文章。
- 用户可以评论和点赞文章。
- 管理员可以管理所有用户和文章。
- 网站支持多语言(英文和中文)。
-
技术栈选择
- 确定使用的库和工具(如 Vuex、Tailwind CSS),规划项目结构与模块划分。
- 实战:制定项目技术栈和目录结构,确保模块化和可扩展性。
技术栈:
- 前端框架: Nuxt.js
- 状态管理: Vuex
- 样式框架: Tailwind CSS
- 数据库: MongoDB
- 认证: @nuxtjs/auth-next
- 部署平台: Vercel
项目目录结构:
my-blog-app/ ├── assets/ │ └── css/ ├── components/ ├── composables/ ├── layouts/ ├── middleware/ ├── pages/ │ ├── api/ │ ├── posts/ │ ├── _id.vue │ ├── index.vue │ └── about.vue ├── plugins/ ├── static/ ├── store/ ├── types/ ├── nuxt.config.js ├── tsconfig.json └── package.json
10.2 项目搭建
-
初始化项目结构
- 设置目录结构和文件组织,配置开发环境和工具(如 ESLint、Prettier)。
- 实战:创建项目骨架,配置代码格式化和 lint 规则。
npx create-nuxt-app my-blog-app cd my-blog-app npm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p npm install @nuxtjs/auth-next @nuxtjs/axios vuex
/* assets/css/tailwind.css */ @tailwind base; @tailwind components; @tailwind utilities;
// nuxt.config.js export default { modules: ['@nuxtjs/axios', '@nuxtjs/auth-next', '@nuxtjs/tailwindcss'], axios: { baseURL: 'http://localhost:3000/api', }, auth: { strategies: { github: { clientId: 'your-github-client-id', clientSecret: 'your-github-client-secret', codeChallengeMethod: '', responseType: 'code', }, }, }, css: ['~/assets/css/tailwind.css'], build: { postcss: { plugins: { tailwindcss: {}, autoprefixer: {}, }, }, }, // 其他配置... };
10.3 功能实现
-
用户认证模块
- 实现用户注册、登录、登出功能,保护受限页面和资源。
- 实战:开发一个用户认证系统,确保只有登录用户才能访问特定页面。
<!-- components/ProtectedRoute.vue --> <template> <div v-if="isAuthenticated"> <slot></slot> </div> <div v-else> <nuxt-link to="/login">Please login to access this page.</nuxt-link> </div> </template> <script> export default { computed: { isAuthenticated() { return this.$auth.loggedIn; }, }, }; </script> <style scoped> /* 可以添加额外的样式 */ </style>
<!-- pages/dashboard.vue --> <template> <ProtectedRoute> <div> <h1>Dashboard</h1> <p>This is a protected page.</p> </div> </ProtectedRoute> </template> <script> import ProtectedRoute from '~/components/ProtectedRoute.vue'; export default { components: { ProtectedRoute, }, }; </script>
-
数据展示与交互
- 实现数据列表展示、分页、搜索功能,增加用户交互(如评论、点赞)。
- 案例:为博客应用添加文章列表、分页和搜索功能,提升用户体验。
<!-- components/PostsList.vue --> <template> <div> <h2>Blog Posts</h2> <input type="text" v-model="searchTerm" @input="handleSearch" placeholder="Search posts..." class="mb-4 p-2 border" /> <ul> <li v-for="post in filteredPosts" :key="post.id"> <nuxt-link :to="`/posts/${post.id}`">{{ post.title }}</nuxt-link> by {{ post.author }} </li> </ul> <div class="mt-4"> <button @click="prevPage" :disabled="currentPage === 1">Previous</button> <span class="mx-2">Page {{ currentPage }}</span> <button @click="nextPage">Next</button> </div> </div> </template> <script> import { mapState, mapActions } from 'vuex'; export default { data() { return { currentPage: 1, pageSize: 5, searchTerm: '', }; }, computed: { ...mapState(['posts']), filteredPosts() { let filtered = this.posts; if (this.searchTerm) { filtered = filtered.filter(post => post.title.toLowerCase().includes(this.searchTerm.toLowerCase()) ); } const start = (this.currentPage - 1) * this.pageSize; return filtered.slice(start, start + this.pageSize); }, }, methods: { ...mapActions(['fetchPosts']), handleSearch() { this.currentPage = 1; }, nextPage() { this.currentPage += 1; }, prevPage() { if (this.currentPage > 1) this.currentPage -= 1; }, }, async mounted() { await this.fetchPosts(); }, }; </script> <style scoped> button { padding: 0.5rem 1rem; margin-right: 0.5rem; background-color: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer; } button:disabled { background-color: #cccccc; cursor: not-allowed; } button:hover:not(:disabled) { background-color: #0056b3; } ul { list-style: none; padding: 0; } li { padding: 0.5rem 0; border-bottom: 1px solid #ccc; } </style>
// store/messages.js export const state = () => ({ posts: [], }); export const mutations = { setPosts(state, posts) { state.posts = posts; }, }; export const actions = { async fetchPosts({ commit }) { try { const res = await this.$axios.get('/messages'); commit('setPosts', res.data); } catch (error) { console.error(error); } }, };
<!-- pages/posts/index.vue --> <template> <div> <h1>Blog Posts</h1> <PostsList /> </div> </template> <script> import PostsList from '~/components/PostsList.vue'; export default { components: { PostsList, }, }; </script>
-
优化与测试
- 应用性能优化和 SEO 设置,编写基本的单元测试和集成测试。
- 实战:优化项目的性能,确保 SEO 设置符合最佳实践,并编写关键功能的测试用例。
性能优化:
- 使用 Nuxt Image 组件优化图片加载。
- 实现代码分割和懒加载。
- 使用 SWR 或 vue-query 进行数据缓存。
测试示例:
# 安装 Jest 和 Vue Testing Library npm install --save-dev jest @vue/test-utils @testing-library/vue @testing-library/jest-dom @testing-library/user-event @types/jest
// jest.config.js module.exports = { preset: 'ts-jest', testEnvironment: 'jsdom', setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], moduleNameMapper: { '\\.(css|less)$': 'identity-obj-proxy', }, };
// jest.setup.js import '@testing-library/jest-dom';
// tests/unit/Button.test.ts import { render, screen, fireEvent } from '@testing-library/vue'; import Button from '~/components/Button.vue'; test('renders button with label and handles click', async () => { const handleClick = jest.fn(); render(Button, { props: { label: 'Test Button' }, listeners: { click: handleClick }, }); const buttonElement = screen.getByText(/Test Button/i); expect(buttonElement).toBeInTheDocument(); await fireEvent.click(buttonElement); expect(handleClick).toHaveBeenCalledTimes(1); });
// tests/unit/PostCard.test.ts import { render, screen } from '@testing-library/vue'; import PostCard from '~/components/PostCard.vue'; import { Post } from '~/types'; test('renders post card with correct data', () => { const post: Post = { id: '1', title: 'Test Post', content: 'This is a test post.', author: 'Jane Doe', date: '2024-01-01', }; render(PostCard, { props: { post }, }); expect(screen.getByText(/Test Post/i)).toBeInTheDocument(); expect(screen.getByText(/This is a test post./i)).toBeInTheDocument(); expect(screen.getByText(/By Jane Doe on 2024-01-01/i)).toBeInTheDocument(); });
// package.json { "scripts": { "test": "jest" }, "jest": { "testEnvironment": "jsdom", "setupFilesAfterEnv": ["<rootDir>/jest.setup.js"], "moduleNameMapper": { "\\.(css|less)$": "identity-obj-proxy" } } }
# 运行测试 npm run test
覆盖率报告:查看
coverage/lcov-report/index.html
,识别未覆盖的代码区域。
学习活动
- 项目讲解:介绍项目需求和实现步骤,确保学员理解项目目标。
- 动手开发:分阶段完成项目的各个功能模块,逐步实现项目目标。
- 导师指导:定期进行项目进度检查和答疑,确保学员顺利完成项目。
- 项目评审:完成项目后进行代码评审和优化建议,提升代码质量和项目可维护性。
资源与参考
- Nuxt.js 示例项目
- [项目管理工具推荐](https://trello.com/, https://github.com/)
- GitHub 项目管理指南
- 示例代码仓库(GitHub 链接)
模块十一:测试与质量保障
学习目标
- 理解测试的重要性,掌握测试的基本类型
- 学会使用 Jest 和 Vue Testing Library 进行测试
- 实现持续测试与质量保障
详细内容
11.1 测试基础
-
单元测试与集成测试
- 理解单元测试和集成测试的区别与应用场景,测试金字塔的概念。
- 案例:分析一个功能模块的单元测试和集成测试的不同点。
单元测试:测试组件或函数的单个部分,确保其独立工作。
集成测试:测试多个组件或模块的集成,确保它们协同工作。
11.2 测试工具
-
使用 Jest 进行单元测试
- 安装和配置 Jest,编写和运行基本的单元测试。
- 实战:为用户认证模块编写单元测试,确保功能正确性。
# 安装 Jest 及相关依赖 npm install --save-dev jest @vue/test-utils @testing-library/vue @testing-library/jest-dom @testing-library/user-event @types/jest
// jest.config.js module.exports = { preset: 'ts-jest', testEnvironment: 'jsdom', setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], moduleNameMapper: { '\\.(css|less)$': 'identity-obj-proxy', }, };
// jest.setup.js import '@testing-library/jest-dom';
// tests/unit/LoginForm.test.ts import { render, screen, fireEvent } from '@testing-library/vue'; import LoginForm from '~/components/LoginForm.vue'; import axios from 'axios'; jest.mock('axios'); const mockedAxios = axios as jest.Mocked<typeof axios>; test('renders login form and handles login', async () => { mockedAxios.post.mockResolvedValueOnce({ data: { message: 'Login successful' } }); render(LoginForm); const usernameInput = screen.getByPlaceholderText(/Username/i); const passwordInput = screen.getByPlaceholderText(/Password/i); const submitButton = screen.getByText(/Login/i); await fireEvent.update(usernameInput, 'user1'); await fireEvent.update(passwordInput, 'pass123'); await fireEvent.click(submitButton); const successMessage = await screen.findByText(/Login successful/i); expect(successMessage).toBeInTheDocument(); expect(mockedAxios.post).toHaveBeenCalledWith('/login', { username: 'user1', password: 'pass123' }); });
-
使用 Vue Testing Library 进行组件测试
- 安装和配置 Vue Testing Library,编写和运行组件测试,模拟用户交互。
- 案例:为博客文章组件编写测试,确保数据正确渲染和交互功能正常。
// tests/unit/PostCard.test.ts import { render, screen } from '@testing-library/vue'; import PostCard from '~/components/PostCard.vue'; import { Post } from '~/types'; test('renders post card with correct data', () => { const post: Post = { id: '1', title: 'Test Post', content: 'This is a test post.', author: 'Jane Doe', date: '2024-01-01', }; render(PostCard, { props: { post }, }); expect(screen.getByText(/Test Post/i)).toBeInTheDocument(); expect(screen.getByText(/This is a test post./i)).toBeInTheDocument(); expect(screen.getByText(/By Jane Doe on 2024-01-01/i)).toBeInTheDocument(); });
// tests/unit/ProtectedRoute.test.ts import { render, screen } from '@testing-library/vue'; import ProtectedRoute from '~/components/ProtectedRoute.vue'; import { createStore } from 'vuex'; import { auth } from '@nuxtjs/auth-next'; test('renders children when authenticated', () => { const store = createStore({ modules: { auth: { namespaced: true, state: () => ({ loggedIn: true, user: { name: 'John Doe', email: '[email protected]' }, }), getters: { loggedIn: (state) => state.loggedIn, user: (state) => state.user, }, }, }, }); render(ProtectedRoute, { slots: { default: '<div>Protected Content</div>', }, global: { plugins: [store], }, }); expect(screen.getByText(/Protected Content/i)).toBeInTheDocument(); }); test('does not render children when not authenticated', () => { const store = createStore({ modules: { auth: { namespaced: true, state: () => ({ loggedIn: false, user: null, }), getters: { loggedIn: (state) => state.loggedIn, user: (state) => state.user, }, }, }, }); render(ProtectedRoute, { slots: { default: '<div>Protected Content</div>', }, global: { plugins: [store], }, }); expect(screen.queryByText(/Protected Content/i)).not.toBeInTheDocument(); expect(screen.getByText(/Please login to access this page./i)).toBeInTheDocument(); });
11.3 持续测试
-
集成测试到 CI 流程
- 在 GitHub Actions 中配置测试工作流,实现代码提交后的自动化测试。
- 实战:为项目配置 GitHub Actions,使每次代码提交后自动运行测试用例。
# .github/workflows/test.yml name: Run Tests on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup Node.js uses: actions/setup-node@v2 with: node-version: '16' - name: Install Dependencies run: npm install - name: Run Tests run: npm run test
-
实现代码覆盖率
- 配置 Jest 生成代码覆盖率报告,分析和提升测试覆盖率。
- 案例:通过代码覆盖率报告识别未覆盖的代码区域,完善测试用例。
// package.json { "scripts": { "test": "jest --coverage" }, "jest": { "preset": "ts-jest", "testEnvironment": "jsdom", "setupFilesAfterEnv": ["<rootDir>/jest.setup.js"], "moduleNameMapper": { "\\.(css|less)$": "identity-obj-proxy" }, "collectCoverage": true, "collectCoverageFrom": ["components/**/*.{vue,ts}", "pages/**/*.{vue,ts}"], "coverageReporters": ["json", "lcov", "text", "clover"] } }
// 使用 Jest 进行代码覆盖率 // 运行测试并生成覆盖率报告 npm run test
覆盖率报告:查看
coverage/lcov-report/index.html
,识别未覆盖的代码区域。
学习活动
- 视频讲解:介绍测试的基本概念和工具的使用方法。
- 动手练习:为项目中的组件编写单元测试和集成测试。
- 练习项目:实现一个简单的测试覆盖率,确保关键功能的正确性。
资源与参考
模块十二:扩展与高级应用
学习目标
- 掌握无服务器架构,使用 Serverless Functions
- 实现实时功能,如聊天或通知
- 集成 GraphQL,构建高效的数据接口
详细内容
12.1 Serverless Functions
-
无服务器架构简介
- 理解无服务器架构的概念与优势,常见的无服务器平台(如 Vercel Functions、AWS Lambda)。
- 案例:分析无服务器架构在实际项目中的应用场景。
-
使用 Nuxt.js 的 Serverless Functions
- 创建和部署 Serverless Functions,处理异步任务和后台处理。
- 实战:开发一个图像处理的 Serverless Function,实现用户上传图片后自动调整大小。
# 安装 Sharp 进行图像处理 npm install sharp
// server/api/resize-image.js const sharp = require('sharp'); export default async function handler(req, res) { if (req.method === 'POST') { try { const { image, width, height } = req.body; const buffer = Buffer.from(image, 'base64'); const resizedImage = await sharp(buffer) .resize(width, height) .toBuffer(); res.status(200).json({ resizedImage: resizedImage.toString('base64') }); } catch (error) { res.status(500).json({ message: 'Image processing failed' }); } } else { res.setHeader('Allow', ['POST']); res.status(405).end(`Method ${req.method} Not Allowed`); } }
<!-- components/ImageUploader.vue --> <template> <div> <h2>Image Uploader and Resizer</h2> <input type="file" accept="image/*" @change="handleImageChange" /> <div v-if="image"> <img :src="image" alt="Original" width="100" /> <div> <label> Width: <input type="number" v-model.number="width" /> </label> <label> Height: <input type="number" v-model.number="height" /> </label> </div> <button @click="handleResize">Resize Image</button> </div> <div v-if="resizedImage"> <h3>Resized Image</h3> <img :src="resizedImage" alt="Resized" /> </div> </div> </template> <script> export default { data() { return { image: null, resizedImage: null, width: 300, height: 300, }; }, methods: { handleImageChange(e) { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = () => { this.image = reader.result; }; reader.readAsDataURL(file); } }, async handleResize() { if (this.image) { const base64Image = this.image.split(',')[1]; const res = await this.$axios.post('/resize-image', { image: base64Image, width: this.width, height: this.height, }); this.resizedImage = `data:image/jpeg;base64,${res.data.resizedImage}`; } }, }, }; </script> <style scoped> input { margin-bottom: 1rem; padding: 0.5rem; border: 1px solid #ccc; border-radius: 3px; } button { padding: 0.5rem 1rem; background-color: #ffc107; color: black; border: none; border-radius: 3px; cursor: pointer; } button:hover { background-color: #e0a800; } img { margin-top: 1rem; border: 1px solid #ccc; border-radius: 5px; } </style>
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js!</h1> <ImageUploader /> </div> </template> <script> import ImageUploader from '~/components/ImageUploader.vue'; export default { components: { ImageUploader, }, }; </script>
12.2 实时功能
-
集成 WebSockets
- WebSockets 的基本概念与使用场景,在 Nuxt.js 中集成 WebSocket 服务(如 Socket.io)。
- 案例:开发一个实时聊天应用,理解 WebSocket 的通信机制。
-
实现实时聊天或通知功能
- 构建实时聊天应用的基础功能,实现实时通知系统。
- 实战:为博客应用添加实时评论通知,提升用户互动体验。
# 安装 Socket.io npm install socket.io socket.io-client
// server/socket.js const { Server } = require('socket.io'); let io; module.exports = function (server) { if (!io) { io = new Server(server, { cors: { origin: '*', methods: ['GET', 'POST'], }, }); io.on('connection', (socket) => { console.log('New client connected'); socket.on('sendComment', (comment) => { io.emit('newComment', comment); }); socket.on('disconnect', () => { console.log('Client disconnected'); }); }); } return io; };
// server/index.js const express = require('express'); const http = require('http'); const socketHandler = require('./socket'); const app = express(); const server = http.createServer(app); const io = socketHandler(server); app.use(express.json()); app.get('/api/messages', (req, res) => { res.json([]); }); app.post('/api/messages', (req, res) => { const { name, message } = req.body; io.emit('newComment', { name, message, id: Date.now() }); res.status(201).json({ message: 'Comment added successfully' }); }); server.listen(3000, () => { console.log('Server is running on port 3000'); });
<!-- components/RealTimeComments.vue --> <template> <div> <h2>Real-Time Comments</h2> <form @submit.prevent="handleSubmit"> <input name="name" v-model="form.name" placeholder="Your Name" required /> <textarea name="message" v-model="form.message" placeholder="Your Comment" required></textarea> <button type="submit">Send</button> </form> <ul> <li v-for="msg in messages" :key="msg.id"> <strong>{{ msg.name }}:</strong> {{ msg.message }} </li> </ul> </div> </template> <script> import io from 'socket.io-client'; import { ref, onMounted, onUnmounted } from '@nuxtjs/composition-api'; export default { setup() { const socket = io(); const messages = ref([]); const form = ref({ name: '', message: '' }); const handleSubmit = () => { socket.emit('sendComment', form.value); form.value = { name: '', message: '' }; }; const addMessage = (msg) => { messages.value.push(msg); }; onMounted(() => { socket.on('newComment', addMessage); }); onUnmounted(() => { socket.off('newComment', addMessage); socket.disconnect(); }); return { messages, form, handleSubmit }; }, }; </script> <style scoped> form { display: flex; flex-direction: column; max-width: 400px; } input, textarea { margin-bottom: 1rem; padding: 0.5rem; border: 1px solid #ccc; border-radius: 3px; } button { padding: 0.5rem 1rem; background-color: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer; } button:hover { background-color: #0056b3; } ul { list-style: none; padding: 0; } li { padding: 0.5rem; border-bottom: 1px solid #ccc; } </style>
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js!</h1> <RealTimeComments /> </div> </template> <script> import RealTimeComments from '~/components/RealTimeComments.vue'; export default { components: { RealTimeComments, }, }; </script>
12.3 GraphQL 与 Nuxt.js
-
使用 Apollo Client 集成 GraphQL
- Apollo Client 的安装与配置,查询和变更数据的基本操作。
- 案例:在项目中集成 Apollo Client,实现高效的数据查询。
# 安装 Apollo Client npm install @nuxtjs/apollo
// nuxt.config.js export default { modules: ['@nuxtjs/apollo'], apollo: { clientConfigs: { default: { httpEndpoint: 'http://localhost:3000/api/graphql', }, }, }, // 其他配置... };
<!-- components/ApolloPosts.vue --> <template> <div> <h2>GraphQL Posts</h2> <ul> <li v-for="post in posts" :key="post.id"> <strong>{{ post.title }}</strong> by {{ post.author }} </li> </ul> </div> </template> <script> import { gql } from 'graphql-tag'; import { useQuery } from '@vue/apollo-composable'; export default { setup() { const GET_POSTS = gql` query GetPosts { posts { id title content author date } } `; const { result, loading, error } = useQuery(GET_POSTS); return { posts: result, loading, error, }; }, }; </script>
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js!</h1> <ApolloPosts /> </div> </template> <script> import ApolloPosts from '~/components/ApolloPosts.vue'; export default { components: { ApolloPosts, }, }; </script>
-
构建 GraphQL API
- 使用 Apollo Server 创建 GraphQL API,定义 Schema 和 Resolvers。
- 实战:开发一个 GraphQL API,支持文章的查询和创建操作。
# 安装 Apollo Server 和相关依赖 npm install apollo-server-micro graphql
// server/api/graphql.js const { ApolloServer, gql } = require('apollo-server-micro'); const Cors = require('micro-cors'); const cors = Cors(); const typeDefs = gql` type Post { id: ID! title: String! content: String! author: String! date: String! } type Query { posts: [Post!]! } type Mutation { addPost(title: String!, content: String!, author: String!): Post! } `; let posts = [ { id: '1', title: 'First Post', content: 'Content of first post', author: 'John Doe', date: '2024-01-01' }, { id: '2', title: 'Second Post', content: 'Content of second post', author: 'Jane Smith', date: '2024-01-02' }, ]; const resolvers = { Query: { posts: () => posts, }, Mutation: { addPost: (_, { title, content, author }) => { const newPost = { id: (posts.length + 1).toString(), title, content, author, date: new Date().toISOString() }; posts.push(newPost); return newPost; }, }, }; const apolloServer = new ApolloServer({ typeDefs, resolvers }); const startServer = apolloServer.start(); export default cors(async function handler(req, res) { if (req.method === 'OPTIONS') { res.end(); return false; } await startServer; await apolloServer.createHandler({ path: '/api/graphql', })(req, res); }); export const config = { api: { bodyParser: false, }, };
<!-- components/AddPost.vue --> <template> <div> <h2>Add New Post</h2> <form @submit.prevent="handleSubmit"> <input name="title" v-model="form.title" placeholder="Title" required /> <textarea name="content" v-model="form.content" placeholder="Content" required></textarea> <input name="author" v-model="form.author" placeholder="Author" required /> <button type="submit" :disabled="loading">Add Post</button> </form> <p v-if="error">Error adding post.</p> <p v-if="data">Post added successfully!</p> </div> </template> <script> import { gql } from 'graphql-tag'; import { useMutation } from '@vue/apollo-composable'; import { ref } from '@nuxtjs/composition-api'; export default { setup() { const ADD_POST = gql` mutation AddPost($title: String!, $content: String!, $author: String!) { addPost(title: $title, content: $content, author: $author) { id title content author date } } `; const form = ref({ title: '', content: '', author: '' }); const { mutate, onDone, onError, loading, error, data } = useMutation(ADD_POST); const handleSubmit = () => { mutate(form.value); }; return { form, handleSubmit, loading, error, data }; }, }; </script> <style scoped> form { display: flex; flex-direction: column; max-width: 400px; } input, textarea { margin-bottom: 1rem; padding: 0.5rem; border: 1px solid #ccc; border-radius: 3px; } button { padding: 0.5rem 1rem; background-color: #ffc107; color: black; border: none; border-radius: 3px; cursor: pointer; } button:disabled { background-color: #e0a800; cursor: not-allowed; } p { color: red; } </style>
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js with GraphQL!</h1> <AddPost /> <ApolloPosts /> </div> </template> <script> import AddPost from '~/components/AddPost.vue'; import ApolloPosts from '~/components/ApolloPosts.vue'; export default { components: { AddPost, ApolloPosts, }, }; </script>
12.4 集成第三方服务
-
支付集成
- 使用 Stripe 或其他支付服务,实现支付功能。
- 案例:为博客应用添加付费订阅功能,允许用户订阅以获取高级内容。
# 安装 Stripe npm install stripe @types/stripe
// server/api/stripe.js const Stripe = require('stripe'); const stripe = Stripe(process.env.STRIPE_SECRET_KEY); export default async function handler(req, res) { if (req.method === 'POST') { try { const session = await stripe.checkout.sessions.create({ payment_method_types: ['card'], line_items: [ { price_data: { currency: 'usd', product_data: { name: 'Premium Subscription', }, unit_amount: 999, }, quantity: 1, }, ], mode: 'payment', success_url: `${req.headers.origin}/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${req.headers.origin}/cancel`, }); res.status(200).json({ sessionId: session.id }); } catch (err) { res.status(500).json({ error: 'Failed to create session' }); } } else { res.setHeader('Allow', 'POST'); res.status(405).end('Method Not Allowed'); } }
<!-- components/CheckoutButton.vue --> <template> <button @click="handleCheckout" :disabled="loading"> {{ loading ? 'Processing...' : 'Subscribe for $9.99/month' }} </button> </template> <script> export default { data() { return { loading: false, }; }, methods: { async handleCheckout() { this.loading = true; try { const res = await this.$axios.post('/stripe'); const stripe = Stripe('your-publishable-key'); await stripe.redirectToCheckout({ sessionId: res.data.sessionId }); } catch (error) { console.error(error); } finally { this.loading = false; } }, }, }; </script> <style scoped> button { padding: 0.5rem 1rem; background-color: #ffc107; color: black; border: none; border-radius: 3px; cursor: pointer; } button:disabled { background-color: #e0a800; cursor: not-allowed; } button:hover:not(:disabled) { background-color: #d39e00; } </style>
<!-- pages/index.vue --> <template> <div> <h1>Welcome to Nuxt.js!</h1> <CheckoutButton /> </div> </template> <script> import CheckoutButton from '~/components/CheckoutButton.vue'; export default { components: { CheckoutButton, }, }; </script>
注意:确保在
.env
文件中配置 Stripe 的密钥。STRIPE_SECRET_KEY=your-stripe-secret-key NEXTAUTH_SECRET=your-auth-secret
12.5 监控与日志
-
集成监控工具
- 使用 Sentry 或其他监控工具,监控应用错误和性能。
- 实战:为项目集成 Sentry,实时监控和报告错误。
# 安装 Sentry npm install @nuxtjs/sentry
// nuxt.config.js export default { modules: ['@nuxtjs/sentry'], sentry: { dsn: 'your-sentry-dsn', config: {}, }, // 其他配置... };
<!-- components/ErrorBoundary.vue --> <template> <nuxt-error v-if="hasError" :error="error" /> <div v-else> <slot></slot> </div> </template> <script> export default { data() { return { hasError: false, error: null, }; }, errorCaptured(err, vm, info) { this.hasError = true; this.error = err; this.$sentry.captureException(err); return false; }, }; </script>
<!-- layouts/default.vue --> <template> <div> <NavBar /> <ErrorBoundary> <nuxt /> </ErrorBoundary> </div> </template> <script> import NavBar from '~/components/NavBar.vue'; import ErrorBoundary from '~/components/ErrorBoundary.vue'; export default { components: { NavBar, ErrorBoundary, }, }; </script>
SENTRY_DSN=your-sentry-dsn
学习活动
- 视频讲解:演示 Serverless Functions 和实时功能的实现过程。
- 动手练习:在项目中添加 Serverless Functions,集成 WebSockets 实现实时聊天。
- 练习项目:为博客应用添加实时评论功能,使用 GraphQL 构建数据接口。
资源与参考
- Vercel Serverless Functions 文档
- Socket.io 官方文档
- Apollo Client 文档
- GraphQL 官方文档
- Apollo Server 文档
- Stripe 官方文档
- Sentry 官方文档
- 示例代码仓库(GitHub 链接)
模块十三:课程总结与展望
学习目标
- 回顾并巩固课程中学到的核心概念和技能
- 指导学员制定进一步学习和发展的路径
- 提供就业指导和项目展示机会
详细内容
13.1 知识回顾
- 复习核心概念与技能
- 总结 Nuxt.js 的关键功能和最佳实践,回顾各模块的重要知识点。
- 案例:通过问答和讨论,帮助学员巩固所学知识。
13.2 进一步学习路径
-
深入学习 Nuxt.js 高级特性
- 探索 Nuxt.js 的高级功能,如 Middleware、Server Middleware,学习性能优化的深层技巧。
- 实战:为项目添加 Middleware,实现请求的拦截和处理。
// middleware/auth.ts export default function ({ store, redirect }) { if (!store.state.auth.loggedIn) { return redirect('/login'); } }
<!-- pages/dashboard.vue --> <template> <div> <h1>Dashboard</h1> <p>This is a protected page.</p> </div> </template> <script> export default { middleware: 'auth', }; </script>
-
探索相关技术栈
- 了解 Nuxt.js 与其他框架的集成,如 Vue Native,构建跨平台移动应用。
- 案例:展示一个使用 Vue Native 开发的移动应用示例。
13.3 就业指导与项目展示
-
项目作品集制作
- 如何展示和组织项目作品,编写项目文档和 README 文件。
- 实战:指导学员整理和优化个人项目,准备作品集。
# My Blog App ## Description A full-stack blog application built with Nuxt.js, TypeScript, Vuex, Tailwind CSS, and GraphQL. Features include user authentication, real-time comments, and SEO optimization. ## Features - User registration and login with @nuxtjs/auth-next - Create, edit, and delete blog posts - Real-time comments with Socket.io - GraphQL API with Apollo Server - Responsive design with Tailwind CSS ## Installation 1. Clone the repository: ```bash git clone https://github.com/yourusername/my-blog-app.git
- Navigate to the project directory:
cd my-blog-app
- Install dependencies:
npm install
- Create a
.env
file and add the necessary environment variables:GITHUB_CLIENT_ID=your-github-client-id GITHUB_CLIENT_SECRET=your-github-client-secret STRIPE_SECRET_KEY=your-stripe-secret-key SENTRY_DSN=your-sentry-dsn
Usage
npm run dev
License
MIT
-
求职建议与面试技巧
- 准备技术面试的问题和答案,简历优化和求职渠道推荐。
- 案例:模拟技术面试,提供面试问题和答题技巧。
常见面试问题:
- 解释 Nuxt.js 的静态生成(SSG)和服务端渲染(SSR)的区别。
- 如何在 Nuxt.js 中实现 API 路由?
- 什么是 Vue Composition API?举例说明其使用。
- 如何在 Nuxt.js 应用中进行国际化?
- 解释 JWT 的工作原理及其在认证中的应用。
答题技巧:
- 清晰简洁:简明扼要地回答问题,避免冗长。
- 示例说明:使用具体的代码示例或项目经验来支持回答。
- 结构化思维:逻辑清晰地组织答案,分点阐述。
学习活动
- 视频讲解:总结课程内容,分享进一步学习的建议。
- 动手活动:整理和完善个人项目,准备项目展示。
- 互动讨论:参与在线讨论,分享学习心得和求职经验。
资源与参考
教学方式
视频讲解
每个模块包含详细的视频教程,分步骤讲解理论知识和实际操作,结合示范代码演示关键概念。视频内容结构清晰,便于学员跟随学习。
实战项目
通过实际项目开发,帮助学员将所学知识应用于真实场景,提升项目开发和问题解决能力。每个模块都有对应的实战项目,确保学员在实践中巩固所学知识。
课后练习
每个模块配有练习题和编程任务,巩固学习效果,确保学员能够理解并应用所学内容。练习题涵盖理论知识和实际编码,全面提升学员的综合能力。
讨论与答疑
提供在线论坛或社群,学员可以互相交流,分享学习经验,导师实时答疑,解决学习中的疑问。通过互动讨论,促进学员之间的合作与学习。
推荐学习资源
- 官方文档
- 书籍推荐
- 《Mastering Nuxt.js》 by Packt Publishing
- 《Learning Vue.js》 by Alex Banks & Eve Porcello
- 在线社区
- 教程与博客
- 示例代码仓库