年级数据表的分页基本操作实现node+js

227 阅读8分钟

前言

学生表与班级表数据使用mongodb数据库 node使用mongoose连接,Express 框架来创建Express 服务器,模块分离,前端使用html,css,js,在此之前已用vue2,vue3都写过但没后台,在学习vue时还不能理解响应式好处再次使用js后就能明白了,js里如果 一个基本类型变量=一个数据 后续使用其他方式修改这个数据后 变量不会变 而vue只要做了响应式变量也会改变,导致每次js做完一个操作后就是大量数据手动改变

node

连接数据库

使用try,ctach 处理错误使用async await处理异步 连接本地的playground数据库 并将连接暴露出去

const mongoose = require('mongoose');

async function connectToDatabase() {
    try {
        await mongoose.connect('mongodb://127.0.0.1:27017/playground');
        return mongoose.connection;
    } catch (error) {
        console.error('连接失败', error);
        throw error;
    }
}


// 确保正确导出函数
module.exports = {
    connectToDatabase
};

定义模型

多个学生表和班级表 没有做严格的字段设置 想连接数据库下的已有集合需要在第三个参数指明集合名 因为 mongoose.mode的第一个参数也就是集合名 会给你加s

const mongoose=require('mongoose')
// 定义学生模式
const studentSchema=new mongoose.Schema({
    学号:String,
    姓名:String,
    性别:String,
})
// 定义班级模式
const classSChema=new mongoose.Schema({
    classNumber:Number,
    specialtyName:String,
    instructorName:String,
})
// 定义学生模型
const studentTableNames=['java1班','java2班','web1班','web2班','web3班','web4班']
const studentModels=studentTableNames.map(name=>{
    return mongoose.model(name,studentSchema,name)
})
const classAll=mongoose.model('2023级',classSChema,'2023级')

module.exports={
    classAll,
    studentModels,
    studentSchema
}

初始化数据

由于学生表是多个用数组包括着 所以使用Promise.all并行查找 find({})就是所有数据 而获取后studentResults={Model,Model,Model,Model...}是 Mongoose 文档对象集合 allClasses也是所以需要将其转换为正常的数组对象 使用toObject()将文档转换为正常的对象形式 才可用. []的方式获取属性值 学生表也是使用...拓展运算符 后和班级表组成一个新表既有学生数据和班级数据,由于没做学号唯一认证 而班级与学生的关联就是学号的前七位 所以关闭了.filter班级号过滤学号前7位与班级表没对应的,最后暴露出去 注意由于是async定义的返回的是Promise 使用时也要做异步处理

const { models } = require("mongoose");
const { connectToDatabase } = require("./connection")
const {classAll,studentModels}=require('./定义模型')
async function getStudents(){
    // MongoDB的连接
    let dbConnection;
    try{
        dbConnection=await connectToDatabase()
        // 并行获取多个学生数据并返回结果数组
        const studentResults=await Promise.all(studentModels.map(model=>model.find({})))
        // flat()使用递归将多维数组降维  默认降一维
        const allStudents=studentResults.flat()    
        // 获取班级数据
        const allClasses=await classAll.find({})
        const classMap={}
        // 将班级数据转换为数组对象形式 方便后续查找
        allClasses.forEach(el=>{
            // calssMap[class.cla]
            el=el.toObject()
            classMap[el.班级编号]=el
        });
        // 关联学生和班级数据
        const result=allStudents.map(student=>{
            // 当前是model类型
            student=student.toObject()
            const classNumber=student.学号.slice(0,7);
            const classInfo=classMap[classNumber];
            if(classInfo){
                return{
                    ...student,
                    classInfo:{
                        班级编号: classInfo.班级编号,
                        导员: classInfo.辅导员,
                        专业名称: classInfo['行政班名']
                    }
                }
            }
            return null;
            // 过滤掉没有班级信息的学生
        })
        // .filter(item=>item!=null)
        
        return result
    }catch(error){
        console.error('关联数据时出错:', error);
        throw error
    }finally{
        // 没连接上时或等待时间过长时关闭连接
        if(dbConnection){
            dbConnection.close
        }
    }
}
module.exports = {
    getStudents
};

创建服务器

