【前端1】单据编辑 -EasyUI/Vue/React/Bootstrap 主流框架实现订单单据主子表显示和编辑比较,哪个你最易入门?

【前端1】单据编辑 -EasyUI/Vue/React/Bootstrap 主流框架实现订单单据主子表显示和编辑比较,哪个你最易入门?

作者:长江支流 日期:2026-08-25

你开发一个订单管理系统,老板说:

"订单列表里,每一行都要显示该订单包含的所有商品明细,而且要能直接编辑、新增行、删除行。"

你心想:"就一个主子表单据而已,不难吧?"

结果你打开项目,发现这个"看起来很简单"的需求,比你想的要复杂得多:

  • EasyUI DataGrid:要配置 editor、formatter,还要手动管编辑状态
  • Vue + Element:要写 template、v-for、响应式数据、方法函数
  • React + Ant Design:要写 JSX、useState、handleChange、map 渲染
  • Bootstrap Table:要写 formatter、手动拼 HTML 字符串

纯HTML实现方式,5行JS代码解决

加载表格数据,其实5行核心代码,实例化插件、加载数据到网格/Table/DIV/任意元素,然后就可增加删除(模板)行。

javascript 复制代码
//Step1 实例化插件
 var detailGrid = new UserBaoDataGrid("detailGrid");

function doFilter() {
		//Step2 加载数据到Table/DIV等任意元素
    detailGrid.datagrid({
        url: "/demo/data/13order_details_relation.json"
    });
}

// ★★★ 新增明细行(模板方式) ★★★
function addDetailRow() {
	  //Step3 增加行,可以指定参数按自己的定制模板增加
    detailGrid.appendByTemplateRow();	
}

// ★★★ 删除明细行 ★★★
function deleteDetailRow(el) {
    if (confirm('确定要删除该明细记录吗?')) {
    		//Step3 删除行
        detailGrid.removeRow(el);
    }
}

完整单据代码如下,会Html人都会。

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>订单详情(可编辑 · 简单版)-- 长江支流·咏方舟</title>

    <script type="text/javascript" src="/lib/jquery/dist/jquery.min.js"></script>
    <script src="/js/UserBaoDataGrid.js"></script>
    <link href="/css/GoldReportTemplatePack.css" rel="stylesheet" />

    <script type="text/javascript">
        var masterGrid = new UserBaoDataGrid("masterGrid");
        var detailGrid = new UserBaoDataGrid("detailGrid");

        function getUrlParameter(name) {
            var urlParams = new URLSearchParams(window.location.search);
            return urlParams.get(name);
        }

        var mOrderId = getUrlParameter('orderId');
        if (!mOrderId) mOrderId = '1';

        $(document).ready(function () {
            doFilter();
        });

        function doFilter() {
            masterGrid.datagrid({
                url: "/demo/data/13orders_relation_with_customers.json",
                queryParams: { pageSize: 100 },
                success: function (data) {
                    //alert("主表加载");
                    //这个是离线json演示才加if,在线根本不用,因为在线的肯定直接返回当前订单的明细
                    if (data && data.rows) {
                        data.rows = data.rows.filter(function (row) { return row.id == mOrderId; });
                        data.total = data.rows.length;
                    }
                }
            });

            detailGrid.datagrid({
                url: "/demo/data/13order_details_relation.json",
                queryParams: { pageSize: 100 },
                success: function (data) {
                    //这个是离线json演示才加if,在线根本不用,因为在线的肯定直接返回当前订单的明细
                    if (data && data.rows) {
                        data.rows = data.rows.filter(function (row) { return row.OrderID == mOrderId; });
                        data.total = data.rows.length;
                    }
                }
            });
        }

        function reloadAll() { doFilter(); }

        // ★★★ 新增明细行(模板方式) ★★★
        function addDetailRow() {
            detailGrid.appendByTemplateRow();
        }

        // ★★★ 删除明细行 ★★★
        function deleteDetailRow(el) {
            if (confirm('确定要删除该明细记录吗?')) {
                detailGrid.removeRow(el);
            }
        }
    </script>

