基于 jQuery 实现商品列表增删改查与数据统计

在电商类前端开发中,商品列表的增删改查(CRUD)是高频核心场景。本文将从原理拆解 + 代码实现 + 细节优化三个维度,手把手教你用 jQuery 完成商品列表的渲染、数据操作(新增 / 编辑 / 删除 / 数量调整)及实时统计,不仅能掌握核心语法,更能理解「数据驱动视图」的前端底层逻辑,适合新手入门与实战复盘。

一、核心需求与实现目标

本次实战需达成以下可落地的功能目标,覆盖电商列表核心场景:

  1. 基于 AJAX 异步加载本地 JSON 格式的初始商品数据,处理加载失败的异常场景;
  2. 渲染商品列表到表格,支持数量加减(数量≥1,禁止减至 0);
  3. 实现商品新增 / 编辑 / 删除,新增 / 编辑时做严格的输入合法性校验;
  4. 数据变化(新增 / 编辑 / 数量调整 / 删除)时,实时计算并更新总价格、总数量;
  5. 优化用户体验(如删除确认、金额格式化、输入提示等)。

二、技术栈与项目结构

1. 核心技术栈

  • HTML5:语义化标签搭建页面结构,input 原生属性(min/step)做基础输入限制;
  • CSS3:极简样式保证布局规整,兼顾基础交互体验;
  • jQuery 3.7.1:简化 DOM 操作、AJAX 请求、事件绑定,降低新手学习成本;
  • JSON:存储结构化的初始商品数据,模拟后端数据返回格式。

2. 标准化项目结构

plaintext

复制代码
├── index.html                // 核心页面(结构+逻辑)
├── js/
│   ├── jquery-3.7.1.min.js   // jQuery核心库(需提前下载)
│   └── index.json            // 商品初始数据文件

三、核心问题拆解与实现方案

问题 1:如何使用 AJAX 请求加载 JSON 格式的商品初始数据?

原理说明

AJAX(异步 JavaScript 和 XML)是前端异步请求数据的核心技术,jQuery 封装了$.ajax()方法,可简化请求流程。加载本地 JSON 文件的核心逻辑是:通过 GET 请求读取 JSON 文件,成功后将数据存入数组,触发列表渲染;失败时给出明确的错误提示。

完整实现代码

javascript

运行

复制代码
// 全局变量:存储商品数据的核心数组 + 标记编辑状态(-1=新增,其他=编辑索引)
let goodsList = []; 
let editIndex = -1;

$(function () {
    // 1. AJAX加载初始商品数据
    $.ajax({
        url: "./js/index.json",       // JSON文件路径(需与实际目录匹配)
        type: "GET",                  // GET请求(读取静态文件)
        dataType: "json",             // 预期返回数据类型为JSON
        timeout: 5000,                // 超时时间:5秒
        success: function (res) {
            console.log("初始商品数据加载成功:", res);
            goodsList = res;          // 将返回数据存入核心数组
            renderGoodsList();        // 触发列表渲染
        },
        error: function (xhr, status, error) {
            // 分类处理错误,提升调试效率
            if (status === "timeout") {
                alert("数据请求超时,请检查网络!");
            } else if (xhr.status === 404) {
                alert("JSON文件不存在,请检查文件路径:./js/index.json");
            } else {
                console.error("数据加载失败详情:", error);
                alert("初始数据加载失败,详情请查看控制台!");
            }
        }
    });
});

// 配套JSON数据示例(index.json)
[
    {"id": 1, "name": "小米14 Pro", "price": 4999.99, "num": 2},
    {"id": 2, "name": "华为FreeBuds Pro 3", "price": 1299.00, "num": 5},
    {"id": 3, "name": "苹果MagSafe充电器", "price": 149.00, "num": 10}
]
关键细节说明
  1. 路径问题url需使用相对路径(如./js/index.json),避免绝对路径导致的跨域 / 找不到文件问题;
  2. 超时处理 :添加timeout参数,避免请求无响应时用户等待;
  3. 错误分类 :根据statusxhr.status区分超时、404 等错误,提升用户体验与调试效率;
  4. 数据承接 :请求成功后将数据赋值给全局数组goodsList,作为所有操作的「数据源」。

问题 2:如何实现商品的新增 / 编辑 / 删除功能?

