将登录系统和 To-Do List 系统结合在一起,我们可以创建一个简单的 Vue 应用,该应用具备用户登录功能,并在用户登录后展示其个人 To-Do List。用户可以添加、删除、标记任务完成状态等。我们可以使用 Pinia 来管理用户登录状态和 To-Do 列表数据。
这个应用整体分为几大模块:
- 主文件 (
App.vue
和main.js
) - 路由管理 (
router/index.js
) - 状态管理 (
stores/userStore.js
) - 页面组件 (
Login.vue
和TodoList.vue
)
1. 设置项目和安装 Pinia
在有管理员权限下的终端创建项目并测试
npm create vite@latest day4 -- -- template vue
npm install
npm install element-plus
npm run dev
在项目目录里运行下面的命令安装 Pinia和Vue Router:
npm install pinia
npm install pinia vue-router
2. 文件结构
假设项目结构如下:
src
├── App.vue
├── main.js
├── router
│ └── index.js // 配置 Vue Router
├── stores
│ └── userStore.js // Pinia 状态管理
└── views
├── Login.vue // 登录界面
└── TodoList.vue // To-Do List 界面
3. 主文件 main.js
和 App.vue
main.js
:应用启动的核心
main.js
文件是应用的入口,负责启动整个 Vue 应用并将其挂载到页面上。
// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import { createPinia } from 'pinia';
import router from './router';
// 创建应用实例,并配置应用的核心插件
const app = createApp(App);
app.use(createPinia()); // 安装 Pinia,用于管理全局状态
app.use(router); // 安装路由,用于页面跳转
app.mount('#app'); // 将应用挂载到页面上
- createApp(App):创建一个 Vue 应用实例,准备将应用展示在网页上。
- app.use(createPinia()):安装 Pinia,用于全局管理数据。
- app.use(router):安装路由,用于页面跳转。
- app.mount('#app'):把应用挂载到页面上,通过 HTML 中的
id="app"
来渲染整个应用。
App.vue
:页面的主要框架
App.vue
是应用的根组件,它负责展示页面内容。
<!-- src/App.vue -->
<template>
<div id="app">
<router-view /> <!-- 在此处展示不同页面内容 -->
</div>
</template>
<script setup>
// 不需要添加任何逻辑,因为这里只是一个展示页面内容的容器
</script>
<style>
/* 添加基础样式 */
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
h1, h2 {
font-weight: normal;
color: #4caf50;
}
</style>
<router-view />
:这是路由的占位符。根据用户访问的网址,Vue Router 会决定显示 Login.vue
还是 TodoList.vue
,这让应用可以在不同页面之间切换。
4.路由管理 router/index.js
router/index.js
用于设置页面之间的导航。比如用户在登录页面登录后,可以跳转到待办事项页面。
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Login from '../views/Login.vue';
import TodoList from '../views/TodoList.vue';
const routes = [
{ path: '/', name: 'Login', component: Login },
{ path: '/todolist', name: 'TodoList', component: TodoList },
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
routes
:定义了应用中的路径与组件的对应关系。/
表示首页路径,加载Login.vue
;/todolist
加载TodoList.vue
。createRouter
:创建一个路由实例,用于管理页面间的导航。export default router
:将路由实例导出,供main.js
引入和使用。
问题:
const router = createRouter({
history: createWebHistory(),
routes,
});
这个是什么用法,.createWebHistory是什么?
createRouter
是 Vue Router 4.x 中的一个函数,用于创建路由实例,用来管理 Vue 应用中的页面导航。而createWebHistory
是一种路由模式,决定了应用的 URL 是如何被管理的。解释
createRouter
和createWebHistory
createRouter
:这是 Vue Router 提供的一个方法,用于创建路由器实例。创建的路由器实例能帮助应用在不同页面间导航,比如在登录页面和待办事项页面之间切换。
createWebHistory
:这是 Vue Router 提供的三种路由模式之一,它决定了 URL 的管理方式。createWebHistory
使用浏览器内置的 History API,URL 变得更“干净”、更现代,不包含#
符号。
- 示例 URL(Web History):
https://example.com/todolist
- 与其他模式对比:
createWebHashHistory
:URL 中会带有#
符号(哈希模式),像https://example.com/#/todolist
。这种方式适用于无需后端支持的单页面应用。createMemoryHistory
:所有导航都在内存中进行,常用于服务器渲染或一些测试环境。
5.状态管理 stores/userStore.js
userStore.js
负责管理登录状态和用户的待办事项。它通过 Pinia 提供了统一的数据存储。
// src/stores/userStore.js
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
isLoggedIn: false,
todos: [],
}),
actions: {
login(userName) {
this.name = userName;
this.isLoggedIn = true;
},
logout() {
this.name = '';
this.isLoggedIn = false;
},
addTodo(task) {
this.todos.push({ task, done: false });
},
toggleTodo(index) {
this.todos[index].done = !this.todos[index].done;
},
deleteTodo(index) {
this.todos.splice(index, 1);
},
},
});
- state:用来存储用户状态的数据。
name
:记录用户名。isLoggedIn
:记录用户是否登录。todos
:一个待办事项数组,每个事项包含task
(任务名称)和done
(是否完成)。
- actions:定义了更新状态的方法。
login
:接受用户名参数,将用户登录。logout
:将用户登出,清空用户名和登录状态。addTodo
:添加新的待办事项。toggleTodo
:切换任务的完成状态。deleteTodo
:删除待办事项。
问题:
export const useUserStore = defineStore('user', {
state: () => ({
name: '',
isLoggedIn: false,
todos: [],
}),
actions: {
login(userName) {
this.name = userName;
this.isLoggedIn = true;
},
logout() {
this.name = '';
this.isLoggedIn = false;
},
addTodo(task) {
this.todos.push({ task, done: false });
},
toggleTodo(index) {
this.todos[index].done = !this.todos[index].done;
},
deleteTodo(index) {
this.todos.splice(index, 1);
},
},
});
这个我不理解,我几乎没学过java,但我学过c和python,你帮我类比一下,解释一下每一行的作用,帮助我触类旁通
逐行解释和类比
export const useUserStore = defineStore('user', {...});
- 作用:创建并导出一个“数据仓库”函数,用于管理用户的登录信息和待办事项。
- 类比:
- Python:类似于定义一个类,比如
class UserStore
。- C语言:可以类比成创建一个带函数指针的结构体,用于集中管理信息和行为。
state: () => ({...})
- 作用:
state
是一个函数,返回一个对象用于存储数据。这里的数据是name
、isLoggedIn
、todos
。- 类比:
- Python:
state
相当于类中的实例属性。- C语言:可以理解为结构体的成员变量,用于存储用户状态信息。
name: ''
- 作用:用户的名字,初始为空。
- 类比:
- Python/C:相当于一个字符串变量
name = ""
。
isLoggedIn: false
- 作用:用户是否已登录,初始值为
false
。- 类比:
- Python/C:一个布尔变量
isLoggedIn = False;
或isLoggedIn = false;
。
todos: []
- 作用:待办事项列表,初始为空数组。
- 类比:
- Python:
todos = []
,即一个空列表。- C语言:可以想象成动态分配的指针数组。
actions
部分
actions
包含对数据执行操作的方法,类似于类中的方法或 C 中的函数指针。它们可以直接修改state
中的数据。
login(userName)
- 作用:将
name
更新为userName
,并设置isLoggedIn
为true
。- 类比:
- Python:
def login(self, userName): self.name = userName; self.isLoggedIn = True
- C语言:类似于更新结构体成员的函数
void login(UserStore* store, char* userName)
logout()
- 作用:清空
name
并设置isLoggedIn
为false
。- 类比:
- Python:
def logout(self): self.name = ""; self.isLoggedIn = False
- C语言:更新
UserStore
成员变量的值。
addTodo(task)
- 作用:添加一个新的待办事项,
task
是待办内容,默认为未完成。- 类比:
- Python:
self.todos.append({"task": task, "done": False})
- C语言:可以想象成向结构体的指针数组添加新的字符串。
toggleTodo(index)
- 作用:切换指定任务的完成状态。
- 类比:
- Python:
self.todos[index]["done"] = not self.todos[index]["done"]
- C语言:可以想象成通过索引修改数组成员的布尔值。
deleteTodo(index)
- 作用:根据索引删除待办事项。
- 类比:
- Python:
self.todos.pop(index)
- C语言:手动管理数组的内存,移除指定项。
6.页面组件 Login.vue
和 TodoList.vue
Login.vue
:用户登录界面
登录页面让用户输入用户名并点击“登录”按钮,完成登录后跳转到 TodoList.vue
。
<!-- src/views/Login.vue -->
<template>
<div class="login-container">
<h1>Welcome to the App</h1>
<input v-model="userName" placeholder="Enter your name" />
<button @click="handleLogin">Login</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useUserStore } from '../stores/userStore';
import { useRouter } from 'vue-router';
const userStore = useUserStore();
const router = useRouter();
const userName = ref('');
const handleLogin = () => {
if (userName.value.trim()) {
userStore.login(userName.value.trim());
router.push({ name: 'TodoList' });
}
};
</script>
<style scoped>
/* 样式:布局居中、按钮美化 */
</style>
- v-model="userName":使用
v-model
双向绑定让输入框和userName
变量同步。 - handleLogin:当点击“登录”按钮时,检查
userName
是否为空,如果有值,则调用userStore.login(userName)
将用户登录,并跳转到TodoList
页面。
TodoList.vue
:待办事项界面
TodoList.vue
页面展示用户的待办事项列表,并支持添加、删除和标记完成。
<template>
<div class="todo-container">
<h2>{{ userStore.name }}'s To-Do List</h2>
<button @click="handleLogout" class="logout-btn">Logout</button>
<div class="input-container">
<input v-model="newTask" placeholder="Add a new task" @keyup.enter="addTask" />
<button @click="addTask">Add</button>
</div>
<ul class="ul-container">
<li v-for="(todo, index) in userStore.todos" :key="index" class="todo-item">
<input type="checkbox" v-model="todo.done" class="checkboxs"/>
<span :class="{ done: todo.done }">{{ todo.task }}</span>
<button @click="removeTask(index)" class="delete-btn">Delete</button>
</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useUserStore } from '../stores/userStore';
import { useRouter } from 'vue-router';
const userStore = useUserStore();
const router = useRouter();
const newTask = ref('');
const addTask = () => {
if (newTask.value.trim()) {
userStore.addTodo(newTask.value.trim());
newTask.value = '';
}
};
const removeTask = (index) => {
userStore.deleteTodo(index);
};
const handleLogout = () => {
userStore.logout();
router.push({ name: 'Login' });
};
</script>
<style scoped>
.todo-container {
width: 80%;
max-width: 500px;
margin: auto;
text-align: center;
margin-top: 50px;
}
.input-container {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
input {
padding: 10px;
width: 80%;
border: 1px solid #ccc;
border-radius: 5px;
margin-right: 5px;
}
button {
padding: 10px;
background-color: #4caf50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.logout-btn {
background-color: #f44336;
margin-bottom: 20px;
}
.ul-container {
list-style: none;
padding: 0;
margin: 0 auto; /* 将列表容器居中 */
text-align: center; /* 居中对齐文本 */
width: 80%; /* 设置宽度,例如 80% */
max-width: 600px; /* 最大宽度 */
height: 400px; /* 设置高度 */
overflow-y: auto; /* 允许垂直滚动(如果高度超出) */
border: 1px solid #c4f1ec; /* 可选:添加边框,方便查看宽高 */
background-color: #cdf3d4; /* 可选:背景颜色 */
}
.checkboxs{
width: 80%; /* 设置宽度,例如 80% */
max-width: 20px; /* 最大宽度 */
}
.todo-item {
display: flex;
align-items: center;
justify-content: space-between;
margin: 10px 0;
}
.todo-item input[type="checkbox"] {
margin-right: 10px;
}
.done {
text-decoration: line-through;
}
.delete-btn {
background-color: #f44336;
color: white;
border: none;
padding: 5px 10px;
border-radius: 5px;
cursor: pointer;
}
</style>
- addTask:用来添加新任务。
- removeTask:用来删除特定的任务。
- handleLogout:登出后返回登录页面。
问题:
<div class="input-container">
<input v-model="newTask" placeholder="Add a new task" @keyup.enter="addTask" />
<button @click="addTask">Add</button>
</div>
keyup.enter是什么?
@keyup.enter="addTask"
:
@keyup
监听keyup
事件,即当用户松开一个键时触发。.enter
是修饰符,仅在用户松开 Enter 键时触发事件。addTask
是调用的方法名,在用户按下 Enter 键后,调用addTask
方法。作用
用户在输入框中输入任务内容时,按下 Enter 键 会自动调用
addTask
方法,添加任务到待办列表,而无需点击“Add”按钮。
<ul>
<li v-for="(todo, index) in userStore.todos" :key="index" class="todo-item">
<input type="checkbox" v-model="todo.done" />
<span :class="{ done: todo.done }">{{ todo.task }}</span>
<button @click="removeTask(index)" class="delete-btn">Delete</button>
</li>
</ul>
这里面每一行是什么意思?
<ul> ... </ul>
- 作用:创建一个无序列表,用于显示多个待办事项(
<ul>
是 HTML 的标签表示无序列表)。- 渲染内容:列表项将放在
<ul>
标签内显示。
<li v-for="(todo, index) in userStore.todos" :key="index" class="todo-item">
v-for="(todo, index) in userStore.todos"
:Vue 指令,用于遍历userStore.todos
数组。todo
是当前循环的待办项,每个待办项有task
和done
属性,index
是当前项目的索引。:key="index"
:每个列表项的唯一标识,有助于 Vue 更高效地跟踪和更新每一项。class="todo-item"
:添加一个 CSS 类名todo-item
,方便样式控制。
<input type="checkbox" v-model="todo.done" />
- 作用:复选框,用于显示和更新每个待办事项的完成状态。
v-model="todo.done"
:双向绑定,将待办事项的done
状态绑定到复选框。勾选或取消勾选复选框时,todo.done
的值会自动更新。
<span :class="{ done: todo.done }">{{ todo.task }}</span>
{{ todo.task }}
:显示待办事项的任务内容(文本)。:class="{ done: todo.done }"
:动态绑定类名。若todo.done
为true
,则span
元素将会有done
类,可以用 CSS 设置完成任务的样式,比如显示删除线。
<button @click="removeTask(index)" class="delete-btn">Delete</button>
- 作用:删除按钮,每次点击删除当前待办项。
@click="removeTask(index)"
:Vue 指令,绑定click
事件,点击时调用removeTask
方法并传入index
,从userStore.todos
中移除指定的待办项。class="delete-btn"
:CSS 类名delete-btn
,用于样式设置,比如调整按钮的外观。
7.项目效果
- 登录页面:用户可以在此输入用户名并点击登录。登录后会自动跳转到 To-Do List 页面。
- To-Do List 页面:用户可以添加任务、标记完成状态、删除任务,并包含“退出登录”按钮。点击“退出登录”会返回到登录页面。
第五天任务:学习 Vue 3 中的路由管理(Vue Router),实现不同页面间的切换与用户权限管理。