HTML+CSS+JS对话式实战(3)-活动管理页实现查询、新增、删除API后端接口

点此查看 所有教程、项目、源码导航

文章目录

  • [1. 任务说明](#1. 任务说明)
  • [2. 后端返回统一格式讲解](#2. 后端返回统一格式讲解)
  • [3. 修改HTML文件,引入样式和JS文件](#3. 修改HTML文件,引入样式和JS文件)
  • [4. 编写 common.js:通用POST请求封装](#4. 编写 common.js:通用POST请求封装)
  • [5. 功能1:查询活动列表,渲染表格](#5. 功能1:查询活动列表,渲染表格)
  • [6. 功能2:新增活动,弹窗表单提交接口](#6. 功能2:新增活动,弹窗表单提交接口)
  • [7. 功能3:删除活动接口](#7. 功能3:删除活动接口)
  • [8. 小结](#8. 小结)

1. 任务说明

副队:毛哥,咱们项目中JS、CSS文件不多,就不单独建文件夹了,全部和html放在同一级目录。另外有一个关键点,通用的post请求函数,属于整个项目公用代码,可以抽取到通用的JS文件中。

毛哥:哦,我懂了!如果以后还有学生页面,也需要发请求,那还得再复制一遍这个函数,就重复了对吧?

副队:对的,咱们单独新建一个 common.js 放通用请求方法,所有页面都可以引入它。咱们开发顺序调整为:先实现查询活动列表、再实现新增活动、最后实现删除活动,页面打开优先展示已有活动列表,更符合用户正常使用习惯。

调整后的目录结构:

复制代码
├── common.css
├── activity-manage.css
├── common.js
├── activity-manage.js
└── activity-manage.html
  • common.css:公共样式,导航、按钮、表格、弹窗通用样式
  • activity-manage.css:活动管理页面专属样式
  • common.js:项目通用JS,这里放封装好的post请求函数
  • activity-manage.js:活动管理页面专属交互、业务逻辑
  • activity-manage.html:活动管理页面主体

2. 后端返回统一格式讲解

副队:在写接口之前,咱们再巩固一个重点:咱们所有接口,后端返回数据统一是 {code, msg, data} 这个JSON结构,这个设计非常关键,你想想为什么要统一格式?

毛哥:我猜一下,如果每个接口返回的样子不一样,我写前端的时候,每个接口都要单独判断,会很乱?

副队:没错!就是这个道理,我给你拆开讲三个字段:

  • code:状态码,数字。code:200 代表本次请求业务成功;其他数字代表失败,比如400参数不对、500服务器出错。前端判断code就能知道这次操作成功还是失败。
  • msg:提示消息,字符串。成功的时候可以写"新增成功",失败的时候写"活动名称不能为空"这类文字,直接alert给用户看。
  • data:真正的数据。成功的时候,需要返回的数据放在这里;像删除这种不需要返回数据的接口,data可以是空。
    毛哥:我明白了!不管是查列表、新增、删除,返回的外壳都是这三个字段,我写前端代码的时候,处理逻辑是一套,不用来回改判断方式,太方便了!

3. 修改HTML文件,引入样式和JS文件

在上一篇的 activity-manage.html 的 <head> 里面,先引入公共样式,再引入页面专属样式:

html 复制代码
<link rel="stylesheet" href="common.css">
<link rel="stylesheet" href="activity-manage.css">

JS引入顺序很重要:先引入common.js(通用方法),再引入页面自己的activity-manage.js,保证页面JS里可以调用postRequest。

在 </body> 标签上方引入JS:

js 复制代码
<script src="common.js"></script>
<script src="activity-manage.js"></script>

副队:顺序不能反,要是先引入页面JS,那时候通用请求方法 还没有定义,直接报错。

毛哥:记下了,公共的在前,页面业务在后。

4. 编写 common.js:通用POST请求封装

新建 common.js,写入通用请求函数:

js 复制代码
/* 定义基准请求地址,此处是副队提前部署好的服务器域名+端口+后端项目名 */
let baseUrl = "http://www.pandabrother.cn:8080/java-api-demo";

/**
 * 通用POST请求函数
 * @param {string} url 请求地址
 * @param {object} params 传给后端的参数对象
 * @param {function} callback 请求完成后的回调函数
 */
function postRequest(url, params, callback) {
    // 创建XHR对象
    let xhr = new XMLHttpRequest();
    // 使用POST方式打开连接
    xhr.open('POST', baseUrl + url);
    // 设置请求头,告诉后端传递的是JSON格式数据
    xhr.setRequestHeader('Content-Type', 'application/json');
    // 监听请求状态变化
    xhr.onreadystatechange = function () {
        // readyState=4 代表请求已经完成
        if (xhr.readyState === 4) {
            // 把后端返回的JSON字符串转为JS对象
            let res = JSON.parse(xhr.responseText);
            // 把返回结果交给回调函数处理
            callback(res);
        }
    }
    // 参数对象转为JSON字符串发送给后端
    xhr.send(JSON.stringify(params));
}

毛哥:副队,这里的 callback 我有点懵,这个回调到底是什么意思?

副队:这个是很多初学者卡在这里的地方,我给你打个比方。咱们发网络请求,是一件慢动作 ,不是立刻就能拿到结果。

毛哥:慢动作?

副队:对,代码执行到 xhr.send() 的时候,只是把消息发给后端了,然后JS会继续往下跑别的代码,不会卡在原地等后端返回数据。等后端处理完、数据回来了,onreadystatechange 才会触发。

毛哥:那我怎么知道数据什么时候回来?

副队:所以我们提前把"拿到数据之后要干什么"这一段逻辑,打包成一个函数,当做参数传给 postRequest。这个打包好的函数,就是 callback,回调函数。

毛哥:哦!相当于我提前写好一张纸条:等数据回来了,就执行纸条上的操作?

副队:这个比喻很到位!postRequest 本身只负责一件事:发POST请求、等响应。它不关心拿到数据之后是弹窗、刷新表格还是删除提示 。这些业务逻辑,交给外面传进来的 callback。

毛哥:我好像懂了!那每次调用 postRequest 的时候,我写的那个 function(res) { ... } 就是 callback?

副队:没错!你看后面查询列表那里:

复制代码
postRequest('/api/activity/list', {}, function(res) {
    // 这里就是回调,拿到res之后渲染表格
})

这一段 function,就是 callback。请求成功拿到 res,postRequest 内部执行 callback(res),把后端返回的数据送进去,交给你写的业务代码处理。

毛哥:那如果我不用回调,直接写一个变量接收返回值行不行?

副队:不行,这就是异步的特点。如果写 let result = postRequest(...),result 拿到的一定是 undefined,因为请求还没回来,代码早就跑完了。必须等网络响应回来,才能处理结果。

毛哥:明白了!postRequest 是工具,只管发请求;回调是我交给这个工具的任务清单,等数据回来自动执行清单上的逻辑。

5. 功能1:查询活动列表,渲染表格

副队:咱们优先写查询。页面一打开,第一件事就是调用列表接口,从后端拿到已经存在的活动数据,渲染到表格。用户进来第一眼就能看到全部活动。

毛哥:懂了,不管数据库里有没有数据,页面加载都会自动查询一遍。

首先给表格的tbody一个idtable-body,便于操作表格。

然后编辑 activity-manage.js,先写查询列表代码:

js 复制代码
// 获取活动列表,渲染表格
function getActivityList() {
    postRequest('/activity/list', {}, function(res) {
        // 打印一下,看下后端返回的结果是什么
        console.log("getActivityList返回:",res);
        // 统一判断code,200代表业务成功
        if (res.code === 200) {
            // res.data 就是后端返回的活动数组
            let list = res.data;
        
            let tableBody = document.getElementById('table-body');
            // 先清空表格原有内容
            tableBody.innerHTML = '';
            // 循环遍历数据,拼接表格行
            for (let i = 0; i < list.length; i++) {
                let item = list[i];
                let tr = document.createElement('tr');
                tr.innerHTML = `
                    <td>${item.id}</td>
                    <td>${item.name}</td>
                    <td>${item.time}</td>
                    <td>${item.address}</td>
                    <td>${item.maxNum}</td>
                    <td>
                        <button class="btn btn-del" onclick="delActivity(${item.id})">删除</button>
                    </td>
                `;
                tableBody.appendChild(tr);
            }
        } else {
            // 请求业务失败,取出msg提示用户
            alert(res.msg);
        }
    })
}

// 页面加载自动执行,加载活动列表
getActivityList();

注意,循环遍历数据,拼接表格行使用了模板字符串,${item.id}这个语法,可以将item对象的id属性值插入到字符串中。

代码写完之后运行结果:

看到结果后毛哥狂喜,这是他人生第一次从后端接口拿到数据,并渲染到了界面上。

接着副队提醒毛哥看控制台数据:

哇哦,控制台将后端返回的对象打印的一清二楚,太棒了。

6. 功能2:新增活动,弹窗表单提交接口

副队:查询功能写完了,接下来实现新增。点击新增按钮弹出弹窗,填写信息提交给后端保存。新增成功之后,再次调用查询方法刷新表格,就能立刻看到刚新增的数据。

毛哥:提交之前加上非空校验,避免空数据传给后端。

首先,为了方便获取网页元素,给按钮和弹窗元素设置id值:

html 复制代码
<!-- 新增活动按钮 -->
<button class="btn btn-add" id="btnAdd">新增活动</button>
<!-- 弹窗 -->
<div class="mask" id="editMask">
    <!-- 弹窗内容盒子 -->
    <div class="dialog">
      <h3>新增活动</h3>
      <!-- 表单每一组 -->
      <div class="dialog-item">
        <label>活动名称</label>
        <input type="text" id="name">
      </div>
      <div class="dialog-item">
        <label>分类</label>
        <input type="text" id="type">
      </div>
      <div class="dialog-item">
        <label>活动时间</label>
        <input type="text" id="time">
      </div>
      <div class="dialog-item">
        <label>活动地点</label>
        <input type="text" id="address">
      </div>
      <div class="dialog-item">
        <label>报名截止时间</label>
        <input type="text" id="endTime">
      </div>
      <div class="dialog-item">
        <label>人数上限</label>
        <input type="text" id="maxNum">
      </div>
      <div class="dialog-item">
        <label>简介</label>
        <textarea id="about"></textarea>
      </div>
      <!-- 弹窗底部按钮区域 -->
      <div class="dialog-footer">
        <button class="cancel" id="btnCancel">取消</button>
        <button class="submit" id="btnSubmit">提交</button>
      </div>
    </div>
  </div>

继续补充到 activity-manage.js:

js 复制代码
// 获取弹窗和表单元素
const editMask= document.getElementById('editMask');

// 提交新增活动
function submitAddActivity() {
    // 获取表单输入值
    let name= document.getElementById('name').value;
    let type= document.getElementById('type').value;
    let time= document.getElementById('time').value;
    let address= document.getElementById('address').value;
	let endTime= document.getElementById('endTime').value;
	let maxNum= document.getElementById('maxNum').value;
	let about= document.getElementById('about').value;
	
    // 简单前端非空校验
    if (!name|| !type|| !time|| !address|| !endTime|| !maxNum|| !about) {
        alert('请填写全部活动信息!');
        return;
    }

    // 组装传给后端的参数
    let params = {
        name: name,
        type: type,
        time: time,
        address: address,
        endTime: endTime,
        maxNum: maxNum,
        about: about
    }
	// 向后端发送请求
    postRequest('/activity/add', params, function(res) {
        if (res.code === 200) {
            alert('新增活动成功!');
            // 关闭弹窗
            editMask.style.display = 'none';
            // 新增成功,重新查询列表刷新页面数据
            getActivityList();
        } else {
            alert(res.msg);
        }
    })
}

// 新增按钮,打开弹窗(flex用于居中)
document.getElementById('btnAdd').onclick = function () {
    editMask.style.display = 'flex';
}
// 取消弹窗按钮
document.getElementById('btnCancel').onclick = function () {
    editMask.style.display = 'none';
}
// 提交按钮绑定新增方法
document.getElementById('btnSubmit').onclick = submitAddActivity;

写完代码后,单击界面新增按钮:

然后打击提交后,因为在此调用了查询接口,所以界面自动刷新了数据:

哇塞,这感觉太棒了,毛哥爽了。

7. 功能3:删除活动接口

副队:查询、新增都完成,最后做删除。点击删除按钮,先弹出确认框,防止误操作,确认之后发送删除请求,删除完成后刷新列表。

毛哥:这个流程很贴合真实使用:先看数据 → 有需要就新增 → 不需要的就删掉。

继续补充到 activity-manage.js:

js 复制代码
// 删除活动
function delActivity(id) {
    if (!confirm('确定要删除这个活动吗?')) {
        return;
    }
    let params = {
        id: id
    }
    postRequest('/activity/delete', params, function(res) {
        if (res.code === 200) {
            alert('删除成功');
            getActivityList();
        } else {
            alert(res.msg);
        }
    })
}

当看到删除成功后,界面自动刷新(因为调用了getActivityList()),毛哥又乐了。

8. 小结

副队:咱们现在的顺序是:查询 → 新增 → 删除 ,符合用户真实访问逻辑:打开页面先加载并查看所有活动,再按需添加新活动,过时的活动执行删除。公共请求函数抽离到 common.js,后续其他页面可以直接复用。

毛哥:这个顺序很贴合日常使用!新增、删除之后都会重新查询列表,页面数据和后端数据库保持同步。

副队:没错。当前管理员页面还差修改活动功能,下一节我们就实现编辑弹窗,原有数据带入弹窗回显,然后对接后端修改接口。

相关推荐
炫酷的大高个1 小时前
6 亿播放的闪身步,我用前端算出 15 倍重心差
前端·javascript
林小帅1 小时前
【笔记】Prisma v7 + SQLite TS2305 及连锁 TS7006 问题
前端·javascript·后端
Dumbness1 小时前
订阅消息弹窗必须在用户点击里调:联调实验室里 timing 和 Mock 支付通知踩的坑
javascript·uni-app
黄连木笛2 小时前
不改一行 Postman 源码,把它汉化了:Electron preload 注入 + fetch 拦截实战
javascript·electron
叼烟扛炮3 小时前
[LangChain] 学习笔记(03):打破失忆症——对话机制与上下文管理
java·前端·javascript·笔记·学习·langchain
卋罖7504 小时前
Razor 视图 + JavaScript + Vue/iView 组件实现的表格自定义列
javascript·vue·iview·razor视图
ZhengEnCi16 小时前
12 种高级 UI 交互效果及对应 AI 提示词:从按压回弹到底部抽屉的物理感教学
前端·css·交互设计
系统集成-鹰翔19 小时前
ERP图片改名后仍命中识别缓存:缓存键、十分钟到期与调用计数
javascript·经验分享·后端
FlyWIHTSKY20 小时前
vue2和vue3有什么区别,用法上有哪些区别
前端·javascript·vue.js