核心思路

以全局数组goodsList为核心数据源,新增 / 编辑 / 删除本质是对数组的「增 / 改 / 删」操作,操作完成后重新渲染列表,实现「数据驱动视图」。

完整实现代码
步骤 1:HTML 结构(新增 / 编辑弹窗)

html

预览

复制代码
<!-- 新增按钮 -->
<button class="add-btn">新增商品</button>
<!-- 新增/编辑弹窗(默认隐藏) -->
<div class="form-modal" style="display: none; margin: 20px 0;">
    <input type="text" id="goods-name" placeholder="请输入商品名称" required>
    <input type="number" id="goods-price" placeholder="请输入商品单价" min="0.01" step="0.01">
    <input type="number" id="goods-num" placeholder="请输入商品数量" min="1" step="1">
    <button class="confirm-btn">确定</button>
    <button class="cancel-btn">取消</button>
</div>
步骤 2:JS 逻辑(新增 / 编辑 / 删除)

javascript

运行

复制代码
$(function () {
    // 2. 绑定新增按钮事件:打开弹窗+重置状态
    $(".add-btn").on("click", function () {
        $(".form-modal").show();
        editIndex = -1; // 标记为「新增状态」
        // 清空输入框,避免残留编辑数据
        $("#goods-name, #goods-price, #goods-num").val("");
    });

    // 3. 绑定取消按钮事件:关闭弹窗+清空输入
    $(".cancel-btn").on("click", function () {
        $(".form-modal").hide();
        $("#goods-name, #goods-price, #goods-num").val("");
        editIndex = -1;
    });

    // 4. 绑定确定按钮事件:新增/编辑核心逻辑
    $(".confirm-btn").on("click", function () {
        // 1. 获取并格式化输入值(trim去除首尾空格)
        const name = $("#goods-name").val().trim();
        const price = parseFloat($("#goods-price").val());
        const num = parseInt($("#goods-num").val());

        // 2. 严格输入校验(前端兜底,避免无效数据)
        if (!name) {
            alert("商品名称不能为空!");
            return; // 终止执行
        }
        if (isNaN(price) || price <= 0) {
            alert("商品单价必须是大于0的有效数字(如:99.99)!");
            return;
        }
        if (isNaN(num) || num <= 0) {
            alert("商品数量必须是大于0的整数(如:1/5/10)!");
            return;
        }

        // 3. 判断:新增 OR 编辑
        if (editIndex === -1) {
            // 新增逻辑:生成唯一ID(基于现有最大ID+1)
            const maxId = goodsList.length > 0 
                ? Math.max(...goodsList.map(item => item.id)) 
                : 0;
            const newGoods = {
                id: maxId + 1,
                name: name,
                price: price,
                num: num
            };
            goodsList.push(newGoods); // 新增商品到数组
        } else {
            // 编辑逻辑:更新对应索引的商品数据
            goodsList[editIndex].name = name;
            goodsList[editIndex].price = price;
            goodsList[editIndex].num = num;
        }

        // 4. 操作完成:关闭弹窗+清空输入+重新渲染
        $(".form-modal").hide();
        $("#goods-name, #goods-price, #goods-num").val("");
        editIndex = -1;
        renderGoodsList(); // 重新渲染列表
    });
});

// 5. 删除商品函数(绑定到表格删除按钮)
function deleteGoods(index) {
    // 确认删除,防止误操作
    if (!confirm(`确定要删除【${goodsList[index].name}】吗?`)) {
        return;
    }
    goodsList.splice(index, 1); // 从数组删除对应索引的商品
    renderGoodsList(); // 重新渲染
}

// 6. 编辑商品函数(绑定到表格编辑按钮)
function editGoods(index) {
    editIndex = index; // 标记为「编辑状态」
    const currentGoods = goodsList[index];
    // 回填数据到输入框
    $("#goods-name").val(currentGoods.name);
    $("#goods-price").val(currentGoods.price);
    $("#goods-num").val(currentGoods.num);
    $(".form-modal").show(); // 打开弹窗
}