</head>
<body>
    <div class="webmis-bill-container">
        <div class="webmis-bill-title">📋 订单详情(可编辑)</div>
        <div class="webmis-bill-subtitle">
            主表 + 明细,全部可编辑 &nbsp;·&nbsp; 当前订单 ID:<span class="webmis-bill-badge" id="orderIdDisplay">1</span>
        </div>

        <div class="webmis-bill-btn-group">
            <button class="webmis-bill-btn webmis-bill-btn-primary" onclick="doFilter();">🔍 查询</button>
            <button class="webmis-bill-btn webmis-bill-btn-success" onclick="reloadAll();">🔄 重新加载</button>
        </div>

        <!-- ============================================================ -->
        <!-- 主表(可编辑) -->
        <!-- ============================================================ -->
        <div class="webmis-bill-master">
            <div id="masterGrid">
                <div class="webmis-templatepack" data-src="rows">
                    <div class="webmis-templatepackrow" templaterow="true" style="cursor:default;display:block;padding:0;border-bottom:1px solid #e8ecf1;margin-bottom:8px;">
                        <div class="webmis-card">
                            <!-- ★★★ 主表字段全部变成 input ★★★ -->
                            <div class="webmis-bill-row">
                                <span class="webmis-bill-label">订单编号</span>
                                <span class="webmis-bill-value">
                                    <input type="text" value="<templatefield>order_id</templatefield>" style="width:100%;padding:4px 8px;border:1px solid #d0d8e4;border-radius:4px;" />
                                </span>
                            </div>
                            <div class="webmis-bill-row">
                                <span class="webmis-bill-label">客户名称</span>
                                <span class="webmis-bill-value">
                                    <input type="text" value="<templatefield>customerName</templatefield>" style="width:100%;padding:4px 8px;border:1px solid #d0d8e4;border-radius:4px;" />
                                </span>
                            </div>
                            <div class="webmis-bill-row">
                                <span class="webmis-bill-label">总金额</span>
                                <span class="webmis-bill-value">
                                    <input type="number" step="0.01" value="<templatefield>total_amount</templatefield>" style="width:100%;padding:4px 8px;border:1px solid #d0d8e4;border-radius:4px;" />
                                </span>
                            </div>
                            <div class="webmis-bill-row">
                                <span class="webmis-bill-label">下单日期</span>
                                <span class="webmis-bill-value">
                                    <input type="datetime-local" value="<templatefield>order_date</templatefield>" style="width:100%;padding:4px 8px;border:1px solid #d0d8e4;border-radius:4px;" />
                                </span>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <div class="webmis-bill-separator"></div>

        <!-- ============================================================ -->
        <!-- 明细表(可编辑) -->
        <!-- ============================================================ -->
        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">
            <div style="font-weight:600;font-size:14px;color:#1a2a3a;">📦 商品明细</div>
            <button onclick="addDetailRow();" style="padding:4px 16px;background:#1f9a6e;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:13px;">➕ 新增明细</button>
        </div>

        <table id="detailGrid" class="webmis-bill-detail">
            <thead>
                <tr>
                    <th>订单ID</th>
                    <th>商品编号</th>
                    <th>数量</th>
                    <th>单价</th>
                    <th>金额</th>
                    <th>备注</th>
                    <th width="15%">操作</th>
                </tr>
            </thead>
            <tbody>
                <tr templaterow="true" data-src="rows">
                    <!-- ★★★ 明细表字段全部变成 input ★★★ -->
                    <td><input type="text" value="<templatefield>OrderID</templatefield>" style="width:100%;padding:4px 6px;border:1px solid #d0d8e4;border-radius:3px;" /></td>
                    <td><input type="text" value="<templatefield>ProductID</templatefield>" style="width:100%;padding:4px 6px;border:1px solid #d0d8e4;border-radius:3px;" /></td>
                    <td><input type="number" value="<templatefield>Qty</templatefield>" style="width:100%;padding:4px 6px;border:1px solid #d0d8e4;border-radius:3px;" /></td>
                    <td><input type="number" step="0.01" value="<templatefield>Price</templatefield>" style="width:100%;padding:4px 6px;border:1px solid #d0d8e4;border-radius:3px;" /></td>
                    <td><input type="number" step="0.01" value="<templatefield>Amount</templatefield>" style="width:100%;padding:4px 6px;border:1px solid #d0d8e4;border-radius:3px;" /></td>
                    <td><input type="text" value="<templatefield>Memo</templatefield>" style="width:100%;padding:4px 6px;border:1px solid #d0d8e4;border-radius:3px;" /></td>
                    <td>
                        <button onclick="deleteDetailRow(this)" style="padding:2px 12px;background:#dc3545;color:#fff;border:none;border-radius:3px;cursor:pointer;font-size:12px;">移除</button>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>

    <script>
        $(document).ready(function () {
            $('#orderIdDisplay').text(mOrderId);
        });
    </script>

