文章目录
- 1. 项目介绍
- 2. 开发环境准备
- 3. 设计应用界面
- 4. 创建Vue实例和数据模型
- 5. 实现记事本功能
- 5.1 添加新记事项
- 5.2 删除记事项
- 5.3 清空所有记事
- 6. 添加样式
- 7. 功能扩展:显示创建时间
- 8. 功能扩展:记事项搜索
- 9. 完整代码
- 10. Vue知识点解析
- 10.1 数据绑定
- 10.2 列表渲染
- 10.3 条件渲染
- 10.4 事件处理
- 10.5 计算属性
- 10.6 侦听器
- 10.7 生命周期钩子
- 11. 功能扩展思路
- 12. 总结
1. 项目介绍
本教程将带领新手开发一个基于Vue.js的记事本应用,不需要使用脚手架,仅通过引入Vue.js库即可完成。通过这个项目,你将学习Vue的基础知识,包括:
- 数据绑定和响应式原理
- 列表渲染
- 事件处理
- 计算属性
- 条件渲染
- 本地存储
完成后的记事本应用具有以下功能:
- 显示所有记事项
- 添加新记事项
- 删除单个记事项
- 显示记事项总数
- 一键清空所有记事项
- 数据持久化(浏览器本地存储)
2. 开发环境准备
对于初学者,我们采用最简单的方式搭建环境:通过CDN引入Vue.js。
创建一个基本的HTML文件结构:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue记事本</title>
<!-- 引入Vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<!-- 添加一些基本样式 -->
<style>
/* 样式稍后添加 */
</style>
</head>
<body>
<div id="app">
<!-- 这里将是我们的应用 -->
</div>
<script>
// Vue代码将写在这里
</script>
</body>
</html>
3. 设计应用界面
我们设计一个简洁的记事本界面,包含以下部分:
- 标题
- 输入框和添加按钮
- 记事项列表(每项包含内容和删除按钮)
- 底部统计栏(显示数量和清空按钮)
更新HTML结构:
<div id="app">
<div class="notepad">
<h1>{{ title }}</h1>
<!-- 添加记事项 -->
<div class="add-note">
<input type="text" v-model="newNote" @keyup.enter="addNote" placeholder="请输入记事内容...">
<button @click="addNote">添加</button>
</div>
<!-- 记事项列表 -->
<ul class="note-list">
<li v-for="(note, index) in notes" :key="index">
<span>{{ note.text }}</span>
<button @click="deleteNote(index)">删除</button>
</li>
<li v-if="notes.length === 0" class="empty-note">暂无记事项</li>
</ul>
<!-- 底部统计 -->
<div class="note-stats" v-show="notes.length > 0">
<span>共 {{ notes.length }} 条记事</span>
<button @click="clearAll">清空记事</button>
</div>
</div>
</div>
4. 创建Vue实例和数据模型
在script标签中,我们创建Vue实例并定义数据模型:
new Vue({
el: '#app',
data: {
title: 'Vue记事本',
newNote: '', // 用于存储新输入的记事内容
notes: [], // 用于存储所有记事项
},
// 在实例创建时从本地存储加载数据
created() {
// 从localStorage读取保存的笔记
const savedNotes = JSON.parse(localStorage.getItem('vue-notes') || '[]');
this.notes = savedNotes;
},
// 方法将在下一步定义
methods: {
// 方法将在这里实现
},
// 侦听器:当notes变化时保存到本地存储
watch: {
notes: {
handler(newNotes) {
// 将笔记保存到localStorage
localStorage.setItem('vue-notes', JSON.stringify(newNotes));
},
deep: true
}
}
});
5. 实现记事本功能
5.1 添加新记事项
首先,我们实现添加记事项的功能。在methods对象中添加addNote方法:
methods: {
// 添加新记事项
addNote() {
// 去除首尾空格
const text = this.newNote.trim();
// 如果输入为空,则不添加
if (text === '') {
return;
}
// 创建新记事项对象
const note = {
text: text,
createdAt: new Date().toLocaleString() // 添加创建时间
};
// 将新记事项添加到数组
this.notes.push(note);
// 清空输入框
this.newNote = '';
},
// 其他方法将在下面实现
}
5.2 删除记事项
接下来,实现删除单个记事项的功能:
// 添加到methods对象中
deleteNote(index) {
// 确认删除
if (confirm('确定要删除这条记事吗?')) {
// 使用splice方法从数组中删除指定索引的记事项
this.notes.splice(index, 1);
}
}
5.3 清空所有记事
最后,实现清空所有记事项的功能:
// 添加到methods对象中
clearAll() {
// 确认清空
if (confirm('确定要清空所有记事吗?')) {
// 将记事项数组清空
this.notes = [];
}
}
6. 添加样式
为了让应用看起来更美观,我们添加一些CSS样式:
/* 将这些样式添加到<style>标签中 */
body {
font-family: 'Microsoft YaHei', sans-serif;
background-color: #f5f5f5;
padding: 20px;
}
.notepad {
max-width: 600px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
h1 {
text-align: center;
color: #333;
margin-top: 0;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.add-note {
display: flex;
margin-bottom: 20px;
}
.add-note input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px 0 0 4px;
outline: none;
}
.add-note button {
padding: 10px 15px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
}
.note-list {
list-style: none;
padding: 0;
margin: 0;
}
.note-list li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}
.note-list li:hover {
background-color: #f9f9f9;
}
.note-list li span {
flex: 1;
}
.note-list li button {
padding: 5px 10px;
background-color: #f44336;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.empty-note {
text-align: center;
color: #999;
padding: 20px 0;
}
.note-stats {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
padding-top: 10px;
border-top: 1px solid #eee;
}
.note-stats button {
padding: 5px 10px;
background-color: #ff9800;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
opacity: 0.9;
}
button:active {
opacity: 0.8;
}
7. 功能扩展:显示创建时间
现在让我们扩展记事项,显示每条记事的创建时间。修改列表渲染部分:
<!-- 更新记事项列表 -->
<ul class="note-list">
<li v-for="(note, index) in notes" :key="index">
<div class="note-content">
<span class="note-text">{{ note.text }}</span>
<span class="note-time">{{ note.createdAt }}</span>
</div>
<button @click="deleteNote(index)">删除</button>
</li>
<li v-if="notes.length === 0" class="empty-note">暂无记事项</li>
</ul>
添加对应的样式:
.note-content {
flex: 1;
}
.note-text {
display: block;
margin-bottom: 5px;
}
.note-time {
display: block;
font-size: 12px;
color: #999;
}
8. 功能扩展:记事项搜索
让我们增加一个搜索功能,帮助用户快速找到特定记事项:
在HTML中添加搜索框:
<!-- 在添加记事项的下方添加 -->
<div class="search-note">
<input type="text" v-model="searchText" placeholder="搜索记事...">
</div>
在Vue实例中添加数据和计算属性:
data: {
// 添加到现有data对象中
searchText: '', // 用于存储搜索文本
},
computed: {
// 过滤后的记事项列表
filteredNotes() {
const searchText = this.searchText.trim().toLowerCase();
if (!searchText) {
return this.notes;
}
// 返回包含搜索文本的记事项
return this.notes.filter(note =>
note.text.toLowerCase().includes(searchText)
);
}
}
然后,修改列表渲染,使用过滤后的记事项:
<!-- 更新记事项列表,使用filteredNotes替代notes -->
<ul class="note-list">
<li v-for="(note, index) in filteredNotes" :key="index">
<div class="note-content">
<span class="note-text">{{ note.text }}</span>
<span class="note-time">{{ note.createdAt }}</span>
</div>
<button @click="deleteNote(notes.indexOf(note))">删除</button>
</li>
<li v-if="filteredNotes.length === 0" class="empty-note">
{{ notes.length === 0 ? '暂无记事项' : '没有匹配的记事项' }}
</li>
</ul>
添加搜索框样式:
.search-note {
margin-bottom: 20px;
}
.search-note input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
outline: none;
box-sizing: border-box;
}
9. 完整代码
以下是完整的HTML文件代码:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue记事本</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<style>
body {
font-family: 'Microsoft YaHei', sans-serif;
background-color: #f5f5f5;
padding: 20px;
}
.notepad {
max-width: 600px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
h1 {
text-align: center;
color: #333;
margin-top: 0;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.add-note {
display: flex;
margin-bottom: 20px;
}
.add-note input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px 0 0 4px;
outline: none;
}
.add-note button {
padding: 10px 15px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
}
.search-note {
margin-bottom: 20px;
}
.search-note input {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
outline: none;
box-sizing: border-box;
}
.note-list {
list-style: none;
padding: 0;
margin: 0;
}
.note-list li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}
.note-list li:hover {
background-color: #f9f9f9;
}
.note-content {
flex: 1;
}
.note-text {
display: block;
margin-bottom: 5px;
}
.note-time {
display: block;
font-size: 12px;
color: #999;
}
.note-list li button {
padding: 5px 10px;
background-color: #f44336;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.empty-note {
text-align: center;
color: #999;
padding: 20px 0;
}
.note-stats {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
padding-top: 10px;
border-top: 1px solid #eee;
}
.note-stats button {
padding: 5px 10px;
background-color: #ff9800;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
opacity: 0.9;
}
button:active {
opacity: 0.8;
}
</style>
</head>
<body>
<div id="app">
<div class="notepad">
<h1>{{ title }}</h1>
<!-- 添加记事项 -->
<div class="add-note">
<input type="text" v-model="newNote" @keyup.enter="addNote" placeholder="请输入记事内容...">
<button @click="addNote">添加</button>
</div>
<!-- 搜索记事项 -->
<div class="search-note">
<input type="text" v-model="searchText" placeholder="搜索记事...">
</div>
<!-- 记事项列表 -->
<ul class="note-list">
<li v-for="(note, index) in filteredNotes" :key="index">
<div class="note-content">
<span class="note-text">{{ note.text }}</span>
<span class="note-time">{{ note.createdAt }}</span>
</div>
<button @click="deleteNote(notes.indexOf(note))">删除</button>
</li>
<li v-if="filteredNotes.length === 0" class="empty-note">
{{ notes.length === 0 ? '暂无记事项' : '没有匹配的记事项' }}
</li>
</ul>
<!-- 底部统计 -->
<div class="note-stats" v-show="notes.length > 0">
<span>共 {{ notes.length }} 条记事</span>
<button @click="clearAll">清空记事</button>
</div>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
title: 'Vue记事本',
newNote: '',
notes: [],
searchText: ''
},
computed: {
// 过滤后的记事项列表
filteredNotes() {
const searchText = this.searchText.trim().toLowerCase();
if (!searchText) {
return this.notes;
}
// 返回包含搜索文本的记事项
return this.notes.filter(note =>
note.text.toLowerCase().includes(searchText)
);
}
},
created() {
// 从localStorage读取保存的笔记
const savedNotes = JSON.parse(localStorage.getItem('vue-notes') || '[]');
this.notes = savedNotes;
},
methods: {
// 添加新记事项
addNote() {
// 去除首尾空格
const text = this.newNote.trim();
// 如果输入为空,则不添加
if (text === '') {
return;
}
// 创建新记事项对象
const note = {
text: text,
createdAt: new Date().toLocaleString()
};
// 将新记事项添加到数组
this.notes.push(note);
// 清空输入框
this.newNote = '';
},
// 删除记事项
deleteNote(index) {
// 确认删除
if (confirm('确定要删除这条记事吗?')) {
// 使用splice方法从数组中删除指定索引的记事项
this.notes.splice(index, 1);
}
},
// 清空所有记事
clearAll() {
// 确认清空
if (confirm('确定要清空所有记事吗?')) {
// 将记事项数组清空
this.notes = [];
}
}
},
watch: {
// 监听notes变化,保存到localStorage
notes: {
handler(newNotes) {
localStorage.setItem('vue-notes', JSON.stringify(newNotes));
},
deep: true
}
}
});
</script>
</body>
</html>
10. Vue知识点解析
通过这个项目,我们学习了以下Vue的知识点:
10.1 数据绑定
v-model
:用于表单输入和应用数据之间的双向绑定- 插值表达式
{{ }}
:显示变量内容
10.2 列表渲染
v-for
:遍历数组渲染列表项:key
:为列表项提供唯一标识符,帮助Vue优化渲染
10.3 条件渲染
v-if
/v-else
:根据条件决定是否渲染元素v-show
:根据条件切换元素的显示和隐藏(CSS的display属性)
10.4 事件处理
@click
(即v-on:click
):监听点击事件@keyup.enter
:监听按下回车键事件
10.5 计算属性
computed
:基于响应式依赖进行缓存,只有依赖更新时才重新计算
10.6 侦听器
watch
:监听数据变化,执行相应操作deep
选项:深度监听对象内部变化immediate
选项:组件创建时立即执行一次
10.7 生命周期钩子
created
:实例创建后执行代码(如从本地存储加载数据)
11. 功能扩展思路
以下是一些可以进一步扩展记事本功能的思路:
-
优先级标记:允许用户为记事项设置不同的优先级,并可按优先级排序。
-
分类功能:添加分类标签,对记事项进行分组管理。
-
编辑功能:允许用户编辑已添加的记事项。
-
完成状态:添加复选框,标记记事项为已完成或未完成状态。
-
拖拽排序:使用拖拽功能调整记事项的顺序。
-
导入/导出:支持将记事数据导出为文件,或从文件导入记事。
-
日期筛选:按照创建日期筛选记事项。
-
数据统计:显示更多统计信息,如已完成/未完成的任务数量。
-
夜间模式:添加切换夜间/日间模式的功能。
-
云同步:结合后端服务,实现记事内容的云端同步。
12. 总结
通过本教程,我们使用Vue.js构建了一个功能完整的记事本应用。虽然这是一个小型应用,但它涵盖了Vue的核心概念和实践技巧。作为新手,你可以通过这个项目了解Vue的数据驱动、组件化和响应式的特性,为进一步学习Vue打下基础。
要深入学习Vue,建议接下来了解Vue CLI、Vue Router、Vuex等进阶工具和概念,并尝试使用组件化的方式重构此应用。