// 7. 列表渲染函数(核心:将数组数据转为DOM)
function renderGoodsList() {
    calculateTotal(); // 先计算总数/总价
    const $tbody = $("tbody");
    $tbody.empty(); // 清空原有列表,避免重复渲染

    // 遍历数组,生成表格行
    $.each(goodsList, function (index, item) {
        const tr = `
            <tr>
                <td>${item.name}</td>
                <td>¥${item.price.toFixed(2)}</td>
                <td>
                    <button onclick="reduceNum(${index})">-</button>
                    <span>${item.num}</span>
                    <button onclick="addNum(${index})">+</button>
                </td>
                <td>
                    <button onclick="editGoods(${index})">编辑</button>
                    <button onclick="deleteGoods(${index})">删除</button>
                </td>
            </tr>
        `;
        $tbody.append(tr);
    });
}

// 8. 数量加减函数(配套列表)
function addNum(index) {
    goodsList[index].num += 1;
    renderGoodsList();
}
function reduceNum(index) {
    if (goodsList[index].num <= 1) {
        alert("商品数量不能小于1!");
        return;
    }
    goodsList[index].num -= 1;
    renderGoodsList();
}
关键细节说明
  1. 状态标记 :通过editIndex区分「新增」(-1)和「编辑」(对应商品索引),避免新增 / 编辑逻辑混淆;
  2. ID 生成:新增商品时基于现有最大 ID+1,保证 ID 唯一性;
  3. 输入校验 :不仅判断「非空」,还通过isNaN判断是否为有效数字,避免用户输入非数字内容;
  4. 用户体验 :删除时添加confirm确认,数量减至 1 时给出提示,避免误操作;
  5. 数据回填:编辑时将商品数据回填到输入框,提升编辑效率。

问题 3:如何实时计算并展示商品的总数量和总价格?

核心思路

封装「统计函数」,遍历商品数组累加「数量 × 单价」得到总价格,累加「数量」得到总数量;所有数据操作后(新增 / 编辑 / 删除 / 数量调整)调用该函数,实现实时更新。

完整实现代码

javascript

运行

复制代码
// 统计总价格、总数量函数
function calculateTotal() {
    let totalPrice = 0; // 总价格
    let totalNum = 0;   // 总数量

    // 遍历数组累加数据
    $.each(goodsList, function (index, item) {
        totalPrice += item.price * item.num; // 单价×数量=单商品总价,累加
        totalNum += item.num;                // 数量累加
    });

    // 更新到页面(保留2位小数,符合金额展示规范)
    $(".total-price").text(`¥${totalPrice.toFixed(2)}`);
    $(".total-num").text(totalNum);
}

// HTML统计区域(表格tfoot)
<tfoot>
    <tr>
        <th colspan="2">总价格:</th>
        <th colspan="2" class="total-price">¥0.00</th>
    </tr>
    <tr>
        <th colspan="2">总数量:</th>
        <th colspan="2" class="total-num">0</th>
    </tr>
</tfoot>
关键细节说明
  1. 触发时机calculateTotal()需在renderGoodsList()开头调用,确保每次渲染列表前先更新统计数据;
  2. 金额格式化 :使用toFixed(2)保留 2 位小数,避免出现1999.999999等不规范的金额格式;
  3. 初始值 :统计变量初始化为 0,避免数组为空时出现NaN
  4. 遍历方式 :使用$.each()遍历数组,兼容低版本浏览器,新手更易理解。

四、完整源码整合(可直接运行)

html

预览