</body>
</html>

一个"主子表单据",四种主流方案要写多少行?

EasyUI DataGrid(约 60 行)

javascript 复制代码
$('#masterGrid').datagrid({
    url: '/api/order',
    columns: [[
        { field: 'orderNo', title: '订单编号' },
        { field: 'customer', title: '客户名称', editor: 'text' },
        { field: 'total', title: '总金额', editor: 'numberbox' }
    ]],
    onDblClickRow: function(index) {
        $('#masterGrid').datagrid('beginEdit', index);
    }
});

// 明细表
$('#detailGrid').datagrid({
    url: '/api/order_details',
    columns: [[
        { field: 'product', title: '商品', editor: 'text' },
        { field: 'qty', title: '数量', editor: 'numberbox' },
        { field: 'price', title: '单价', editor: 'numberbox' }
    ]],
    onDblClickRow: function(index) {
        $('#detailGrid').datagrid('beginEdit', index);
    }
});

// 新增行、删除行还要单独写
function addRow() {
    $('#detailGrid').datagrid('appendRow', {});
}
function deleteRow() {
    var row = $('#detailGrid').datagrid('getSelected');
    var index = $('#detailGrid').datagrid('getRowIndex', row);
    $('#detailGrid').datagrid('deleteRow', index);
}

Vue + Element(约 50 行)

vue 复制代码
<template>
    <el-table :data="masterData">
        <el-table-column prop="orderNo" label="订单编号" />
        <el-table-column prop="customer" label="客户名称">
            <template #default="{ row }">
                <el-input v-model="row.customer" />
            </template>
        </el-table-column>
    </el-table>
    <el-table :data="detailData">
        <el-table-column prop="product" label="商品">
            <template #default="{ row }">
                <el-input v-model="row.product" />
            </template>
        </el-table-column>
    </el-table>
</template>

<script>
data() {
    return {
        masterData: [],
        detailData: []
    };
},
methods: {
    loadData() {
        axios.get('/api/order').then(res => { this.masterData = res.data; });
        axios.get('/api/order_details').then(res => { this.detailData = res.data; });
    },
    addRow() { this.detailData.push({}); },
    deleteRow(index) { this.detailData.splice(index, 1); }
}
</script>

React + Ant Design(约 70 行)

jsx 复制代码
const columns = [
    { title: '订单编号', dataIndex: 'orderNo' },
    { title: '客户名称', dataIndex: 'customer',
      render: (text, record) => <Input value={text} onChange={...} /> }
];

const detailColumns = [
    { title: '商品', dataIndex: 'product',
      render: (text, record) => <Input value={text} onChange={...} /> }
];

function OrderPage() {
    const [master, setMaster] = useState([]);
    const [details, setDetails] = useState([]);
    const [loading, setLoading] = useState(false);

    useEffect(() => {
        setLoading(true);
        axios.get('/api/order').then(res => setMaster(res.data));
        axios.get('/api/order_details').then(res => setDetails(res.data));
    }, []);

    const addRow = () => setDetails([...details, {}]);
    const deleteRow = (index) => setDetails(details.filter((_, i) => i !== index));

    return (
        <div>
            <Table dataSource={master} columns={columns} loading={loading} />
            <Table dataSource={details} columns={detailColumns} />
            <Button onClick={addRow}>新增明细</Button>
        </div>
    );
}

Bootstrap Table(约 80 行)

javascript 复制代码
$('#masterGrid').bootstrapTable({
    url: '/api/order',
    columns: [
        { field: 'orderNo', title: '订单编号' },
        { field: 'customer', title: '客户名称' },
        { field: 'total', title: '总金额' }
    ]
});

$('#detailGrid').bootstrapTable({
    url: '/api/order_details',
    columns: [
        { field: 'product', title: '商品' },
        { field: 'qty', title: '数量' },
        { field: 'price', title: '单价' }
    ],
    // 编辑需要额外配置
});

数据对比

方案 主表+明细 + 增删改 需要学什么 代码量
EasyUI DataGrid 配置 columns + editor + 手动管状态 DataGrid API、editor 配置 ~60 行
Vue + Element 写 template + data + methods v-for、响应式、axios ~50 行
React + Ant Design 写 JSX + useState + useEffect Hooks、JSX、axios ~70 行
Bootstrap Table 配置 + 手动拼 HTML formatter、DOM 操作 ~80 行
UserBaoDataGrid引擎 3 行模板 + 1 行 JS 会写 HTML 即可 ~4 行