配置中间件将前端附带的数据进行json解析 并配置静态资源,我只给app.get('/student) 和查询操作返回了数据 懒 前端发了添加删除修改请求和初始数据请求后的数据获取并对其进行划分我写成了一个函数 不想由于不同情况做出一个返回数据 一个不返回数据js部分 由于id也就是学号是唯一标识 也是知道数据在哪个数据集合操作的指明 所以前端返回的数据没什么区别

const express=require('express')
// 初始数据
const { getStudents } = require('./studentsInitial');
// 添加操作
const {insertStudent}=require('./添加数据')
// 删除操作
const {deleteStudent}=require('./删除数据')
// 更新操作
const {updateStudent}=require('./修改数据')
// 查询操作
const {searchStudents}=require('./查询数据')
const app = express()
const port = 3001
app.use(express.static(__dirname+'/public'))

app.use(express.json())

// 获取最新数据
app.get('/student',async(req,res)=>{
    try{
        const data=await getStudents()
        // console.log(data.length);
        res.json(data) 
    }catch(error){
        res.status(500).json({error:"获取关联数据时错误"})
    }
})

app.get('/search',async(req,res)=>{
    try {
        // 从查询参数中获取姓名和学号
        const { name, studentNumber } = req.query
        // console.log(name,studentNumber)
        
        // 调用 searchStudents 函数进行查询
        const data = await searchStudents(name, studentNumber)
        // 将查询结果以 JSON 格式返回给客户端
        res.json(data)
    } catch (error) {
        // 若查询出错,返回 500 状态码和错误信息
        res.status(500).json({ error: "查询数据时出错" })
    }
})


// 添加
app.post('/student',async(req,res)=>{
    try {
        const studentData = req.body
        // console.log(studentData);
        const result = await insertStudent(studentData)
        res.status(201).json(result)
    } catch (error) {
    res.status(400).json({ error: error.message })
    }
})
// 删除  /studnt 下的任何参数 变为studentNumber
app.delete('/student/:studentNumber',async(req,res)=>{
    try{
        const studentNumber = req.params.studentNumber
        
        const isDeleted=await deleteStudent(studentNumber)
        // 会操作的结果返回布尔
        if (isDeleted) {
            res.status(200).json({ message: '学生数据删除成功' })
        } else {
            res.status(404).json({ message: '未找到该学号对应的学生' })
        }
        // 
    }catch(error){
         // 若未找到学生,返回 404 状态码并设置描述
        res.status(404).json({ message: '未找到该学号对应的学生' })
    }
})

// 修改数据
app.put('/student/:studentNumber', async(req, res) => {
    try{
        // 学号作为查找数据的唯一标识
        const studentNumber = req.params.studentNumber
        // 主要修改数据 把多余数据排除
        const updateData=req.body
        
        const isUpdated=await updateStudent(studentNumber,updateData)
        if(isUpdated) {
            res.status(200).json({ message: '学生数据更新成功' })
        } else {
            res.status(404).json({ message: '未找到该学号对应的学生' })
        }
    }catch(error){
        res.status(500).json({ message: '更新学生数据时出错', error: error.message })
    }
});
// 监听端口
app.listen(port, (err) => {
    if (err) {
        console.error(`服务器启动失败: ${err.message}`);
    } else {
        console.log(`服务器已经启动,http://127.0.0.1:${port}`);
    }
});

普通数据操作

添加

studentData是使用函数时传达的形参 studentModels第一模型的暴露学生表这是一个数组数组内是学生集合await newStudent.save() newStudent.save()是一个操作往对应的集合添加文档数据有返回添加数据

try{
        // console.log(studentData,studentModels);
        const classIndex = studentData.classInfo?.班级表 - 1;
         // 检查索引是否有效
        if (classIndex === undefined || classIndex < 0 || classIndex >= studentModels.length) {
            throw new Error('无效的班级表索引');
        }
        // 获取对应的模型
        const targetModel = studentModels[classIndex];
        // 移除 studentData 中的 classInfo 字段,因为可能不需要存储该信息到数据库
        const { classInfo, ...cleanStudentData } = studentData;
        // console.log(cleanStudentData);
        
        // 使用对应的模型创建新的文档实例并保存
        const newStudent = new targetModel(cleanStudentData);
        // console.log(newStudent);
        
        const savedStudent = await newStudent.save()
删除

就是找到对应学号的文档就行删除就行 修改差不多也是看找到对应学号的文档并使用model.findOneAndUpdate

 for (const model of studentModels) {
            // 删除操作 并将结果返回给result
            const result = await model.deleteOne({ 学号: studentNumber });
            // result.deletedCount记录了有多少个符号标准的文档
            if (result.deletedCount > 0) {
                // 若删除成功,返回 true
                return true;
            }
        }
        // 若未找到对应学号的学生,返回 false
        return false;
查询

与数据初始化的不同就是find({})时将对应的{}里的数据改成你需要的数据就行模糊搜索使用正则

  try{

        // 构建每个模型的查询条件
        const queries = studentModels.map(model => {
            const conditions = {};
            if (name) {
                // 姓名模糊搜索
                conditions.姓名 = new RegExp(name, 'i'); 
            }
            if (studentNumber) {
                // 学号精确匹配
                conditions.学号 = studentNumber; 
            }
            return model.find(conditions);
        });

        // 并行执行所有查询
        const results = await Promise.all(queries);

        // 合并所有查询结果
        const allStudents = results.flat();

        // 获取班级数据
        const allClasses = await classAll.find({});
        // 构建班级映射  与studentsInitial相似
        const classMap = {};
        allClasses.forEach(el => {
            el = el.toObject();
            classMap[el.班级编号] = el;
        });
        // console.log(allClasses);
        
        // 关联学生和班级数据 将查询到的新数据进行studentsInitial里的数据关联操作
        const finalResult = allStudents.map(student => {
            // 当前是model类型 要将其转换为对象类型
            student = student.toObject();
            const classNumber = student.学号.slice(0, 7);
            const classInfo = classMap[classNumber];
            if (classInfo) {
                return {
                    ...student,
                    classInfo: {
                        班级编号: classInfo.班级编号,
                        导员: classInfo.辅导员,
                        专业名称: classInfo['行政班名']
                    }
                };
            }
            return null;
            // 过滤掉前7位学号与班级表不匹配的学生数据
        })
        // .filter(item => item !== null);
        return finalResult;
    }catch (error) {
        console.error('搜索学生数据时出错:', error);
        throw error;

前端

屏幕截图 2025-04-26 142903.png

数据渲染和请求数据

请求数据
   window.addEventListener('DOMContentLoaded',async()=>{
        fetchStudentData()
    })


    async function fetchStudentData() {
        try {
            const response = await fetch('http://127.0.0.1:3001/student');
            // 检测响应是否正常
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            // 将响应数据解析为 JSON 格式
            const students = await response.json();
            // 这里可以添加渲染学生数据的代码
            // console.log(students);
            学生原数据 = students;
            当前学生数据 = students;
            sumNumber.innerHTML = `共${当前学生数据.length}条`;
            pagesSumNumber = Math.ceil(当前学生数据.length / pageValue);
            // 从第 1 页开始渲染数据
            applyAllRows(当前学生数据);
            // 生成从第 1 页开始的分页按钮
            添加页数按钮(pagesSumNumber);
        } catch (error) {
            console.error('加载学生数据时出错:', error);
        }
    }
数据渲染
    function applyAllRows(过滤后数据){
        removeAllRows();
    //     console.log('pageSelect'+':'+pageSelect);
    // console.log('pageValue'+':'+pageValue);
    // console.log('currentPage'+':'+currentPage);
        const pageData = getPageData(过滤后数据, currentPage, pageValue);
        pageData.forEach(student => {
            const row = document.createElement('tr');
            row.innerHTML = `
                <td><input type="radio"></td>
                <td >${student.学号}</td>
                <td id="skipName">${student.姓名}</td>
                <td>${student.性别}</td>
                <td>${student.classInfo.班级编号}</td>
                <td>${student.classInfo.专业名称.slice(12)}</td>
                <td>${student.classInfo.导员}</td>
                <td>
                    <span class="changeListItem">修改</span>
                </td>
                <td>
                    <span class="delectListItem">删除</span>
                </td>
            `;
            studentTableList.appendChild(row);
            const skipName=row.querySelector('#skipName')
            skipName.addEventListener('click',()=>{
                  // 获取姓名
                  const name = skipName.textContent;
                // 对姓名进行编码,防止特殊字符问题
                const encodedName = encodeURIComponent(name);
                // 跳转到 a.html 并携带姓名参数
                window.location.href = `a.html?name=${encodedName}`;
            })
            const deleteListItem = row.querySelector('.delectListItem');
            // 为每行的删除按钮添加点击事件监听器
            deleteListItem.addEventListener('click', async () => {
                const studentNumber = student.学号;
                try {
                    // 删除请求
                    const response = await fetch(`http://127.0.0.1:3001/student/${studentNumber}`, {
                        method: 'DELETE'
                    });

                    if (!response.ok) {
                        throw new Error(`HTTP error! status: ${response.status}`);
                    }

                    // 删除成功后重新获取数据
                    await fetchStudentData();
                } catch (error) {
                    console.error('删除学生数据时出错:', error);
                }
            });
            const changeListItem = row.querySelector('.changeListItem');
            changeListItem.addEventListener('click', () => {
                maskLayer.classList.remove('maskLayerFShow');
                maskLayer.classList.add('maskLayerShow');
                stateWidow = false;

                // 弹窗弹出时的默认当前数据
                document.getElementById('name').value = student.姓名;
                let studentNumber=document.getElementById('studentNumber')
                studentNumber.value = student.学号;
                // 设置学号输入框为只读
                studentNumber.readOnly = true
                document.getElementById('sex').value = student.性别;
                document.getElementById('changeClassSelect').value = student.classInfo.班级编号;
            });
        });

    }

添加数据和修改数据

发送请求就不写了 就是获取元素 value值然后携带在地址上

屏幕截图 2025-04-26 143645.png

页码

屏幕截图 2025-04-26 143833.png

function 添加页数按钮(num){
        const pageNation = document.querySelector('.pageNation');
        // 转换伪元素
        const delButtons = Array.from(pageNation.children).slice(1, -1);
        // 删除这些按钮
        delButtons.forEach(button => {
            pageNation.removeChild(button);
        });
        // 计算起始页码和结束页码
        // 刚开始是第1页   currentPage-4  使每次选中的页码都在中间 也可以使当前显示的起始页(当前页小于4时)永远是当前页的前4页
        let startPage=Math.max(1,currentPage-4)

        // 每次最多10个  由于起始页是除初始化时(当前页小于4时)是第1页  结束页是10  后续也就是起始页加9 也就是currentPage
        let endPage=startPage+9
        if (endPage > num) {
            // 当最后一页大于总页数时,将总页数设置为结束页·
            endPage = num;
            // 起始页就是结束页减去9
            startPage = Math.max(1, endPage - 9);
        }
        for (let i = startPage; i <= endPage; i++) {
            const button = document.createElement('button');
            button.textContent = i;
            if (i === currentPage) {
                button.classList.add('bgColor');
            }
            button.addEventListener('click', () => {
                currentPage = i;
                // 添加num就是添加当前总页数pagesSumNumber
                添加页数按钮(pagesSumNumber);
                // 并执行渲染学生数据
                applyAllRows(当前学生数据);
            });
            pageNation.insertBefore(button, pageNation.lastElementChild)
        }
    }
    添加页数按钮(pagesSumNumber)

分班

屏幕截图 2025-04-26 143901.png

// 班级下拉的选择逻辑
    classSelect.addEventListener('change', function () {
        // 获取当前选中的选项的值
        const selectedValue = this.value;
        const selectedIndex = this.selectedIndex-2;
        currentPage=1
        // console.log(`选中的班级是: ${selectedValue},是 select 中的第 ${selectedIndex} 个选项`);

        // 删除表格除表头外的所有行
        removeAllRows();

        // 根据选中的班级筛选数据并重新渲染表格
        const filteredStudents = 学生原数据.filter(student => {
            
            return selectedValue === '全部' || student.classInfo.班级编号 === classArray[selectedIndex];
        });
        
        sumNumber.innerHTML=`共${filteredStudents.length}条`
        // console.log(filteredStudents);
        当前学生数据=filteredStudents
        pagesSumNumber=parseInt(filteredStudents.length/pageValue)+1
        添加页数按钮(pagesSumNumber)
        applyAllRows(filteredStudents)
    });

总结

经过几天的node学习后并亲自实现后更加了解到前后端的交互实现,不过在学习vue后原生js感觉真麻烦无论什么动不动就要手动修改所有使用的变量 变量也是一堆光在所有函数外的变量有11个 实现分页的有4,5个 然后剩下的都是获取按钮绑定事件的,ES6模块化也学了但真不会用 由于是第一次前后端都写写一半想一半写完就东一块西一块了划分不了