复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>jQuery商品列表CRUD</title>
    <script src="./js/jquery-3.7.1.min.js"></script>
    <style>
        /* 基础样式优化 */
        .goods-table {
            width: 800px;
            margin: 20px auto;
            border-collapse: collapse;
            text-align: center;
        }
        .goods-table th, .goods-table td {
            border: 1px solid #333;
            padding: 10px;
        }
        .form-modal {
            width: 800px;
            margin: 20px auto;
        }
        .form-modal input {
            margin-right: 10px;
            padding: 5px;
        }
        .btn-group {
            width: 800px;
            margin: 0 auto;
        }
        button {
            padding: 5px 10px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div class="btn-group">
        <button class="add-btn">新增商品</button>
    </div>

    <!-- 新增/编辑弹窗 -->
    <div class="form-modal" style="display: none;">
        <input type="text" id="goods-name" placeholder="请输入商品名称">
        <input type="number" id="goods-price" placeholder="请输入商品单价" min="0.01" step="0.01">
        <input type="number" id="goods-num" placeholder="请输入商品数量" min="1" step="1">
        <button class="confirm-btn">确定</button>
        <button class="cancel-btn">取消</button>
    </div>

    <!-- 商品表格 -->
    <table class="goods-table">
        <thead>
            <tr>
                <th>商品名称</th>
                <th>单价</th>
                <th>数量</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody></tbody>
        <tfoot>
            <tr>
                <th colspan="2">总价格:</th>
                <th colspan="2" class="total-price">¥0.00</th>
            </tr>
            <tr>
                <th colspan="2">总数量:</th>
                <th colspan="2" class="total-num">0</th>
            </tr>
        </tfoot>
    </table>

    <script>
        // 全局变量
        let goodsList = [];
        let editIndex = -1;

        // 页面加载完成后执行
        $(function () {
            // 1. 加载初始数据
            loadInitialData();

            // 2. 绑定事件
            bindEvents();
        });

        // 1. 加载初始JSON数据
        function loadInitialData() {
            $.ajax({
                url: "./js/index.json",
                type: "GET",
                dataType: "json",
                timeout: 5000,
                success: function (res) {
                    goodsList = res;
                    renderGoodsList();
                },
                error: function (xhr, status, error) {
                    if (status === "timeout") {
                        alert("数据请求超时,请检查网络!");
                    } else if (xhr.status === 404) {
                        alert("未找到JSON文件,请检查路径:./js/index.json");
                    } else {
                        console.error("数据加载失败:", error);
                        alert("初始数据加载失败!");
                    }
                }
            });
        }

        // 2. 绑定所有事件
        function bindEvents() {
            // 新增按钮
            $(".add-btn").on("click", function () {
                $(".form-modal").show();
                editIndex = -1;
                $("#goods-name, #goods-price, #goods-num").val("");
            });

            // 取消按钮
            $(".cancel-btn").on("click", function () {
                $(".form-modal").hide();
                $("#goods-name, #goods-price, #goods-num").val("");
                editIndex = -1;
            });

            // 确定按钮(新增/编辑)
            $(".confirm-btn").on("click", function () {
                const name = $("#goods-name").val().trim();
                const price = parseFloat($("#goods-price").val());
                const num = parseInt($("#goods-num").val());

                // 输入校验
                if (!name) {
                    alert("商品名称不能为空!");
                    return;
                }
                if (isNaN(price) || price <= 0) {
                    alert("商品单价必须是大于0的有效数字!");
                    return;
                }
                if (isNaN(num) || num <= 0) {
                    alert("商品数量必须是大于0的整数!");
                    return;
                }

                // 新增/编辑逻辑
                if (editIndex === -1) {
                    // 新增
                    const maxId = goodsList.length > 0 ? Math.max(...goodsList.map(item => item.id)) : 0;
                    goodsList.push({
                        id: maxId + 1,
                        name: name,
                        price: price,
                        num: num
                    });
                } else {
                    // 编辑
                    goodsList[editIndex].name = name;
                    goodsList[editIndex].price = price;
                    goodsList[editIndex].num = num;
                }

                // 重置状态
                $(".form-modal").hide();
                $("#goods-name, #goods-price, #goods-num").val("");
                editIndex = -1;

                // 重新渲染
                renderGoodsList();
            });
        }

        // 3. 渲染商品列表
        function renderGoodsList() {
            calculateTotal(); // 先统计
            const $tbody = $("tbody");
            $tbody.empty();

            $.each(goodsList, function (index, item) {
                const tr = `
                    <tr>
                        <td>${item.name}</td>
                        <td>¥${item.price.toFixed(2)}</td>
                        <td>
                            <button onclick="reduceNum(${index})">-</button>
                            <span>${item.num}</span>
                            <button onclick="addNum(${index})">+</button>
                        </td>
                        <td>
                            <button onclick="editGoods(${index})">编辑</button>
                            <button onclick="deleteGoods(${index})">删除</button>
                        </td>
                    </tr>
                `;
                $tbody.append(tr);
            });
        }

        // 4. 统计总价格/总数量
        function calculateTotal() {
            let totalPrice = 0;
            let totalNum = 0;

            $.each(goodsList, function (index, item) {
                totalPrice += item.price * item.num;
                totalNum += item.num;
            });

            $(".total-price").text(`¥${totalPrice.toFixed(2)}`);
            $(".total-num").text(totalNum);
        }

        // 5. 数量加减
        function addNum(index) {
            goodsList[index].num += 1;
            renderGoodsList();
        }
        function reduceNum(index) {
            if (goodsList[index].num <= 1) {
                alert("商品数量不能小于1!");
                return;
            }
            goodsList[index].num -= 1;
            renderGoodsList();
        }

        // 6. 编辑商品
        function editGoods(index) {
            editIndex = index;
            const item = goodsList[index];
            $("#goods-name").val(item.name);
            $("#goods-price").val(item.price);
            $("#goods-num").val(item.num);
            $(".form-modal").show();
        }

        // 7. 删除商品
        function deleteGoods(index) {
            if (!confirm(`确定删除【${goodsList[index].name}】吗?`)) {
                return;
            }
            goodsList.splice(index, 1);
            renderGoodsList();
        }
    </script>
</body>
</html>

五、进阶优化建议(提升实战价值)

  1. 本地存储 :添加localStorage,将goodsList数据持久化,刷新页面后数据不丢失;

    javascript

    运行

    复制代码
    // 示例:保存数据到本地
    function saveToLocal() {
        localStorage.setItem("goodsList", JSON.stringify(goodsList));
    }
    // 示例:从本地加载数据
    function loadFromLocal() {
        const data = localStorage.getItem("goodsList");
        if (data) goodsList = JSON.parse(data);
    }
  2. 表单美化:使用 CSS / 第三方 UI 库优化弹窗样式,添加输入校验的视觉提示(如红色边框);

  3. 防抖处理:数量加减按钮添加防抖,避免快速点击导致数据异常;

  4. 对接后端:将 AJAX 请求改为对接真实后端接口(如 POST/DELETE/PUT),实现前后端交互;

  5. 分页功能:当商品数量较多时,实现分页渲染,提升页面性能。

六、核心知识点总结

1. AJAX 加载 JSON 核心

  • $.ajax()的核心参数:url/type/dataType/success/error
  • 错误分类处理:超时、404 等场景需针对性提示;
  • 路径规范:本地 JSON 使用相对路径,避免跨域问题。

2. CRUD 功能核心

  • 数据驱动视图:所有操作围绕数组展开,操作后重新渲染;
  • 状态标记:通过editIndex区分新增 / 编辑,避免逻辑混淆;
  • 输入校验:前端兜底校验,保证数据合法性。

3. 实时统计核心

  • 统计函数封装:遍历数组累加数据,统一触发时机;
  • 金额格式化:toFixed(2)保证金额展示规范;
  • 联动更新:统计函数需在所有数据操作后调用。

总结

本文从「问题拆解」角度,完整实现了 jQuery 商品列表的 CRUD 与实时统计,核心是抓住「数组作为核心数据源,操作数组后重新渲染视图」的逻辑。代码兼顾「可运行性」与「可维护性」,新增了错误处理、用户体验优化等实战细节,不仅能帮助新手掌握 jQuery 核心语法,更能理解前端「数据驱动视图」的底层思想。在此基础上,你可结合进阶优化建议,进一步提升项目的实战价值。

相关推荐
web小白成长日记9 小时前
CSS 作用域隔离实战:React、Vue 与 Styled Components 的三种范式
前端·css·vue.js·react.js
Mr -老鬼9 小时前
Electron 与 Tauri 全方位对比指南(2026版)
前端·javascript·rust·electron·nodejs·tauri
幻云20109 小时前
Next.js 之道:从全栈思维到架构实战
开发语言·javascript·架构
king王一帅13 小时前
Incremark Solid 版本上线:Vue/React/Svelte/Solid 四大框架,统一体验
前端·javascript·人工智能
智航GIS18 小时前
10.4 Selenium:Web 自动化测试框架
前端·python·selenium·测试工具
前端工作日常18 小时前
我学习到的A2UI概念
前端
徐同保18 小时前
为什么修改 .gitignore 后还能提交
前端
一只小bit18 小时前
Qt 常用控件详解:按钮类 / 显示类 / 输入类属性、信号与实战示例
前端·c++·qt·gui
Mr -老鬼19 小时前
前端静态路由与动态路由:全维度总结与实践指南
前端