UserBaoDataGrid引擎:3 行模板 + 1 行 JS

主表模板(1 行)

html 复制代码
<div class="webmis-templatepackrow" templaterow="true">
    <input value="<templatefield>customer</templatefield>">
    <input value="<templatefield>total</templatefield>">
</div>

明细表模板(1 行)

html 复制代码
<tr templaterow="true">
    <td><input value="<templatefield>product</templatefield>"></td>
    <td><input value="<templatefield>qty</templatefield>"></td>
    <td><button onclick="detailGrid.removeRow(this)">删除</button></td>
</tr>

JS 调用(1 行)

javascript 复制代码
var masterGrid = new UserBaoDataGrid("masterGrid");
var detailGrid = new UserBaoDataGrid("detailGrid");

function doFilter() {
    masterGrid.datagrid({ url: '/api/order' });
    detailGrid.datagrid({ url: '/api/order_details' });
}

function addDetailRow() {
    detailGrid.appendByTemplateRow();  // 新增一行
}

为什么差这么多?

主流框架 UserBaoDataGrid引擎
核心问题 如何管理状态、如何渲染列表 数据填到哪里
你需要做什么 学 API、配 columns、管 state 会写 HTML 就行
新增一行 操作数组 / 调用 API appendByTemplateRow()
删除一行 找索引 / splice removeRow(this)
谁适配谁 你适配框架 引擎适配你的数据

一个真实场景,覆盖 18 个行业

这种"主表 + 明细"的编辑场景,不是你一个人的痛点:

行业 主表 明细
电商/零售 订单列表 商品明细(名称、数量、单价)
教育培训 学生信息 选修课程列表
考试系统 考生信息 各科成绩
企业OA 审批流程 审批节点列表
项目管理 项目列表 任务列表
生产制造 工单列表 加工工序列表
人力资源 员工信息 岗位变动记录
客户管理 客户类型 该类型下的客户列表
财务系统 报销单 费用明细
医疗系统 患者档案 就诊记录
合同管理 合同列表 变更记录
教学管理 课程表 上课班级列表
物流系统 运单 运输节点
餐饮系统 桌台订单 菜品明细
建筑/工程 工程项目 施工阶段
行政/办公 会议室预约 参会人员名单
IT/运维 配置项 属性列表
通知公告 公告列表 抄送人员列表

结论

EasyUI、Vue、React、Bootstrap,能用几行JS完成主子表单据的显示和编辑?

答案是:至少 50-80 行,还要学一堆 API。

UserBaoDataGrid引擎呢?

3 行模板 + 1 行 JS,不用学任何框架 API。

这就是差距。


UserBaoDataGrid引擎,解决进销存ERP表单、单据、报表,不必要重资产

让组件迁就你的数据,而不是你迁就组件。

本文由 csdn 长江支流-咏方舟 原创,欢迎转载,源码免费索取! 📩 需要UserBaoDataGrid前端引擎完整源码的朋友,欢迎私信或评论区留言,转载和粉丝免费!

相关推荐
码上成长16 分钟前
Mapbox 围栏编辑踩过的几个坑:key 串了、形状炸了、icon 挡住绿点
前端·前端框架
SGAMERrain29 分钟前
做了一个免费的在线绘画网格工具 DrawGrid,聊聊一个小工具是怎么越做越完整的
前端
我家猫叫佩奇31 分钟前
🦭 厌倦了千篇一律的线性图标?Naive Icons 正式开源
前端·javascript·css
LEE1 小时前
前端转型全栈 00:AI 时代该学哪些,不该学哪些
前端·后端
晚安日记wanna1 小时前
SSR 为什么不适合登录态从水合冲突到缓存串号
前端·react.js·面试
aixingpan1 小时前
aixingpan.cn API开发文档:api_docs_bichart_natalvssolararc2接口指南
前端·php
无限压榨切图仔1 小时前
一个优惠券需求改了三端,我才明白全栈不是多学一门语言
前端·后端
晚安日记wanna1 小时前
批量请求失败只弹一个 Toast面试官想听五层
前端·面试·架构
晚安日记wanna1 小时前
TS const 类型参数一道面试题的四层追问
前端·面试·typescript
用户921080262861 小时前
前端 Vue 专栏 09:Vue Router 与前端路由
前端