前文,我们实现了一个使用Vue 3构建的图书管理系统的html部分,接下来我们看一下JavaScript逻辑部分。html部分以及整个系统的完整代码可以查看我的文章:基于Vue.js的图书管理系统前端界面设计-CSDN博客
一、Vue 3简介
Vue 3是渐进式JavaScript框架Vue.js的第三个主要版本,它延续了Vue易于上手、灵活性高和高效的特点,并在性能、可维护性和开发体验上进行了显著提升。
(一)性能优化
(1)Proxy响应式系统:Vue 3使用ES6的Proxy对象实现响应式系统,相较于Vue 2的Object.defineProperty,它能更好地支持嵌套对象的响应式追踪,减少了初始化时的递归遍历开销,同时能够检测到对象属性的新增和删除,使响应式数据的处理更加高效和全面。
(2)编译优化:Vue 3的编译器引入了静态提升、事件缓存等优化策略。静态提升可以将模板中的静态节点提取出来,避免在每次渲染时重复创建,减少了虚拟DOM的比对和创建开销;事件缓存则可以避免在每次渲染时重新创建事件处理函数,提高了事件绑定的性能。
(二)Composition API
(1)逻辑复用与组织:Composition API提供了一种基于函数的方式来组织组件逻辑,使得代码更加模块化和可复用。通过setup函数,开发者可以将相关的逻辑封装在一个函数中,并在组件中复用这些逻辑,解决了Vue 2中选项式API在处理复杂逻辑时可能导致的代码碎片化问题。
(2)更好的类型推导:由于Composition API基于函数,它与TypeScript的集成更加自然,能够提供更好的类型推导和静态类型检查,有助于开发者在开发过程中更早地发现错误,提高代码的健壮性。
(三)更好的Tree - Shaking支持
Vue 3的代码结构进行了重构,使得它能够更好地支持Tree - Shaking。Tree - Shaking是一种优化技术,它可以在打包时移除未使用的代码,从而减小打包文件的体积,提高应用的加载速度。
(四)新特性与改进
(1)Teleport组件:Teleport组件允许开发者将组件的内容渲染到DOM中的其他位置,这在处理模态框、下拉菜单等需要脱离文档流的组件时非常有用。
(2)Suspense组件:Suspense组件可以在异步组件加载完成之前显示一个加载状态,简化了异步组件的处理逻辑,提高了用户体验。
(3)自定义渲染器API:Vue 3提供了自定义渲染器API,开发者可以使用该API创建自定义的渲染器,将Vue组件渲染到不同的目标平台,如Canvas、WebGL等。
在这个图书管理系统的index.html文件中,虽然没有直接应用Vue 3完整的JavaScript代码实现,但HTML中使用的v-if、v-for、v-model、@click等指令以及响应式数据绑定(如 :class、{{ }} 插值),它使用了Vue 3的响应式系统和模板语法来构建交互式的图书管理系统界面,如导航栏的状态切换、图书列表的展示和分页、借阅记录的显示等功能。
二、JavaScript逻辑部分的树形结构
以下是该index.html文件中script部分的树形结构图,并对每个属性、方法进行简要说明:
script
├── Vue 应用逻辑(setup函数)
│ ├── 导航相关
│ │ ├── isScrolled - 布尔值,用于判断页面是否滚动,当页面滚动时可能会改变导航栏的样式
│ │ ├── currentView - 字符串,用于表示当前显示的视图,如 'dashboard'、'books'、'borrows'、'users' 等
│ │ ├── isMobileMenuOpen - 布尔值,用于控制移动端菜单的展开和收起状态
│ ├── 数据模型
│ │ ├── books - 数组,存储系统中所有图书的信息,包含每本图书的详细数据
│ │ ├── borrowedBooksCount - 数字,记录当前已借出图书的数量
│ │ ├── users - 数组,存储系统中所有用户的信息,包含每个用户的详细数据
│ │ ├── overdueBooksCount - 数字,记录当前逾期未还图书的数量
│ │ ├── recentBorrows - 数组,存储最近的图书借阅记录,包含借阅的详细信息
│ ├── 搜索和筛选
│ │ ├── bookSearchQuery - 字符串,用于图书管理视图中的搜索输入框,用户输入的搜索关键词
│ │ ├── bookCategoryFilter - 字符串,用于图书管理视图中按图书分类进行筛选,可选值如 '计算机'、'文学' 等
│ │ ├── bookStatusFilter - 字符串,用于图书管理视图中按图书状态进行筛选,可选值如 'available'、'borrowed'
│ │ ├── borrowSearchQuery - 字符串,用于借阅管理视图中的搜索输入框,用户输入的搜索关键词
│ │ ├── borrowStatusFilter - 字符串,用于借阅管理视图中按借阅状态进行筛选,可选值如 'borrowed'、'returned'
│ ├── 分页
│ │ ├── currentPage - 数字,用于图书列表的分页,当前显示的页码
│ │ ├── booksPerPage - 数字,用于图书列表的分页,每页显示的图书数量
│ ├── 模态框
│ │ ├── isBookModalOpen - 布尔值,用于控制图书编辑或添加模态框的显示和隐藏
│ │ ├── currentBook - 对象,当前正在编辑或添加的图书信息
│ ├── 表单数据
│ │ ├── newBookTitle - 字符串,添加或编辑图书时的图书标题
│ │ ├── newBookAuthor - 字符串,添加或编辑图书时的图书作者
│ │ ├── newBookCategory - 字符串,添加或编辑图书时的图书分类
│ │ ├── newBookCover - 字符串,添加或编辑图书时的图书封面链接
│ │ ├── newBookIsBorrowed - 布尔值,添加或编辑图书时的图书借阅状态
│ ├── 计算属性
│ │ ├── filteredBooks - 根据搜索关键词、分类和状态筛选后的图书列表
│ │ ├── totalPages - 图书列表的总页数
│ │ ├── displayedBooks - 当前页要显示的图书列表
│ │ ├── filteredBorrows - 根据搜索关键词和借阅状态筛选后的借阅记录列表
│ ├── 方法
│ │ ├── toggleMobileMenu - 函数,用于切换移动端菜单的展开和收起状态
│ │ ├── openBookModal - 函数,用于打开图书编辑或添加的模态框,接受一个图书对象作为参数,若为 null 则表示添加新图书
│ │ ├── closeBookModal - 函数,用于关闭图书编辑或添加的模态框
│ │ ├── saveBook - 函数,用于保存新添加或编辑的图书信息
│ │ ├── deleteBook - 函数,用于删除指定 ID 的图书
│ │ ├── prevPage - 函数,用于图书列表分页时切换到上一页
│ │ ├── nextPage - 函数,用于图书列表分页时切换到下一页
│ │ ├── isOverdue - 函数,接受一个日期作为参数,判断该日期是否逾期,返回布尔值
│ ├── 初始化图表
│ │ ├── initBorrowChart - 函数,用于初始化借阅趋势图表
│ │ ├── initCategoryChart - 函数,用于初始化图书分类统计图表
│ ├── 生命周期钩子
│ │ ├── onMounted - 生命周期钩子,在组件挂载后执行,用于初始化数据和图表
│ ├── return
│ ├── isScrolled
│ ├── currentView
│ ├── isMobileMenuOpen
│ ├── books
│ ├── borrowedBooksCount
│ ├── users
│ ├── overdueBooksCount
│ ├── recentBorrows
│ ├── bookSearchQuery
│ ├── bookCategoryFilter
│ ├── bookStatusFilter
│ ├── borrowSearchQuery
│ ├── borrowStatusFilter
│ ├── currentPage
│ ├── booksPerPage
│ ├── isBookModalOpen
│ ├── currentBook
│ ├── newBookTitle
│ ├── newBookAuthor
│ ├── newBookCategory
│ ├── newBookCover
│ ├── newBookIsBorrowed
│ ├── filteredBooks
│ ├── totalPages
│ ├── displayedBooks
│ ├── filteredBorrows
│ ├── toggleMobileMenu
│ ├── openBookModal
│ ├── closeBookModal
│ ├── saveBook
│ ├── deleteBook
│ ├── prevPage
│ ├── nextPage
│ ├── isOverdue
这个树形结构详细展示了一个完整的Vue 3 setup()函数可能包含的各个部分(不是全是属性、方法)。Composition API提供了一种基于函数的方式来组织组件逻辑,使得代码更加模块化和可复用。通过setup函数,开发者可以将相关的逻辑封装在一个函数中,并在组件中复用这些逻辑。当前的setup()函数中包含了:导航相关、数据模型、搜索和筛选、分页、模态框、表单数据、计算属性(很多个)、方法(很多个)、初始化图表、生命周期钩子以及return。
在实际的HTML文件中,Vue的响应式数据和方法通常是通过Vue.createApp()创建应用实例并在实例中定义的,但在我们这个系统的HTML文件里是直接在模板中使用v-bind和v-on指令来绑定数据和事件。
三、JavaScript逻辑部分代码解析
下面我们将从几个关键方面详细解析这段代码:
(一)初始化Vue应用
javascript代码框架如下:
const { createApp, ref, computed, onMounted } = Vue;
createApp({
setup() {
// ...
}}).mount('#app');
代码说明:
(1)从 Vue 对象中解构出 createApp、ref、computed 和 onMounted 这些函数。
(2)使用 createApp 创建一个新的Vue应用实例,并通过mount('#app') 将其挂载到 HTML中 id 为 app 的元素上。
(二)响应式数据
1.什么是响应式数据
在Vue 3中,响应式数据是指当数据发生变化时,与之绑定的DOM元素会自动更新以反映这些变化的数据。这是Vue框架的核心特性之一,它使得开发者可以专注于数据的逻辑处理,而无需手动操作DOM来更新页面显示。
原理:Vue 3使用Proxy对象来实现响应式数据。当一个对象被转换为响应式对象时,Vue会创建一个代理对象,该代理对象会拦截对象的属性访问和修改操作。当属性被访问时,Vue 会追踪哪些代码依赖于这个属性;当属性被修改时,Vue会通知所有依赖于该属性的代码进行更新。
在我们的script代码中,使用了ref和reactive来创建响应式数据:
const { createApp, ref, computed, onMounted } = Vue;
createApp({
setup() {
// 使用 ref 创建响应式数据
const currentView = ref('borrows');
const isScrolled = ref(false);
const isMobileMenuOpen = ref(false);
// 使用 ref 创建数组形式的响应式数据
const books = ref([
{
id: 1,
title: "Python数据分析实战",
// ... 其他属性
},
// ... 其他图书对象
]);
const users = ref([
{
id: 1,
name: "张三",
// ... 其他属性
},
// ... 其他用户对象
]);
const borrows = ref([
{
id: 1,
bookId: 2,
// ... 其他属性
},
// ... 其他借阅记录对象
]);
// 搜索和筛选相关的响应式数据
const bookSearchQuery = ref('');
const bookCategoryFilter = ref('');
const bookStatusFilter = ref('');
......
// 分页相关的响应式数据
const currentPage = ref(1);
const booksPerPage = ref(9);
......
return {
currentView,
isScrolled,
......
borrowsPerPage
};
}
}).mount('#app');
代码解释:
(1)ref:用于创建一个响应式的引用对象,通常用于基本数据类型(如字符串、数字、布尔值)。当你需要修改ref对象的值时,需要通过 .value 属性来访问和修改。例如,要修改currentView的值,可以使用currentView.value = 'books'。
(2)reactive:我们的代码中没有使用,但reactive用于创建一个响应式的对象,通常用于对象和数组。与ref不同,使用reactive创建的对象可以直接访问和修改属性,无需使用 .value。例如:
const state = reactive({
count: 0,
message: 'Hello, Vue!'
});
// 直接修改属性
state.count++;
state.message = 'Updated message';
响应式的作用:当这些响应式数据发生变化时,Vue会自动更新与之绑定的DOM元素。例如,如果你在模板中使用 {{ currentView }} 来显示当前视图,当 currentView.value 发生变化时,页面上显示的内容也会自动更新。这样,开发者只需要关注数据的变化,而无需手动更新DOM,大大提高了开发效率。
2.导航相关
使用ref创建常规类型的响应式数据。
const currentView = ref('borrows');
const isScrolled = ref(false);
const isMobileMenuOpen = ref(false);
代码说明:
(1)currentView:用于存储当前显示的视图,初始值为 'dashboard',表示默认显示仪表盘视图。有'dashboard'、'books'、'borrows'、'users' 等值。
(2)isScrolled:记录页面是否滚动,当页面滚动时用于控制导航栏样式。
(3)isMobileMenuOpen:控制移动端菜单的显示与隐藏。
3.数据模型
使用ref创建数组形式的响应式数据,包括了三个数据模型:
const books = ref([...]);
const users = ref([...]);
const borrows = ref([...]);
代码说明:
(1)books:存储图书信息的数组。
(2)users:存储用户信息的数组。
(3)borrows:存储借阅记录的数组。
4.搜索和筛选
使用ref创建搜索和筛选相关的响应式数据:
const bookSearchQuery = ref('');
const bookCategoryFilter = ref('');
const bookStatusFilter = ref('');
const borrowSearchQuery = ref('');
const borrowStatusFilter = ref('');
const userSearchQuery = ref('');
const userRoleFilter = ref('');
const userStatusFilter = ref('');
这些 ref 对象用于存储搜索关键词和筛选条件,分别对应图书、借阅记录和用户的搜索与筛选。
5.分页
使用ref创建分页相关的响应式数据:
const currentPage = ref(1);
const booksPerPage = ref(9);
const currentBorrowPage = ref(1);
const borrowsPerPage = ref(10);
const currentUserPage = ref(1);
const usersPerPage = ref(6);
用于实现图书、借阅记录和用户列表的分页功能,currentPage 表示当前页码,booksPerPage 表示每页显示的记录数。
6.模态框
使用ref创建模态框相关的响应式数据:
const isBookModalOpen = ref(false);
const isBorrowModalOpen = ref(false);
const isUserModalOpen = ref(false);
const isBorrowDetailsModalOpen = ref(false);
const isConfirmDialogOpen = ref(false);
const editingBook = ref(null);
const editingUser = ref(null);
const selectedBorrow = ref(null);
const confirmDialogTitle = ref('');
const confirmDialogMessage = ref('');
let confirmCallback = null;
代码说明:
(1)前面5个isBookModalOpen、isBorrowModalOpen、isUserModalOpen、isBorrowDetailsModalOpen、isConfirmDialogOpen控制各个模态框和确认对话框的显示与隐藏。
(2)editingBook 和 editingUser 分别存储正在编辑的图书和用户信息。
(3)selectedBorrow 存储当前选中的借阅记录。
(4)confirmDialogTitle和confirmDialogMessage存储确认对话框的标题和消息,confirmCallback 存储确认操作的回调函数。
7.表单数据
使用ref创建表单相关的响应式数据:
const form = ref({
bookId: null,
bookCover: '',
bookTitle: '',
bookAuthor: '',
bookPublisher: '',
bookYear: '',
bookCategory: '计算机',
bookISBN: '',
bookDescription: '',
borrowBookId: '',
borrowUserId: '',
borrowDate: '',
dueDate: '',
userId: null,
userAvatar: '',
userName: '',
userStudentId: '',
userRole: 'student',
userContact: '',
userEmail: '',
userNotes: ''});
存储图书、借阅和用户表单的输入数据。
(三)计算属性
1.什么是计算属性
计算属性(Computed Properties)是Vue.js中一个非常有用的特性,它允许你基于已有的响应式数据来定义新的响应式数据。这些新的数据会根据依赖的响应式数据自动更新,并且会进行缓存,只有当依赖的数据发生变化时才会重新计算。
(1)为什么需要计算属性
在模板中可以使用表达式来进行简单的计算,但如果表达式过于复杂,会让模板变得难以维护和阅读。计算属性可以将复杂的逻辑封装起来,让模板更加简洁,同时也提高了代码的可维护性。
(2)计算属性的特点
缓存机制:计算属性会根据其依赖的数据进行缓存,只有当依赖的数据发生变化时,计算属性才会重新计算。这意味着如果依赖的数据没有变化,多次访问计算属性时会直接返回之前缓存的结果,而不会重复计算,从而提高了性能。
响应式更新:当计算属性依赖的响应式数据发生变化时,计算属性会自动更新,并且与之绑定的DOM元素也会相应地更新。
(3)代码中的计算属性示例
在我们的代码中,有多个计算属性的定义,下面是几个典型的例子:
// 过滤图书列表,根据搜索查询、分类和状态进行筛选
const filteredBooks = computed(() => {
return books.value.filter(book => {
const titleMatch = book.title.toLowerCase().includes(bookSearchQuery.value.toLowerCase());
const authorMatch = book.author.toLowerCase().includes(bookSearchQuery.value.toLowerCase());
const categoryMatch = bookCategoryFilter.value ? book.category === bookCategoryFilter.value : true;
const statusMatch = bookStatusFilter.value === 'available' ? !book.isBorrowed :
bookStatusFilter.value === 'borrowed' ? book.isBorrowed : true;
return (titleMatch || authorMatch) && categoryMatch && statusMatch;
});
});
// 计算总页数
const totalPages = computed(() => {
return Math.ceil(filteredBooks.value.length / booksPerPage.value);
});
// 分页显示图书
const paginatedBooks = computed(() => {
const start = (currentPage.value - 1) * booksPerPage.value;
const end = start + booksPerPage.value;
return filteredBooks.value.slice(start, end);
});
代码解释:
- filteredBooks:根据bookSearchQuery、bookCategoryFilter和bookStatusFilter的值对books数组进行过滤,返回符合条件的图书列表。当这些依赖的数据发生变化时,filteredBooks会自动重新计算。
- totalPages:根据filteredBooks的长度和booksPerPage的值计算总页数。由于totalPages依赖于filteredBooks,当filteredBooks发生变化时,totalPages也会重新计算。
- paginatedBooks:根据currentPage和booksPerPage的值对filteredBooks进行分页,返回当前页的图书列表。当currentPage、booksPerPage或filteredBooks发生变化时,paginatedBooks会重新计算。
(4)计算属性的使用
在模板中,可以像使用普通数据一样使用计算属性:
<div>
<!-- 显示过滤后的图书数量 -->
<p>过滤后的图书数量: {{ filteredBooks.length }}</p>
<!-- 显示总页数 -->
<p>总页数: {{ totalPages }}</p>
<!-- 显示当前页的图书 -->
<ul>
<li v-for="book in paginatedBooks" :key="book.id">{{ book.title }}</li>
</ul>
</div>
通过使用计算属性,我们可以将复杂的逻辑封装在JavaScript代码中,让模板更加简洁,同时也提高了代码的可维护性和性能。
2.图书管理相关的计算属性
上面的例子已经讲清楚了,接下来再简化讲一下。
const filteredBooks = computed(() => {
// ...
});
const totalPages = computed(() => {
return Math.ceil(filteredBooks.value.length / booksPerPage.value);});
const paginatedBooks = computed(() => {
const start = (currentPage.value - 1) * booksPerPage.value;
const end = start + booksPerPage.value;
return filteredBooks.value.slice(start, end);});
代码解释:
(1)filteredBooks:根据搜索关键词和筛选条件过滤图书列表。
(2)totalPages:计算图书列表的总页数。
(3)paginatedBooks:根据当前页码和每页显示的记录数,截取当前页要显示的图书列表。
在搜索框中输入关键词和筛选条件,不需要点击,将直接呈现结果:
3.借阅管理相关的计算属性
const filteredBorrows = computed(() => {
return borrows.value
.map(borrow => {
const book = books.value.find(b => b.id === borrow.bookId);
const user = users.value.find(u => u.id === borrow.userId);
// 添加调试信息
if (!book) {
console.warn('找不到对应的图书:', borrow);
}
if (!user) {
console.warn('找不到对应的用户:', borrow);
}
return { ...borrow, book, user };
})
.filter(borrow => {
// 过滤掉没有关联图书或用户的记录
if (!borrow.book || !borrow.user) {
console.warn('过滤无效借阅记录:', borrow);
return false;
}
// 应用搜索和筛选条件
const bookMatch = borrow.book.title.toLowerCase().includes(borrowSearchQuery.value.toLowerCase());
const userMatch = borrow.user.name.toLowerCase().includes(borrowSearchQuery.value.toLowerCase());
let statusMatch = true;
if (borrowStatusFilter.value === 'borrowed') {
statusMatch = !borrow.isReturned && !isOverdue(borrow.dueDate);
} else if (borrowStatusFilter.value === 'returned') {
statusMatch = borrow.isReturned;
} else if (borrowStatusFilter.value === 'overdue') {
statusMatch = !borrow.isReturned && isOverdue(borrow.dueDate);
}
return (bookMatch || userMatch) && statusMatch;
});
});
const totalBorrowPages = computed(() => {
return Math.ceil(filteredBorrows.value.length / borrowsPerPage.value);
});
const paginatedBorrows = computed(() => {
const start = (currentBorrowPage.value - 1) * borrowsPerPage.value;
const end = start + borrowsPerPage.value;
return filteredBorrows.value.slice(start, end);
});
代码解释:
(1)filteredBorrows:根据搜索关键词和筛选条件过滤借阅记录列表。
(2)totalBorrowPages:计算借阅记录列表的总页数。
(3)paginatedBorrows:根据当前页码和每页显示的记录数,截取当前页要显示的借阅记录列表。
在搜索框中输入关键词和筛选条件,不需要点击,将直接呈现结果:
4.用户管理相关的计算属性
上面的例子已经讲清楚了,接下来再简化讲一下。
const filteredUsers = computed(() => {
return users.value.filter(user => {
const nameMatch = user.name.toLowerCase().includes(userSearchQuery.value.toLowerCase());
const idMatch = user.studentId.toLowerCase().includes(userSearchQuery.value.toLowerCase());
const roleMatch = userRoleFilter.value ? user.role === userRoleFilter.value : true;
const statusMatch = userStatusFilter.value === 'active' ? !user.isBlocked :
userStatusFilter.value === 'blocked' ? user.isBlocked : true;
return (nameMatch || idMatch) && roleMatch && statusMatch;
});
});
const totalUserPages = computed(() => {
return Math.ceil(filteredUsers.value.length / usersPerPage.value);
});
const paginatedUsers = computed(() => {
const start = (currentUserPage.value - 1) * usersPerPage.value;
const end = start + usersPerPage.value;
return filteredUsers.value.slice(start, end);
});
代码解释:
(1)filteredUsers:根据搜索关键词和筛选条件过滤用户列表。
(2)totalUserPages:计算用户列表的总页数。
(3)paginatedUsers :根据当前页码和每页显示的记录数,截取当前页要显示的用户列表。
5.其他计算属性
除了上述有关图书管理、借阅管理、用户管理的计算属性外,还定义了四个计算属性,这些计算属性基于响应式数据books、borrows和users动态计算得出新的数据,并且会根据依赖数据的变化自动更新。以下是对每个计算属性的详细解析:
(1)borrowedBooksCount
const borrowedBooksCount = computed(() => {
return books.value.filter(book => book.isBorrowed).length;
});
功能:计算当前被借出的图书数量。
依赖:books响应式数据。
实现细节:
- 使用books.value获取books数组。
- 通过filter方法筛选出isBorrowed属性为true的图书。
- 使用length属性获取筛选后数组的长度,即被借出的图书数量。
(2)overdueBooksCount
const overdueBooksCount = computed(() => {
return borrows.value.filter(borrow => !borrow.isReturned && isOverdue(borrow.dueDate)).length;
});
功能:计算当前逾期未归还的借阅记录数量。
依赖:borrows响应式数据和isOverdue方法。
实现细节:
- 使用borrows.value获取borrows数组。
- 通过filter方法筛选出isReturned属性为false(未归还)且isOverdue方法返回true(逾期)的借阅记录。
- 使用length属性获取筛选后数组的长度,即逾期未归还的借阅记录数量。
(3)recentBorrows
const recentBorrows = computed(() => {
return [...borrows.value]
.sort((a, b) => new Date(b.borrowDate) - new Date(a.borrowDate))
.slice(0, 5)
.map(borrow => {
return {
...borrow,
book: books.value.find(b => b.id === borrow.bookId),
user: users.value.find(u => u.id === borrow.userId)
};
});
});
功能:获取最近的5条借阅记录,并为每条记录关联对应的图书和用户信息。
依赖:borrows、books和users响应式数据。
实现细节:
- 使用[...borrows.value]创建borrows数组的副本,避免修改原始数组。
- 使用sort方法按borrowDate降序排序,确保最近的借阅记录排在前面。
- 使用slice(0, 5)截取前5条记录。
- 使用map方法遍历截取后的数组,为每条记录添加book和user属性,分别表示借阅的图书和借阅的用户。
(4)availableBooks
const availableBooks = computed(() => {
return books.value.filter(book => !book.isBorrowed);
});
功能:获取当前可借阅的图书列表。
依赖:books 响应式数据。
实现细节:
- 使用books.value获取books数组。
- 通过filter方法筛选出isBorrowed属性为false的图书,即未被借出的图书。
这些计算属性的好处在于,当它们所依赖的响应式数据发生变化时,计算属性会自动重新计算,并且与之绑定的DOM元素也会相应地更新,从而保证数据的实时性和一致性。
(四)方法
1.Vue 3里定义方法的目的和作用
在Vue 3里定义方法具备多方面的目的和作用,以下将结合你提供的代码进行详细阐述:
(1)处理用户交互
当用户与界面进行互动时,如点击按钮、提交表单等操作,需要特定的方法来对这些交互做出响应,从而实现相应的功能。
示例代码:
const toggleMobileMenu = () => {
isMobileMenuOpen.value = !isMobileMenuOpen.value;
};
const changeView = (view) => {
currentView.value = view;
isMobileMenuOpen.value = false;
};
- toggleMobileMenu方法用于切换移动端菜单的显示与隐藏状态。当用户点击菜单切换按钮时,调用此方法来更新 isMobileMenuOpen 的值,进而控制菜单的显示或隐藏。
- changeView方法用于切换当前视图,同时关闭移动端菜单。当用户点击导航链接时,调用该方法更新 currentView 的值,实现视图的切换。
(2)封装业务逻辑
把复杂的业务逻辑封装到方法中,能够提升代码的可读性和可维护性,使代码结构更加清晰。
示例代码:
const saveBook = () => {
if (!form.value.bookTitle || !form.value.bookAuthor) {
alert('请填写图书标题和作者');
return;
}
if (editingBook.value) {
// 更新现有图书
const index = books.value.findIndex(b => b.id === form.value.bookId);
if (index !== -1) {
books.value[index] = {
...books.value[index],
cover: form.value.bookCover,
title: form.value.bookTitle,
author: form.value.bookAuthor,
publisher: form.value.bookPublisher,
year: form.value.bookYear,
category: form.value.bookCategory,
isbn: form.value.bookISBN,
description: form.value.bookDescription
};
}
} else {
// 添加新图书
const newBook = {
id: books.value.length > 0 ? Math.max(...books.value.map(b => b.id)) + 1 : 1,
cover: form.value.bookCover,
title: form.value.bookTitle,
author: form.value.bookAuthor,
publisher: form.value.bookPublisher,
year: form.value.bookYear,
category: form.value.bookCategory,
isbn: form.value.bookISBN,
description: form.value.bookDescription,
isBorrowed: false
};
books.value.push(newBook);
}
isBookModalOpen.value = false;
showToast(editingBook.value ? '图书更新成功' : '图书添加成功');
};
saveBook方法封装了保存图书信息的业务逻辑,涵盖了表单验证、更新现有图书信息以及添加新图书信息等操作。将这些逻辑封装在一个方法中,能够避免在模板或其他地方重复编写相同的代码,提高代码的复用性。
(3)数据处理与计算
方法可用于对数据进行处理和计算,例如日期计算、数据筛选等。
示例代码:
const isOverdue = (dueDate, returnDate = new Date().toISOString().split('T')[0]) => {
return dueDate && returnDate > dueDate;
};
const getOverdueDays = (dueDate, returnDate = new Date().toISOString().split('T')[0]) => {
if (!isOverdue(dueDate, returnDate)) return 0;
const due = new Date(dueDate);
const ret = new Date(returnDate);
const diffTime = Math.abs(ret - due);
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays;
};
- isOverdue方法用于判断图书是否逾期,通过比较应归还日期和实际归还日期来确定。
- getOverdueDays方法用于计算图书逾期的天数,在isOverdue方法的基础上进行日期计算。
(4)控制组件状态
方法可以用来控制组件的显示与隐藏、启用与禁用等状态。
示例代码:
const openBookModal = (book) => {
editingBook.value = book;
if (book) {
form.value = {
bookId: book.id,
bookCover: book.cover,
bookTitle: book.title,
bookAuthor: book.author,
bookPublisher: book.publisher,
bookYear: book.year,
bookCategory: book.category,
bookISBN: book.isbn,
bookDescription: book.description
};
} else {
form.value = {
bookId: null,
bookCover: 'https://picsum.photos/seed/default/200/300',
bookTitle: '',
bookAuthor: '',
bookPublisher: '',
bookYear: '',
bookCategory: '计算机',
bookISBN: '',
bookDescription: ''
};
}
isBookModalOpen.value = true;
};
const closeBookModal = () => {
isBookModalOpen.value = false;
};
- openBookModal方法用于打开图书编辑模态框,并根据传入的图书信息初始化表单数据。
- closeBookModal方法用于关闭图书编辑模态框,通过修改isBookModalOpen的值来控制模态框的显示与隐藏。
综上所述,在Vue 3中定义方法能够有效地处理用户交互、封装业务逻辑、进行数据处理和计算以及控制组件状态,从而提高代码的可维护性和可扩展性。
接下来详细解析本系统中设计的一些方法:
2.导航和滚动处理
const toggleMobileMenu = () => {
isMobileMenuOpen.value = !isMobileMenuOpen.value;};
const changeView = (view) => {
currentView.value = view;
isMobileMenuOpen.value = false;};
const handleScroll = () => {
if (window.scrollY > 10) {
isScrolled.value = true;
} else {
isScrolled.value = false;
}};
- toggleMobileMenu:切换移动端菜单的显示与隐藏。
- changeView:切换当前显示的视图,并关闭移动端菜单。
- handleScroll:监听页面滚动事件,根据滚动位置更新 isScrolled 的值。
3.日期处理
const isOverdue = (dueDate, returnDate = new Date().toISOString().split('T')[0]) => {
return dueDate && returnDate > dueDate;};
const getOverdueDays = (dueDate, returnDate = new Date().toISOString().split('T')[0]) => {
if (!isOverdue(dueDate, returnDate)) return 0;
const due = new Date(dueDate);
const ret = new Date(returnDate);
const diffTime = Math.abs(ret - due);
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays;};
- isOverdue:判断借阅记录是否逾期。
- getOverdueDays:计算逾期的天数。
4.模态框操作
const openBookModal = (book) => {
// ...};
const closeBookModal = () => {
isBookModalOpen.value = false;};
const saveBook = () => {
// ...};
const deleteBook = (id) => {
// ...};
- openBookModal:打开添加或编辑图书的模态框,并根据传入的图书信息初始化表单数据。
- closeBookModal:关闭图书模态框。
- saveBook:保存图书信息,根据 editingBook 的值判断是添加还是更新图书。
- deleteBook:删除图书,先弹出确认对话框,确认后检查图书是否正在被借阅,若未被借阅则删除。
5.其他方法
const openBorrowModal = () => {
// ...};
const closeBorrowModal = () => {
isBorrowModalOpen.value = false;};
const saveBorrow = () => {
// ...};
const returnBook = (borrow) => {
// ...};
const openUserModal = (user) => {
// ...};
const closeUserModal = () => {
isUserModalOpen.value = false;};
const saveUser = () => {
// ...};
const toggleUserBlock = (user) => {
// ...};
const viewBorrowDetails = (borrow) => {
// ...};
const closeBorrowDetailsModal = () => {
isBorrowDetailsModalOpen.value = false;};
const closeConfirmDialog = () => {
isConfirmDialogOpen.value = false;
confirmCallback = null;};
const confirmAction = () => {
if (typeof confirmCallback === 'function') {
confirmCallback();
}
closeConfirmDialog();};
const getBorrowingCount = (userId) => {
// ...};
const getReturnedCount = (userId) => {
// ...};
const getOverdueCount = (userId) => {
// ...};
const getBorrowHistory = (bookId) => {
// ...};
const getStatusText = (borrow) => {
// ...};
const getStatusColor = (borrow) => {
// ...};
const prevPage = () => {
// ...};
const nextPage = () => {
// ...};
const prevBorrowPage = () => {
// ...};
const nextBorrowPage = () => {
// ...};
const prevUserPage = () => {
// ...};
const nextUserPage = () => {
// ...};
const showToast = (message) => {
// ...};
这些方法分别用于处理借阅记录、用户信息的添加、编辑、删除、归还等操作,以及分页、显示提示信息等功能。
(五)生命周期钩子
1.什么是什么周期钩子
在Vue 3里,生命周期钩子(Lifecycle Hooks)是一些特殊的函数,它们允许开发者在组件生命周期的特定阶段执行自定义代码。组件的生命周期涵盖了从创建、挂载到更新,再到销毁的整个过程,而生命周期钩子就像是在这个过程中设置的“监控点”,让开发者可以在关键时间点插入自己的逻辑。有关周期钩子的内容可以看我的CSDN文章:Vue 3里的生命周期钩子(Lifecycle Hooks)-CSDN博客
通过使用生命周期钩子,开发者可以更好地控制组件在不同阶段的行为,确保组件的正常运行和资源的合理利用。
onMounted(() => {
window.addEventListener('scroll', handleScroll);
initCharts();});
在组件挂载完成后,添加滚动事件监听器,并初始化图表。
(六)返回值
即setup()函数的返回值。
return {
// 数据
currentView,
isScrolled,
isMobileMenuOpen,
// ...
// 计算属性
filteredBooks,
totalPages,
paginatedBooks,
// ...
// 方法
toggleMobileMenu,
changeView,
handleScroll,
// ...};
将响应式数据、计算属性和方法返回,以便在模板中使用。
综上所述,这段代码实现了一个完整的图书管理系统的逻辑,包括数据的存储、筛选、分页、模态框操作、日期处理等功能。