admin 管理员组

文章数量: 887006

将登录系统和 To-Do List 系统结合在一起,我们可以创建一个简单的 Vue 应用,该应用具备用户登录功能,并在用户登录后展示其个人 To-Do List。用户可以添加、删除、标记任务完成状态等。我们可以使用 Pinia 来管理用户登录状态和 To-Do 列表数据。

这个应用整体分为几大模块:

  • 主文件 (App.vuemain.js)
  • 路由管理 (router/index.js)
  • 状态管理 (stores/userStore.js)
  • 页面组件 (Login.vueTodoList.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.jsApp.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 是如何被管理的。

解释 createRoutercreateWebHistory

  1. createRouter:这是 Vue Router 提供的一个方法,用于创建路由器实例。创建的路由器实例能帮助应用在不同页面间导航,比如在登录页面和待办事项页面之间切换。

  2. createWebHistory:这是 Vue Router 提供的三种路由模式之一,它决定了 URL 的管理方式。createWebHistory 使用浏览器内置的 History API,URL 变得更“干净”、更现代,不包含 # 符号。

    • 示例 URL(Web History):https://example/todolist
    • 与其他模式对比
      • createWebHashHistory:URL 中会带有 # 符号(哈希模式),像 https://example/#/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,你帮我类比一下,解释一下每一行的作用,帮助我触类旁通

逐行解释和类比

  1. export const useUserStore = defineStore('user', {...});

    • 作用:创建并导出一个“数据仓库”函数,用于管理用户的登录信息和待办事项。
    • 类比
      • Python:类似于定义一个类,比如 class UserStore
      • C语言:可以类比成创建一个带函数指针的结构体,用于集中管理信息和行为。
  2. state: () => ({...})

    • 作用state 是一个函数,返回一个对象用于存储数据。这里的数据是 nameisLoggedIntodos
    • 类比
      • Pythonstate 相当于类中的实例属性。
      • C语言:可以理解为结构体的成员变量,用于存储用户状态信息。
  3. name: ''

    • 作用:用户的名字,初始为空。
    • 类比
      • Python/C:相当于一个字符串变量 name = ""
  4. isLoggedIn: false

    • 作用:用户是否已登录,初始值为 false
    • 类比
      • Python/C:一个布尔变量 isLoggedIn = False;isLoggedIn = false;
  5. todos: []

    • 作用:待办事项列表,初始为空数组。
    • 类比
      • Pythontodos = [],即一个空列表。
      • C语言:可以想象成动态分配的指针数组。

actions 部分

actions 包含对数据执行操作的方法,类似于类中的方法或 C 中的函数指针。它们可以直接修改 state 中的数据。

login(userName)

  • 作用:将 name 更新为 userName,并设置 isLoggedIntrue
  • 类比
    • Pythondef login(self, userName): self.name = userName; self.isLoggedIn = True
    • C语言:类似于更新结构体成员的函数 void login(UserStore* store, char* userName)

logout()

  • 作用:清空 name 并设置 isLoggedInfalse
  • 类比
    • Pythondef logout(self): self.name = ""; self.isLoggedIn = False
    • C语言:更新 UserStore 成员变量的值。

addTodo(task)

  • 作用:添加一个新的待办事项,task 是待办内容,默认为未完成。
  • 类比
    • Pythonself.todos.append({"task": task, "done": False})
    • C语言:可以想象成向结构体的指针数组添加新的字符串。

toggleTodo(index)

  • 作用:切换指定任务的完成状态。
  • 类比
    • Pythonself.todos[index]["done"] = not self.todos[index]["done"]
    • C语言:可以想象成通过索引修改数组成员的布尔值。

deleteTodo(index)

  • 作用:根据索引删除待办事项。
  • 类比
    • Pythonself.todos.pop(index)
    • C语言:手动管理数组的内存,移除指定项。

6.页面组件 Login.vueTodoList.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 是当前循环的待办项,每个待办项有 taskdone 属性,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.donetrue,则 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),实现不同页面间的切换与用户权限管理。

本文标签: Vue