【前端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">
主表 + 明细,全部可编辑 · 当前订单 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前端引擎完整源码的朋友,欢迎私信或评论区留言,转载和粉丝免费!