图书馆管理系统(三)基于jquery、ajax
任务3.4 借书还书页面
任务描述
这部分主要是制作借书还书的界面,这里我分别制作了两个网页分别用来借书和还书。此页面,也是通过获取books.txt内容然后添加到表格中,但是借还的操作没有添加到后端中去,只是一个简单的前端操作。
任务实施
3.4.1 借还的页面搭建
因为两者的整体框架是一样的不一样的点只有在借还操作这一部分是不同的,所以放在一块进行。
这里为借书操作为例:
整体布局就是左面导航栏,右边为主体部分内容
主体内容部分:
.main 包含一个表格,表格的头部定义了七个列:书名、作者、出版社、价格、内容摘要、借阅状态、操作
<tbody> 部分暂时为空,稍后将通过 JavaScript 动态填充数据。
导航栏部分:有三个导航链接:
“我的空间”:链接到用户个人页面。
“图书借阅”:当前所在页面。
“图书还回”:链接到图书还回的页面。
全部代码:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><script src="./js/jQuery.js"></script><link rel="stylesheet" href="./css/borrow.css">
</head>
<style></style><body><div class="content"><div class="nav"><div class="nav-header"><img src="./img/user1.png" alt=""><h3>用户系统</h3></div><div class="nav-menu"><ul><li><a href="user.html">我的空间</a></li><li><a href="#">图书借阅</a></li><li><a href="repaid.html">图书还回</a></li></ul></div></div><div class="main"><table><thead><tr><th>书名</th><th>作者</th><th>出版社</th><th>价格</th><th>内容摘要</th><th>借阅状态</th><th>操作</th></tr></thead><tbody></tbody></table></div></div>
</body>
<script src="./js/borrow.js"></script>
</html>
而还书的部分只有这部分是不一样的<div class="nav-menu"><ul><li><a href="user.html">我的空间</a></li><li><a href="borrow.html">图书借阅</a></li><li><a href="#">图书还回</a></li></ul></div>
3.4.2 页面样式设置
这里样式设置部分不多说,还是以借书为例,全部代码:
*{margin: 0;padding: 0;
}
.nav {background-color: #2f2f2f;width: 20%;height: 100vh;float: left;
}
.nav-header {height: 6rem;display: flex;
}
.nav-header img {width: 6rem;height: 6rem;margin: 1rem 2rem;
}
.nav-header h3 {color: #fff;font-size: 2rem;margin-top: 1rem;text-align: center;line-height: 6rem;
}
.nav-menu {height: 80%;
}
.nav-menu ul {list-style: none;margin-top: 2rem;
}
.nav-menu ul li {margin-bottom: 1rem;text-indent: 2rem;
}
.nav-menu ul :hover{opacity: 0.9;
}
.nav-menu ul li a {color: #fff;text-decoration: none;font-size: 1.5rem;
}
.main{background-color: cadetblue;border-radius: 1.5rem;width: 79%;height: 100%;position: absolute;left: 24rem;
}
.main table{border-collapse: collapse;width: 100%;margin-top: 2rem;text-align: center;
}
.main table th{background-color: #4CAF50;color: white;padding: 12px 16px;
}
.main table td{background-color: #78c37b;border: 1px solid #ddd;padding: 8px;
}
.main table td span{color: green;font-weight: bold;
}
.main table td button{background-color: #5fa862;color: white;border: none;border-radius: 5px;padding: 8px 16px;font-size: 16px;cursor: pointer;
}
.main table td button:hover{opacity: 0.8;
}
3.4.3 借还的功能实现
这里来两者这主要区别就是点击借书或还书时,转换的字体颜色不同。
点击结束或者还书的时候会有提示框并会有一个时间戳的提示。
讲解部分还是以借书为例:
首先利用 $.get 方法从 books.txt 文件中获取书籍数据。使用 $.each 遍历每一本书,并为每本书生成一行 <tr>,包括书名、作者、出版社、价格、内容摘要、借阅状态和借阅按钮。借阅状态初始设置为“未借阅”,并伴随一个“借阅”按钮。
然后使用事件代理 ($('tbody').on(...)) 来处理动态生成的借阅按钮的点击事件。
当按钮被点击时调用 getFormatDate 函数获取当前时间格式化字符串。找到当前行(使用 $(this).closest('tr')),并将 “借阅状态” 文本更改为“已借阅”,同时修改文本的颜色为红色(若是还书则初始页面字体为红色修改后则为绿色),以明确显示书籍的借阅状态。
弹出提示框显示借阅成功的信息和当前时间。
最后重要的部分就是获取当前时间的函数部分。
getFormatDate 函数用于获取当前的日期和时间,并进行格式化。
使用 Date 对象获取当前年份、月份、日期、小时和分钟,并确保它们都是两位数(例如:10、01)。
最终拼接字符串按照“YYYY-MM-DD HH:mm”的格式返回。
其中nowDate.getMonth():获取当前月份的索引(0-11)。
nowDate.getMonth() + 1 < 10 ? "0" + (nowDate.getMonth() + 1):检查月份是否小于 10,如果是,则在前面添加零以确保格式是“01”、“02”等。
nowDate.getDate()、nowDate.getHours()、nowDate.getMinutes()
与这个都是同理。
借书JS部分:
$.get('../books.txt', function (data) {var usres = JSON.parse(data);$.each(usres, function (i, book) {$('tbody').append(`<tr><td>${book.bookname}</td><td>${book.author}</td><td>${book.publisher}</td><td>${book.price}</td><td>${book.content}</td><td><span class="borrow-book">未借阅</span></td><td><button class="borrow-btn">借阅</button></td></tr>`)})
})
$('tbody').on('click', '.borrow-btn', function () {var str = getFormatDate();alert("借阅成功!当前时间:" + str);$(this).closest('tr').find('.borrow-book').text('已借阅').css('color', 'red');
})
function getFormatDate() {var nowDate = new Date();var year = nowDate.getFullYear();var month = nowDate.getMonth() + 1 < 10 ? "0" + (nowDate.getMonth() + 1) : nowDate.getMonth() + 1;var date = nowDate.getDate() < 10 ? "0" + nowDate.getDate() : nowDate.getDate();var hour = nowDate.getHours() < 10 ? "0" + nowDate.getHours() : nowDate.getHours();var minute = nowDate.getMinutes() < 10 ? "0" + nowDate.getMinutes() : nowDate.getMinutes();return year + "-" + month + "-" + date + " " + hour + ":" + minute;
}
还书JS部分:
$.get('../books.txt', function (data) {var usres = JSON.parse(data);$.each(usres, function (i, book) {$('tbody').append(`<tr><td>${book.bookname}</td><td>${book.author}</td><td>${book.publisher}</td><td>${book.price}</td><td>${book.content}</td><td><span class="borrow-book" style="color:red">未还回</span></td><td><button class="borrow-btn">还回</button></td></tr>`)})
})
$('tbody').on('click', '.borrow-btn', function () {var str = getFormatDate();alert("还书成功!当前时间:" + str);$(this).closest('tr').find('.borrow-book').text('已还回').css('color', 'green');})
function getFormatDate() {var nowDate = new Date();var year = nowDate.getFullYear();var month = nowDate.getMonth() + 1 < 10 ? "0" + (nowDate.getMonth() + 1) : nowDate.getMonth() + 1;var date = nowDate.getDate() < 10 ? "0" + nowDate.getDate() : nowDate.getDate();var hour = nowDate.getHours() < 10 ? "0" + nowDate.getHours() : nowDate.getHours();var minute = nowDate.getMinutes() < 10 ? "0" + nowDate.getMinutes() : nowDate.getMinutes();return year + "-" + month + "-" + date + " " + hour + ":" + minute ;
}
任务3.5 图书管理功能界面
任务描述
该任务可以说是整个项目中最重要的部分,即能够连接后端进行前后端的交互,包括新增图书,编辑图书,删除图书等等。以及还有翻页功能的实现。Server.js的编写。
任务实施
3.5.1 页面框架搭建
首先想到的是需要一个表格,将我们通过ajax访问并获取txt文本,然后把获取到的内容放到表格中,所以我们可以新建一个空表格,还有新建图书和编辑图书时的表单。以及一些按钮和超链接,例如新增图书按钮,翻页部分的超链接等等。
分析后即为:
主体内容:使用<body>标签包含整个页面的主体部分。
标题:使用<h2>标签显示“图书管理系统”。
图书表格:使用<table>标签布局图书信息,表头定义了书名、作者、出版社、出版日期、页数、价格、内容摘要和操作等字段。
分页导航:有按钮和链接用于新增图书、翻页和跳转至指定页数。
新增图书表单:包含一个ID为addform的表单,用于输入新图书信息,包括书名、作者、出版社、出版日期、页数、价格和内容摘要等。
编辑图书表单:类似于新增图书的表单,供编辑现有图书信息使用。
HTML具体代码:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>图书信息管理</title><link rel="stylesheet" href="/public/css/Admin-books.css"><script src="./js/jQuery.js"></script>
</head>
<body><div class="container"><h2>图书管理系统</h2><table><thead><tr><th>书名</th><th>作者</th><th>出版社</th><th>出版日期</th><th>页数</th><th>价格</th><th>内容摘要</th><th>操作</th></tr></thead><tbody></tbody></table><div class="nav"><button id="add">新增图书</button><span class="total-records"></span> <span class="current-page">第1页</span><a href="#" id="first-page">第一页</a><a href="#" id='prev-page'>上一页</a><a href="#" id="next-page">下一页</a><a href="#" id="last-page">最后一页</a><span>跳转到第</span><input type="text" value="1" placeholder="页码"><span>页</span><button id="jump-to-page">确定</button></div></div><div class="addbook"><h2>新增图书</h2><form id="addform"><label for="bookname">书名:</label><input type="text" id="bookname" name="bookname"><br><br><label for="author">作者:</label><input type="text" id="author" name="author"><br><br><label for="publisher">出版社:</label><input type="text" id="publisher" name="publisher"><br><br><label for="publishdate">出版日期:</label><input type="date" id="publishdate" name="publishdate"><br><br><label for="page">页数:</label><input type="text" id="page" name="page"><br><br><label for="price">价格:</label><input type="text" id="price" name="price"><br><br><label for="content">内容摘要:</label><textarea id="content" name="content"></textarea><br><br><button type="submit" id="submit">提交</button><button id="close">关闭</button></form></div><div class="editbook"><h2>编辑图书</h2><form id="editform"><label for="bookname">书名:</label><input type="text" id="bookname" name="bookname"><br><br><label for="author">作者:</label><input type="text" id="author" name="author"><br><br><label for="publisher">出版社:</label><input type="text" id="publisher" name="publisher"><br><br><label for="publishdate">出版日期:</label><input type="date" id="publishdate" name="publishdate"><br><br><label for="page">页数:</label><input type="text" id="page" name="page"><br><br><label for="price">价格:</label><input type="text" id="price" name="price"><br><br><label for="content">内容摘要:</label><textarea id="content" name="content"></textarea><br><br><button type="submit" id="edit-submit">提交</button></form></div>
</body>
<script src="./js/Admin-books.js"></script>
</html>
3.5.2 页面样式设置
样式部分,主要就分为容器样式,主体内容样式,导航栏样式,链接样式等等。这里比较重要的是,我们需要先把新增图书和编辑图书隐藏掉,当我们点击相应按钮的时候再把他们分别显示出来。
CSS部分代码:
* {margin: 0;padding: 0;
}body {background-image: url(../img/Admin2.png);background-size: cover;
}.container {width: 70%;height: 20%;margin: 10rem auto;border: 1rem solid #adda8f;background-color: aliceblue;border-radius: 1rem;
}h2 {margin: 1rem;
}.container table {width: 100%;height: 100%;border: 0.1rem solid #000000;
}.container table thead {background-color: #f2f2f2;color: #000000;text-align: center;font-size: 1.5rem;
}
.container table tbody {background-color: #ffffff;color: #000000;text-indent: 1rem;font-size: 1.2rem;
}
.nav{width: 80%;margin: 1rem auto;
}
a{text-decoration: none;
}
input{width: 7rem;height: 1.5rem;
}
.addbook{width: 40%;margin: 1rem auto;border: 1rem solid #adda8f;background-color: aliceblue;border-radius: 1rem;position: absolute;top: 10%;left: 25%;display: none;
}
.editbook{width: 40%;margin: 1rem auto;border: 1rem solid #adda8f;background-color: aliceblue;border-radius: 1rem;position: absolute;top: 10%;left: 25%;display: none;
}
label{display: inline-block;width: 8rem;text-align: right;margin-right: 1rem;
}
textarea{width: 80%;height: 5rem;resize: none;
}
button{width: 6rem;height: 2rem;margin-left: 1rem;background-color: #008CBA;color: #ffffff;border: none;border-radius: 0.5rem;cursor: pointer;
}
td {max-width: 150px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
最终效果:
3.5.3 更新图书表格updateTable()函数
主要目的就是从 books.txt 的文件中获取书籍数据,并将其显示在 HTML 表格中。
这里首先定义了三个全局变量:
- books:一个空数组,用于存储书籍信息。
- currentPage:表示当前页码,初始值为 1。
- booksPerPage:每页显示的书籍数量,这里设置为 5。
首先先获取书籍,使用$.get() 方法从 books.txt 文件中获取数据。成功获取后,将数据解析为 JSON 对象,并将其赋值给 books 数组。最后调用 updateTable() 函数更新表格。
$.get('/books.txt', function (data) {
var usres = JSON.parse(data);
books = usres; // 将文件内容存入数组
updateTable(); // 更新表格
});
下面开始进行更新表格函数的编写,首先要清空 <tbody> 的内容,以准备显示新的书籍数据。再定义了三个变量:
- 计算总页数:
const totalPages = Math.ceil(books.length / booksPerPage);
books.length 获取书籍的总数量(即数组长度),将其除以每页的书籍数量 booksPerPage(这里设为每页最多为5),然后使用 Math.ceil() 函数向上取整,以确保即使有一部分书籍也会增加到新的一页。
- 计算当前页的起始索引:
const start = (currentPage - 1) * booksPerPage;
根据当前页码 currentPage 计算当前页的起始索引。由于数组索引从 0 开始,所以需要将 currentPage 减去 1,然后乘以每页的书籍数量。这个值用于确定在 books 数组中从哪里开始取书籍。
- 计算当前页的结束索引:
const end = Math.min(start + booksPerPage, books.length);
start + booksPerPage 计算出当前页应显示的最后一个书籍的索引,但也可能会超过 books.length。因此,使用 Math.min() 函数确保结束索引不会超过实际书籍的数量。
再下一步,循环遍历当前页的书籍,从 start 索引开始,直到 end 索引(不包括 end),遍历当前页面应显示的书籍并获取当前书籍信息。然后再将遍历完的书籍,每部分对应的值插入到表格中。插入时使用的是jQuery 的 append() 方法。将每一本书的信息以一个新的表格行 (<tr>) 的形式添加到 <tbody> 中。每一列 (<td>) 显示书籍的不同属性。注意:其中包含两个链接,一个是 "编辑" 操作的链接,另一个是 "删除" 操作的链接。它们的 class 属性分别为 edit 和 delete,可以用于后续的事件绑定,实现对书籍的编辑和删除功能。
let books = [];
let currentPage = 1;
const booksPerPage = 5;
// 获取books.txt文件内容
$.get('/books.txt', function (data) {var usres = JSON.parse(data);books = usres; // 将文件内容存入数组updateTable(); // 更新表格
});// 更新表格函数
function updateTable() {$('tbody').empty();const totalPages = Math.ceil(books.length / booksPerPage);const start = (currentPage - 1) * booksPerPage;const end = Math.min(start + booksPerPage, books.length);for (let i = start; i < end; i++) {const book = books[i];$('tbody').append(`<tr><td>${book.bookname}</td><td>${book.author}</td><td>${book.publisher}</td><td>${book.publishdate}</td><td>${book.page}</td><td>${book.price}</td><td>${book.content}</td><td><a href="#" class='edit'>编辑</a><a href="#" class='delete'>删除</a></td></tr>`);}
3.5.4 新增图书功能
我们首先进行新增图书功能的编写,首先先获取到对应所需要的元素,然后实现点击新增图书,该表单显示的操作。
var addbook = document.querySelector('.addbook');
var btn = document.querySelector('#add');
var close1 = document.querySelector('#close');
btn.addEventListener('click', function () {addbook.style.display = 'block';
})close1.addEventListener('click', function (e) {e.preventDefault();addbook.style.display = 'none';
})
该功能就编辑完成,此时点击新增图书,该表单就会显示出来。
接下来开始进行实现新增图书功能的操作,可以将新增的图书,不仅能够添加到浏览器的页面还能够添加到books.txt中去。
首先添加事件监听,当表单被提交时,会触发该事件。
在这个监听函数中获取表单数据,将表单中输入的数据提取出来,并存储在newbook对象中。
这个newbook对象,就是用来后面把新增的图书储存请求后端放到txt中的对象。
发送AJAX请求,使用jQuery的$.ajax()方法向后端发送HTTP请求。请求信息包括:
url: 发送请求的目标URL。
method: 请求的方法,这里使用POST。
contentType: 设置请求头为application/json,表示请求体内容为JSON格式。
data: 将newbook对象转换为JSON字符串,作为请求体发送。
并添加一个处理成功响应,若能成功响应,在回调中,将新书对象newbook添加到books数组中,调用updateTable()函数更新表格显示,并隐藏新增图书的表单。
这里的updateTable()函数将在后面进行编写,这个函数就是用来更新表格每当我们实现一个功能时,调用它来进行刷新。
处理错误响应,添加这个的目的就是我们在编写中难免会出现错误所以我们可以在当我们发生错误时,可以打印一下错误信息,并为之改错。使用error: 定义当请求失败时的回调函数。使用console.error()输出错误信息,便于调试。
Xhr:这是当 AJAX 请求发生错误时,错误回调函数的第一个参数。
新增图书代码:
document.getElementById('addform').addEventListener('submit', function (e) {e.preventDefault(); // 防止默认提交行为var newbook = {bookname: document.getElementById('bookname').value,author: document.getElementById('author').value,publisher: document.getElementById('publisher').value,publishdate: document.getElementById('publishdate').value,page: document.getElementById('page').value,price: document.getElementById('price').value,content: document.getElementById('content').value};// 使用 POST 方法,确保将数据作为请求体发送$.ajax({url: 'http://localhost:5501/addBook', // 后端处理的 URLmethod: 'POST', // 使用 POST 方法contentType: 'application/json', // 设置请求头为 JSONdata: JSON.stringify(newbook), // 将对象转换为 JSON 字符串success: function () {books.push(newbook); // 将新书加入数组updateTable(); // 更新表格addbook.style.display = 'none'; // 隐藏新增表单},error: function (xhr) {console.error('添加图书失败:', xhr.responseText); // 输出错误信息}});
});
3.5.6 删除书籍功能
这部分主要想实现删除图书的功能,首先进行的是一个委托事件绑定。绑定一个点击事件到表格的 .delete 类元素上。当用户点击某个删除链接时,触发这个事件处理函数。点击删除链接时会触发一个弹窗,并提示用户是否删除图书,如果用户选择“确定”,则继续执行后续代码,否则不采取任何行动。
接线来进行若点击删除,下面要执行的代码,首先要知道是哪行在请求删除,所以使用 $(this).closest('tr') 获取当前链接所在的行 (<tr>)。这样可以找到与点击的删除链接对应的数据行。
知道是哪行了以后,由于获取到的书籍是在刚刚新建的数组中,所以要获取行的索引并加上当前页的书籍数量,以计算出在 books 数组中的实际索引位置。这是确保正确定位要删除的书籍。
最后开始与后端相交互,发送删除请求,使用 DELETE 方法向指定的 URL 发送删除请求。注意端口名,再添加一个删除图书回调,若请求成功,则从数组中删除对应的书籍。再删除表格对应的行,最后更新分页信息。若请求失败,则要打印一下错误信息以方便改错。
$("table").on('click', ".delete", function () {if (confirm("是否确定删除图书")) {var tr = $(this).closest('tr'); // 获取当前行var bookIndex = tr.index() + (currentPage - 1) * booksPerPage; // 计算索引$.ajax({url: 'http://localhost:5501/books/' + bookIndex, // 确保URL正确method: 'DELETE', // 使用 DELETE 方法success: function (response) {// 删除成功books.splice(bookIndex, 1); // 从数组中移除对应的书籍tr.remove(); // 删除表格中的行updatePagination(Math.ceil(books.length / booksPerPage)); // 更新分页信息},error: function (xhr, status, error) {console.error('删除图书失败:', xhr.responseText); // 输出错误信息}});}
});
注意:更新分页信息是为后期翻页功能中输出信息当前共几本书的函数。
3.5.7 编辑图书功能
第一步与前两个功能一样,都可以使用事件委托来进行编辑操作。同样的方法先获取当前所在行,在通过计算分页的页数以及计算出书籍在 books 数组中的实际索引,从而获取对应的书籍数据。
接下来在点击编辑后出现的表单中,进行填充表单。
填充表单之后,管理员即开始对其书籍信息进行编辑,等待编辑完事,并点击提交时就会进行下一步编辑表单提交时处理更新。还是使用表单提交事件。
解除对表单提交事件的所有先前绑定,并定义新的提交处理逻辑。
注意:然后需要获取更新后的书籍信息,就是更改完之后的表单中每项对应的值构建一个 updatedBook 对象,该对象还会用于请求后端时,写入的内容。获取之后开始向后端发起AJAX请求,使用 PUT 方法向服务器发送更新的书籍信息。同样的请求头设置为 application/json,发送的请求体为 updatedBook 对象的 JSON 字符串格式。
请求更新编辑成功后,将更新后的书籍信息存回 books 数组中,并调用 updateTable() 函数刷新表格内容。最后,隐藏编辑表单。
若请求失败,输出错误信息到控制台,便于后续的修改。
$('tbody').on('click', '.edit', function () {var tr = $(this).closest('tr'); // 获取当前行var bookIndex = tr.index() + (currentPage - 1) * booksPerPage; // 更新索引以便正确指向var book = books[bookIndex]; // 获取对应的书籍数据editbook.style.display = 'block'; // 显示编辑表单// 填充编辑表单document.getElementById('editform').reset(); // 清空表单document.querySelector('#editform input[name="bookname"]').value = book.bookname;document.querySelector('#editform input[name="author"]').value = book.author;document.querySelector('#editform input[name="publisher"]').value = book.publisher;document.querySelector('#editform input[name="publishdate"]').value = book.publishdate;document.querySelector('#editform input[name="page"]').value = book.page;document.querySelector('#editform input[name="price"]').value = book.price;document.querySelector('#editform textarea[name="content"]').value = book.content;// 在编辑表单提交时处理更新$('#editform').off('submit').on('submit', function (event) {event.preventDefault(); // 防止默认提交行为var updatedBook = {bookname: document.querySelector('#editform input[name="bookname"]').value,author: document.querySelector('#editform input[name="author"]').value,publisher: document.querySelector('#editform input[name="publisher"]').value,publishdate: document.querySelector('#editform input[name="publishdate"]').value,page: document.querySelector('#editform input[name="page"]').value,price: document.querySelector('#editform input[name="price"]').value,content: document.querySelector('#editform textarea[name="content"]').value};// 向服务器发送PUT请求更新书籍信息$.ajax({url: 'http://localhost:5501/books/' + bookIndex, // 使用正确的URL及IDmethod: 'PUT', // 将方法改为 PUTcontentType: 'application/json', // 设置请求头为 JSONdata: JSON.stringify(updatedBook), // 将对象转换为 JSON 字符串success: function (response) {// 更新数组中的数据books[bookIndex] = updatedBook; // 更新对应索引的书籍信息updateTable(); // 更新表格editbook.style.display = 'none'; // 隐藏编辑表单},error: function (xhr) {console.error('编辑图书失败:', xhr.responseText); // 输出错误信息}});});});// 更新分页信息updatePagination(totalPages);
}
3.5.8 翻页功能及更新分页信息(updatePagination)
首先写一下更新的那些信息的函数,该函数接受参数 totalPages,更新页面上显示的总书籍数量和当前页码信息,确保管理员清楚现在的书籍数量。
- 翻到第一页:
当点击“第一页”按钮时,将 currentPage 设置为 1,并调用 updateTable() 更新表格内容,显示第一页的书籍。即会跳到第一页。
$('.nav #first-page').click(function () {currentPage = 1;updateTable();
});
- 翻到上一页:
点击“上一页”按钮时,如果当前页大于 1,则将 currentPage 减 1,并更新表格内容,显示上一页的书籍。
$('.nav #prev-page').click(function () {if (currentPage > 1) {currentPage--;updateTable();}
});
- 翻到下一页:
当点击“下一页”按钮时,首先计算总页数 totalPages。如果当前页小于总页数,则将 currentPage 加 1,并更新表格,显示下一页的书籍。
$('.nav #next-page').click(function () {const totalPages = Math.ceil(books.length / booksPerPage);if (currentPage < totalPages) {currentPage++;updateTable();}
});
- 翻到最后一页:
点击“最后一页”按钮时,直接将 currentPage 设置为最后一页的页码,然后更新表格以显示最后一页的书籍。
$('.nav #last-page').click(function () {currentPage = Math.ceil(books.length / booksPerPage);updateTable();
});
- 跳转到指定页:
当用户输入一个页码并点击“跳转”按钮时,首先通过 parseInt() 转换输入的值为整数。如果该页码大于 0 且小于等于总页数,则更新 currentPage,并调用 updateTable() 更新表格以显示相应的页。
$('.nav #jump-to-page').click(function () {const pageInput = parseInt($('.nav input[type="text"]').val());if (pageInput > 0 && pageInput <= Math.ceil(books.length / booksPerPage)) {currentPage = pageInput;updateTable();}
});
至此,图书管理的相关功能就写完了。
JS全部代码:
var addbook = document.querySelector('.addbook');
var container = document.querySelector('.container');
var close1 = document.querySelector('#close');
var btn = document.querySelector('#add');
var editbook = document.querySelector('.editbook');btn.addEventListener('click', function () {addbook.style.display = 'block';
})close1.addEventListener('click', function (e) {e.preventDefault();addbook.style.display = 'none';
})//分页功能
// 书籍数据数组// 新增图书
document.getElementById('addform').addEventListener('submit', function (e) {e.preventDefault(); // 防止默认提交行为var newbook = {bookname: document.getElementById('bookname').value,author: document.getElementById('author').value,publisher: document.getElementById('publisher').value,publishdate: document.getElementById('publishdate').value,page: document.getElementById('page').value,price: document.getElementById('price').value,content: document.getElementById('content').value};// 使用 POST 方法,确保将数据作为请求体发送$.ajax({url: 'http://localhost:5501/addBook', // 后端处理的 URLmethod: 'POST', // 使用 POST 方法contentType: 'application/json', // 设置请求头为 JSONdata: JSON.stringify(newbook), // 将对象转换为 JSON 字符串success: function () {books.push(newbook); // 将新书加入数组updateTable(); // 更新表格addbook.style.display = 'none'; // 隐藏新增表单},error: function (xhr) {console.error('添加图书失败:', xhr.responseText); // 输出错误信息}});
});
let books = [];
let currentPage = 1;
const booksPerPage = 5;
// 获取books.txt文件内容
$.get('/books.txt', function (data) {var usres = JSON.parse(data);books = usres; // 将文件内容存入数组updateTable(); // 更新表格
});// 更新表格函数
function updateTable() {$('tbody').empty();const totalPages = Math.ceil(books.length / booksPerPage);const start = (currentPage - 1) * booksPerPage;const end = Math.min(start + booksPerPage, books.length);for (let i = start; i < end; i++) {const book = books[i];$('tbody').append(`<tr><td>${book.bookname}</td><td>${book.author}</td><td>${book.publisher}</td><td>${book.publishdate}</td><td>${book.page}</td><td>${book.price}</td><td>${book.content}</td><td><a href="#" class='edit'>编辑</a><a href="#" class='delete'>删除</a></td></tr>`);}// //编辑操作// 使用事件委托处理动态生成的元素$('tbody').on('click', '.edit', function () {var tr = $(this).closest('tr'); // 获取当前行var bookIndex = tr.index() + (currentPage - 1) * booksPerPage; // 更新索引以便正确指向var book = books[bookIndex]; // 获取对应的书籍数据editbook.style.display = 'block'; // 显示编辑表单// 填充编辑表单document.getElementById('editform').reset(); // 清空表单document.querySelector('#editform input[name="bookname"]').value = book.bookname;document.querySelector('#editform input[name="author"]').value = book.author;document.querySelector('#editform input[name="publisher"]').value = book.publisher;document.querySelector('#editform input[name="publishdate"]').value = book.publishdate;document.querySelector('#editform input[name="page"]').value = book.page;document.querySelector('#editform input[name="price"]').value = book.price;document.querySelector('#editform textarea[name="content"]').value = book.content;// 在编辑表单提交时处理更新$('#editform').off('submit').on('submit', function (event) {event.preventDefault(); // 防止默认提交行为var updatedBook = {bookname: document.querySelector('#editform input[name="bookname"]').value,author: document.querySelector('#editform input[name="author"]').value,publisher: document.querySelector('#editform input[name="publisher"]').value,publishdate: document.querySelector('#editform input[name="publishdate"]').value,page: document.querySelector('#editform input[name="page"]').value,price: document.querySelector('#editform input[name="price"]').value,content: document.querySelector('#editform textarea[name="content"]').value};// 向服务器发送PUT请求更新书籍信息$.ajax({url: 'http://localhost:5501/books/' + bookIndex, // 使用正确的URL及IDmethod: 'PUT', // 将方法改为 PUTcontentType: 'application/json', // 设置请求头为 JSONdata: JSON.stringify(updatedBook), // 将对象转换为 JSON 字符串success: function (response) {// 更新数组中的数据books[bookIndex] = updatedBook; // 更新对应索引的书籍信息updateTable(); // 更新表格editbook.style.display = 'none'; // 隐藏编辑表单},error: function (xhr) {console.error('编辑图书失败:', xhr.responseText); // 输出错误信息}});});});// 更新分页信息updatePagination(totalPages);
}// 删除书籍
$("table").on('click', ".delete", function () {if (confirm("是否确定删除图书")) {var tr = $(this).closest('tr'); // 获取当前行var bookIndex = tr.index() + (currentPage - 1) * booksPerPage; // 计算索引$.ajax({url: 'http://localhost:5501/books/' + bookIndex, // 确保URL正确method: 'DELETE', // 使用 DELETE 方法success: function (response) {// 删除成功books.splice(bookIndex, 1); // 从数组中移除对应的书籍tr.remove(); // 删除表格中的行updatePagination(Math.ceil(books.length / booksPerPage)); // 更新分页信息},error: function (xhr, status, error) {console.error('删除图书失败:', xhr.responseText); // 输出错误信息}});}
});
// 更新分页信息
function updatePagination(totalPages) {$('.nav .total-records').text(`共${books.length}本书`);$('.nav .current-page').text(`当前第${currentPage}页/共${totalPages}页`);
}// 翻页功能
$('.nav #first-page').click(function () {currentPage = 1;updateTable();
});$('.nav #prev-page').click(function () {if (currentPage > 1) {currentPage--;updateTable();}
});$('.nav #next-page').click(function () {const totalPages = Math.ceil(books.length / booksPerPage);if (currentPage < totalPages) {currentPage++;updateTable();}
});$('.nav #last-page').click(function () {currentPage = Math.ceil(books.length / booksPerPage);updateTable();
});$('.nav #jump-to-page').click(function () {const pageInput = parseInt($('.nav input[type="text"]').val());if (pageInput > 0 && pageInput <= Math.ceil(books.length / booksPerPage)) {currentPage = pageInput;updateTable();}
});
目前就差后端代码部分的编写了,加油坚持住下一章博客进行最后阶段的完成以及整合!!!
相关文章:
图书馆管理系统(三)基于jquery、ajax
任务3.4 借书还书页面 任务描述 这部分主要是制作借书还书的界面,这里我分别制作了两个网页分别用来借书和还书。此页面,也是通过获取books.txt内容然后添加到表格中,但是借还的操作没有添加到后端中去,只是一个简单的前端操作。…...
【练习Day20】字符串变形
链接:字符串变形_牛客题霸_牛客网 方法一:双逆转(推荐使用) 思路: 将单词位置的反转,那肯定前后都是逆序,不如我们先将整个字符串反转,这样是不是单词的位置也就随之反转了。但是单…...
鸿蒙项目云捐助第二十讲云捐助项目物联网IOT的使用
鸿蒙项目云捐助第二十讲云捐助项目物联网IOT的使用 在鸿蒙技术完成云捐助项目中,物联网技术也是具有一定生命力的存在。这里也会在云捐助项目中使用物联网的技术。 一、华为云IoT物联网操作 这里以华为云的Iot物联网操作来介绍华为云I ot物联网技术。 这里从百度…...
Linux设置篇
查看主机名 hostname 修改主机名 hostnamectl set-hostname 主机名 配置ip映射 vi /etc/hosts 192.168.1.10 pure 限制SSH登录的IP a) 设置禁止所有ip连接服务器的SSH vi /etc/hosts.deny sshd:all:deny b) 设置允许指定ip连接服务器的SSH(这边建议设置一个备…...
WatchAlert - 开源多数据源告警引擎
概述 在现代 IT 环境中,监控和告警是确保系统稳定性和可靠性的关键环节。然而,随着业务规模的扩大和数据源的多样化,传统的单一数据源告警系统已经无法满足复杂的需求。为了解决这一问题,我开发了一个开源的多数据源告警引擎——…...
百度面试手撕 go context channel部分学习
题目 手撕 对无序的切片查询指定数 使用context进行子协程的销毁 并且进行超时处理。 全局变量定义 var (startLoc int64(0) // --- 未处理切片数据起始位置endLoc int64(0) // --- 切片数据右边界 避免越界offset int64(0) // --- 根据切片和协程数量 在主线程 动态设…...
主流浏览器内核
1、Chromium内核 由Google主导开发,最具代表性的是Google Chrome浏览器。Chromium内核的优点包括无广告、不易崩溃、速度快、安全性高。目前,许多浏览器都采用了Chromium内核,如360极速浏览器、UC浏览器、QQ浏览器等,windows自…...
Apache Tomcat 漏洞CVE-2024-50379条件竞争文件上传漏洞 servlet readonly spring boot 修复方式
1,关于漏洞 Apache Tomcat是一个流行的开源 Web 服务器和 Java Servlet 容器。 二、 漏洞描述 Apache Tomcat中修复了个 TOCTOU 竞争条件远程代码执行漏洞 (CVE-2024-50379),该漏洞的 CVSS 评分为 9.8。Apache Tomcat 中 JSP 编译期间存在检查时间使用时…...
Java学习教程,从入门到精通,Java LinkedList(链表)语法知识点及案例代码(62)
Java LinkedList(链表)语法知识点及案例代码 一、LinkedList概述 LinkedList是Java集合框架中的一个类,位于java.util包中。它实现了List、Deque、Queue等接口,提供了链表数据结构的实现。链表是一种线性数据结构,其…...
3分钟读懂数据分析的流程是什么
数据分析是基于商业目的,有目的地进行收集、整理、加工和分析数据,提炼出有价值的 信息的一个过程。整个过程大致可分为五个阶段,具体如下图所示。 1.明确目的和思路 在开展数据分析之前,我们必须要搞清楚几个问题,比…...
【Leetcode 热题 100 - 扩展】303. 区域和检索 - 数组不可变
问题背景 给定一个整数数组 n u m s nums nums,处理以下类型的多个查询: 计算索引 l e f t left left 和 r i g h t right right(包含 l e f t left left 和 r i g h t right right)之间的 n u m s nums nums 元素的 和 &a…...
Leecode刷题C语言之同位字符串连接的最小长度
执行结果:通过 执行用时和内存消耗如下: bool check(char *s, int m) {int n strlen(s), count0[26] {0};for (int j 0; j < n; j m) {int count1[26] {0};for (int k j; k < j m; k) {count1[s[k] - a];}if (j > 0 && memcmp(count0, cou…...
API开发:Flask VS FastAPI
在当今的Web开发领域,选择合适的框架对于构建高效、稳定且易于维护的API至关重要。Flask和FastAPI是两个备受关注的Python Web框架,它们各自具有独特的特点和优势,适用于不同的开发场景。 文章目录 一、简介二、性能表现三、开发效率…...
fastAPI接口的请求与响应——基础
1. 后端接口 1.接口实现 pip install fastapi pip install uvicorn# api.py from pydantic import BaseModel from fastapi import FastAPI import uvicorn import os# 定义请求体模型 class Payload(BaseModel):key1: strkey2: str# 创建一个FastAPI应用程序实例 app Fast…...
[oeasy]python054_python有哪些关键字_keyword_list_列表_reserved_words
python有哪些关键字_keyword_list_列表_reserved_words 回忆上次内容 hello world 不是 从来就有的 来自于 c语言 print、小括号 和 双引号 也来自于 c语言 添加图片注释,不超过 140 字(可选) python 标识符 的 命名规则 依然 完全 学习…...
面试题整理2---Nginx 性能优化全方案
面试题整理2---Nginx 性能优化全方案 1. 调整工作进程数和线程数1.1 调整工作进程数1.2 调整进程的最大连接数 2. 配置Gzip压缩2.2 配置Gzip压缩 3. 配置缓存策略3.1 配置浏览器缓存时间3.2 配置代理服务器缓存时间 4. 优化文件访问方式4.1 使用sendfile()函数发送文件数据4.2 …...
15.3、陷阱技术 入侵容忍 隐私保护技术
目录 网络攻击陷阱技术与应用蜜罐主机技术陷阱网络技术三代陷阱网络网络攻击陷阱技术应用入侵容忍及系统生存技术入侵容忍及系统生存技术应用隐私保护技术网络安全的前沿技术发展动向 网络攻击陷阱技术与应用 攻击陷阱技术也叫诱骗技术,它是一种主动防御的方法&…...
PyQt6 学习记录1
简介 PyQt6 是基于 Python 的一系列模块。它是一个多平台的工具包,可以在包括 Unix、Windows 和 Mac OS 在内的大部分主要操作系统上运行。 安装 pip install PyQt6 模块 - QtCore - QtGui - QtWidgets - QtDBus - QtNetwork - QtHelp - QtXml - QtSvg - QtSql…...
ModelArts平台打卡体验活动:MindSpore2.4.0快速入门
一、环境准备 1.进入ModelArts官网 云平台帮助用户快速创建和部署模型,管理全周期AI工作流,选择下面的云平台以开始使用昇思MindSpore,可以在昇思教程中进入ModelArts官网 创建notebook,点击【打开】启动,进入ModelA…...
python学习——洛谷P2010 [NOIP2016 普及组] 回文日期 三种方法
[NOIP2016 普及组] 回文日期 文章目录 [NOIP2016 普及组] 回文日期题目背景题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 样例 #2样例输入 #2样例输出 #2 提示方法一方法二方法三 题目背景 NOIP2016 普及组 T2 题目描述 在日常生活中,通过年、月、日这…...
现代风格VUE3易支付用户控制中心
适用系统 彩虹易支付 技术栈 vitevue3elementuiplusphp 亮点 独立前端代码,扩展开发,不改动系统文件,不影响原版升级 支持功能订制 界面预览...
重生之我在异世界学智力题(9)
大家好,这里是小编的博客频道 小编的博客:就爱学编程 很高兴在CSDN这个大家庭与大家相识,希望能在这里与大家共同进步,共同收获更好的自己!!! 本文目录 引言时间旅行者的逻辑谜题题目描述&#…...
《Mycat核心技术》第05章:Mycat中文乱码解决方案
作者:冰河 星球:http://m6z.cn/6aeFbs 博客:https://binghe.gitcode.host 文章汇总:https://binghe.gitcode.host/md/all/all.html 星球项目地址:https://binghe.gitcode.host/md/zsxq/introduce.html 沉淀,…...
Flink执行模式(批和流)如何选择
DataStream API支持不同的运行时执行模式(batch/streaming),你可以根据自己的需求选择对应模式。 DataStream API的默认执行模式就是streaming,用于需要连续增量处理并且预计会一直保持在线的无界(数据源输入是无限的)作业。 而batch执行模式则用于有界(输入有限)作业…...
vue 文本域 展示的内容格式要和填写时保持一致
文本域 展示的内容格式要和填写时保持一致 <el-inputtype"textarea":rows"5"placeholder"请输入内容"v-model"formCredit.point"style"width:1010px;" > </el-input> 样式加个: white-space: pre-w…...
计算机基础知识复习12.20
B树与B树差异的点 叶子节点最底部的节点才会存放实际数据(索引记录),非叶子节点只会存放索引 所有索引都会在叶子节点出现,叶子节点之间构成一个有序链表 为什么mysql使用B树 单点查询:B树的非叶子节点不存放实际的记录数据,仅…...
Flash语音芯片相比OTP语音芯片的优势
Flash语音芯片和OTP语音芯片是两种常见的语音解决方案,在各自的应用领域中发挥着重要作用。本文将介绍Flash语音芯片相比OTP(One-Time Programmable)语音芯片的显著优势。 1.可重复擦写:Flash语音芯片的最大特点是支持多次编程和擦除,…...
深度学习模型中增加随机性可以通过多种方式实现,以下是一些可以应用到你的 `TCNAttentionLSTM`
在深度学习模型中增加随机性可以通过多种方式实现,以下是一些可以应用到你的TCNAttentionLSTM模型中的方法: ### 1. Dropout 你已经在模型中使用了dropout,这是增加随机性的一种常见方法。你可以通过调整dropout率来控制随机性的程度。 ###…...
JavaScript:一个争议与机遇并存的编程语言
JavaScript的诞生带着某种戏剧性色彩。1995年,网景公司的Brendan Eich在短短10天内设计出了这门语言。这种仓促的诞生为JavaScript埋下了许多设计缺陷的隐患,这些问题直到今天仍在困扰着开发者。然而,正是这样一个充满争议的语言,…...
后摩尔定律时代,什么将推动计算机性能优化的发展?
在摩尔定律时代,每两年芯片上的晶体管数量就会翻一番,这一看似不可避免的趋势被称为摩尔定律,它极大地促进了计算机性能的提高。然而,硅基晶体管不可能一直小下去,半导体晶体管的微型化推动了计算机性能的提升…...
台球助教平台系统开发APP和小程序信息收藏功能需求解析(第十二章)
以下是开发台球助教系统客户端(APP,小程序,H5)几端的信息收藏功能的详细需求和功能说明,内容比较详细,可以说是一个教科书式的详细说明了,这套需求说明不仅仅用在我们的台球助教系统程序上&…...
软件实验室认可|实验室比对的形式与方法
实验室比对是软件测试实验室在申请软件实验室认可必须要做的一类质量控制活动。实验室比对包括实验室间比对,和实验室内比对。实验室间比对指的是按照预先规定的条件,由两个或多个实验室对相同或类似的物品进行测量或检测的组织、实施和评价。实验室内比…...
HarmonyOS NEXT开发进阶(四):@Builder 装饰器实现UI结构复用
一、前言 当页面有多个相同的UI结构时,若每个都单独声明,同样会有大量重复的代码。为避免重复代码,可以将相同的UI结构提炼为一个自定义组件,完成UI结构的复用。 除此之外,ArkTS还提供了一种更轻量的UI结构复用机制B…...
Vivado/Vitis中自定义IP的驱动
在使用Xilinx FPGA时,很多时候都要添加自定义的IP以实现相应的功能,一些时候会通过AXI总线与软核或硬核相连,这个时候就可以在软件中对IP进行控制。 当我们打开Vitis开始软件编写时,会遇到自定义IP驱动编译不过的情况,…...
CentOS7安装Python3.9(已装python2.6)
Python下载地址:Index of /ftp/python/ 下载并解压 # 安装wgetyum -y install wget # 下载wget https://www.python.org/ftp/python/3.9.0/Python-3.9.0.tgz # 解压tar zxvf Python-3.9.0.tgz # 切换目录cd Python-3.9.0 编译并安装 # 编译 ./configure --prefix/us…...
cenos如何升级git到2以上版本
1:先卸载旧的版本: # 卸载源默认安装的git $ git --version git version 1.8.3.1 $ sudo yum remove git2: 安装新的git版本 编译 配置环境变量 下载相关依赖 并安装 [rootlocalhost /]# yum install curl-devel expat-devel openssl-devel zlib-devel gcc […...
【JetPack】Navigation知识点总结
Navigation的主要元素: 1、Navigation Graph: 一种新的XML资源文件,包含应用程序所有的页面,以及页面间的关系。 <?xml version"1.0" encoding"utf-8"?> <navigation xmlns:android"http://schemas.a…...
图文社区用户搜索关系表设计方案:空间换时间的权衡与抉择
背景 我们来聊一个解决方案:我们做了一个和抖音产品类似的图文社区,社区有一个搜索栏,通过名字搜索用户,搜索出来的用户需要体现出其与当前用户的关系:1.当前用户的粉丝。2.当前用户关注的人。3.互相关注。目前总用户…...
问题小记-达梦数据库报错“字符串转换出错”处理
最近遇到一个达梦数据库报错“-6111: 字符串转换出错”的问题,这个问题主要是涉及到一条sql语句的执行,在此分享下这个报错的处理过程。 问题表现为:一样的表结构和数据,执行相同的SQL,在Oracle数据库中执行正常&…...
空天地遥感数据识别与计算--数据分析如何助力农林牧渔、城市发展、地质灾害监测等行业革新
在科技飞速发展的时代,遥感数据的精准分析已经成为推动各行业智能决策的关键工具。从无人机监测农田到卫星数据支持气候研究,空天地遥感数据正以前所未有的方式为科研和商业带来深刻变革。然而,对于许多专业人士而言,如何高效地处…...
Cherno C++ 学习笔记 智能指针
这一篇我们会讲到一个C当中非常重要的特性,也就是智能指针smart pointer。在我们知道了new和delete两个关键字之后,我们了解了如何在堆上分配内存以及如何释放内存。但是当我们new了一个或者一堆对象之后,很多时候我们会容易忘掉delete所有需…...
C++ OpenGL学习笔记(2、绘制橙色三角形绘制、绿色随时间变化的三角形绘制)
相关文章链接 C OpenGL学习笔记(1、Hello World空窗口程序) 目录 绘制橙色三角形绘制1、主要修改内容有:1.1、在主程序的基础上增加如下3个函数1.2、另外在主程序外面新增3个全局变量1.3、编写两个shader程序文件 2、initModel()函数3、initS…...
如何创建属于自己的大语言模型:从零开始的指南
如何创建属于自己的大语言模型:从零开始的指南 为什么要创建自己的大语言模型? 随着人工智能的快速发展,大语言模型(LLM)在各种场景中表现出了卓越的能力,例如文本生成、对话交互和内容总结等。虽然市场上…...
【AIGC安全】CCF-CV企业交流会直播回顾:探寻AI安全治理,共筑可信AI未来
文章目录 一、活动背景:AI技术快速发展与安全治理需求迫切二、论坛内容金耀辉:智能共生时代:平衡生成式AI的创新与风险何延哲:人工智能安全检测评估的逻辑和要点谢洪涛:面向特定人物深度伪造视频的主动防御与被动检测技…...
win11+cuda11x+VS2019安装后没有cuda模板,贫穷版cuda环境
显卡是GTX710,挂在闲鱼200块钱一年多都没卖出去的一款。win11最开始安装了visual studio 2022,不过安装目录自定义,后续安装cuda 11.1.1,这个顺序在各种博客资料都是正确的,先VS,后cuda。但是创建项目也没有…...
四相机设计实现全向视觉感知的开源空中机器人无人机
开源空中机器人 基于深度学习的OmniNxt全向视觉算法OAK-4p-New 全景硬件同步相机 机器人的纯视觉避障定位建图一直是个难题: 系统实现复杂 纯视觉稳定性不高 很难选到实用的视觉传感器 为此多数厂家还是采用激光雷达的定位方案。 OAK-4p-New 为了弥合这一差距…...
越疆科技营收增速放缓:毛利率未恢复,持续亏损下销售费用偏高
《港湾商业观察》施子夫 12月13日,深圳市越疆科技股份有限公司(以下简称,越疆科技,02432.HK)发布全球发售公告,公司计划全球发售4000万股股份,其中3800万股国际发售,200万股香港公开…...
南城云趣:智能云平台,杜绝电动车充电安全隐患
电动自行车作为绿色低碳出行的主要方式之一,受到无数市民的推崇,而电动自行车数量的急剧上涨,也严重增加小区管理的负担。记者调查发现,目前电动自行车缺乏有效的管理,使得带车或电瓶上楼充电、乱停乱放、车辆容易被盗等安全问题日益突出,给社区消防安全和管理带来严峻的挑战。…...
uniapp对接unipush 1.0 ios/android
简介 实现方法 是uniapp官网推荐的 unipush-v1 文档配置具体看 uni-app官网 配置好了之后 代码实现 前端代码 前端的主要任务是监听 监听到title content 创建消息推送 安卓 可以收到在线消息并且自动弹出消息 IOS 可以监听到在线消息但是需要手动推送 以下代码app初始…...
Vue.js前端框架教程11:Vue监听器watch和watchEffect
文章目录 监听器(watchers)基本用法deep: trueimmediate: true总结 watchEffect基本用法自动追踪依赖停止监听与 watch 的对比性能优化总结 监听器(watchers) 在 Vue 中,监听器(watchers)是一种…...