【前端2】单据编辑 -订单单据主子表,万能模板行,自定义添加行
作者:长江支流 日期:2026-08-25
前一篇,我发发了 《【前端1】单据编辑 -EasyUI/Vue/React/Bootstrap 主流框架实现订单单据主子表显示和编辑比较,哪个你最易入门?》 点击查看
本篇开始,只发效果图和源码!
先预览
这个示例,不仅实现你新增行时,按你想要的格式做模板,而且点保存时,组织了Json数据,自己连接后端就是一个真实的应用。我已在AI中作了训练,直接用自然语言,它就按UserBaoDataGrid前端渲染引擎的要求,帮我生成了。

**以下是AI对话,你们用AI编程是如何做的?**欢迎探讨分享,反正我不会让AI直接生成C#、Java具体语言,因为这个前端界面,也只是为了验证UserBaoDataGrid前端引擎以及给大家分享前端示例。其实我让AI帮我生成一个描述配置,我去解析,最后我的解析程序也是可直接动态生成前端,实现前端0编码,后端0编码,不需要编译,对AI说完话它生成的东西平台就直接解析运行,解决AI编程还要编译、调试bug、部署、运行这最后一公里的麻烦。

一、标准Html Table,不改变习惯
"订单列表里,每一行都要显示该订单包含的所有商品明细,而且要能直接编辑、新增行、删除行。"
**第一步:我们只按标准table,加一个,组件自动循环 **
**第二步:我们只加Json字段名,组件就替换 **
**第三步:新增行时,这行就是模板,删除行就是当前所在行。 **
**第四步:相编辑,就加input文本框、下拉选择框等等,完全自由 **
html
<button onclick="detailGrid.appendByTemplateRow();" >➕ 新增明细</button>
<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">
<!-- ★★★ 明细表字段全部变成 input ★★★ -->
<td><input type="text" value="<templatefield>OrderID</templatefield>" </td>
<td><input type="text" value="<templatefield>ProductID</templatefield>" /></td>
<td><input type="number" value="<templatefield>Qty</templatefield>" /></td>
<td><input type="number" step="0.01" value="<templatefield>Price</templatefield>" /></td>
<td><input type="number" step="0.01" value="<templatefield>Amount</templatefield>" " /></td>
<td><input type="text" value="<templatefield>Memo</templatefield>" /></td>
<td>
<button onclick="detailGrid.removeRow(this)">移除</button>
</td>
</tr>
</tbody>
</table>
二、3行JavaScript代码,搞定数据加载
javascript
//Step1 实例化插件
var detailGrid = new UserBaoDataGrid("detailGrid");
function doFilter() {
//Step2 加载数据到Table/DIV等任意元素
detailGrid.datagrid({
url: "/demo/data/13order_details_relation.json"
});
}
因而实现了如下效果:

三、新增行时自定义模板
有没有方法?需求特殊,想在添加表格行时,按特定的格式定义模板,增加行就是想要的效果?

原生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" />
<style>
.row-editing {
background: #f0f8ff !important;
border-left: 4px solid #2b6cff !important;
}
.row-saved {
background: #fafffe !important;
border-left: 4px solid #1f9a6e !important;
}
.auto-id-label {
color: #999;
font-size: 13px;
font-weight: 400;
}
.btn-save-row {
padding: 2px 12px;
background: #1f9a6e;
color: #fff;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
}
.btn-save-row:hover {
background: #16825b;
}
.btn-cancel-row {
padding: 2px 12px;
background: #dc3545;
color: #fff;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
}
.btn-cancel-row:hover {
background: #b02a37;
}
.btn-edit-row {
padding: 2px 12px;
background: #2b6cff;
color: #fff;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
}
.btn-edit-row:hover {
background: #1a56db;
}
</style>
<script type="text/javascript">
var masterGrid = new UserBaoDataGrid("masterGrid");
var detailGrid = new UserBaoDataGrid("detailGrid");
// ★★★ 数据存储 ★★★
var masterDataList = [];
var detailDataList = [];
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() {
masterDataList = [];
detailDataList = [];
masterGrid.datagrid({
url: "/demo/data/13orders_relation_with_customers.json",
queryParams: { pageSize: 100 },
success: function (data) {
if (data && data.rows) {
data.rows = data.rows.filter(function (row) { return row.id == mOrderId; });
data.total = data.rows.length;
// 将加载的数据存入 masterDataList
if (data.rows.length > 0) {
masterDataList.push(JSON.parse(JSON.stringify(data.rows[0])));
}
}
}
});
detailGrid.datagrid({
url: "/demo/data/13order_details_relation.json",
queryParams: { pageSize: 100 },
success: function (data) {
if (data && data.rows) {
data.rows = data.rows.filter(function (row) { return row.OrderID == mOrderId; });
data.total = data.rows.length;
// 将加载的数据存入 detailDataList
data.rows.forEach(function (row) {
detailDataList.push(JSON.parse(JSON.stringify(row)));
});
}
}
});
}
function reloadAll() { doFilter(); }
// ============================================================
// ★★★ 自定义新增主表行 ★★★
// ============================================================
function addMasterRow() {
var customHtml = '' +
'<div class="webmis-templatepackrow row-editing" style="cursor:default;display:block;padding:0;margin-bottom:8px;border-radius:6px;">' +
' <div class="webmis-card" style="padding:12px 16px;">' +
' <div class="webmis-bill-row">' +
' <span class="webmis-bill-label">订单编号</span>' +
' <span class="webmis-bill-value auto-id-label">自动编号</span>' +
' </div>' +
' <div class="webmis-bill-row">' +
' <span class="webmis-bill-label">客户名称</span>' +
' <span class="webmis-bill-value">' +
' <input type="text" class="fld-customerName" placeholder="请输入客户名称" style="width:100%;padding:4px 8px;border:1px solid #2b6cff;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" class="fld-total_amount" placeholder="0.00" style="width:100%;padding:4px 8px;border:1px solid #2b6cff;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" class="fld-order_date" style="width:100%;padding:4px 8px;border:1px solid #2b6cff;border-radius:4px;" />' +
' </span>' +
' </div>' +
' <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:8px;padding-top:8px;border-top:1px solid #e8ecf1;">' +
' <button class="btn-save-row" onclick="saveMasterRow(this)">💾 保存</button>' +
' <button class="btn-cancel-row" onclick="$(this).closest(\'.webmis-templatepackrow\').remove()">✕ 取消</button>' +
' </div>' +
' </div>' +
'</div>';
// 追加到主表容器
$('#masterGrid .webmis-templatepack').append(customHtml);
}
// ★★★ 保存主表行 ★★★
function saveMasterRow(btn) {
var $row = $(btn).closest('.webmis-templatepackrow');
var order_id = $row.find('.auto-id-label').text().trim();
var customerName = $row.find('.fld-customerName').val();
var total_amount = $row.find('.fld-total_amount').val();
var order_date = $row.find('.fld-order_date').val();
if (!customerName) { alert('请输入客户名称!'); return; }
var data = {
order_id: order_id,
customerName: customerName,
total_amount: total_amount || '0',
order_date: order_date || new Date().toISOString().slice(0, 16)
};
// 存储数据
masterDataList.push(data);
// 行变为已保存样式
$row.removeClass('row-editing').addClass('row-saved');
$row.find('input').css('border-color', '#d0d8e4').prop('readonly', true);
$row.find('.btn-save-row, .btn-cancel-row').remove();
$row.find('.webmis-card').append(
'<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:8px;padding-top:8px;border-top:1px solid #e8ecf1;">' +
' <span style="color:#1f9a6e;font-size:13px;">✅ 已保存</span>' +
' <button class="btn-edit-row" onclick="editMasterRow(this)">✎ 编辑</button>' +
'</div>'
);
alert('✅ 主表行已保存!\n\n' + JSON.stringify(data, null, 2));
}
// ★★★ 编辑主表行 ★★★
function editMasterRow(btn) {
var $row = $(btn).closest('.webmis-templatepackrow');
$row.removeClass('row-saved').addClass('row-editing');
$row.find('input').prop('readonly', false).css('border-color', '#2b6cff');
$row.find('.btn-edit-row, span:contains("已保存")').closest('div').remove();
$row.find('.webmis-card').append(
'<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:8px;padding-top:8px;border-top:1px solid #e8ecf1;">' +
' <button class="btn-save-row" onclick="saveMasterRow(this)">💾 保存</button>' +
' <button class="btn-cancel-row" onclick="$(this).closest(\'.webmis-templatepackrow\').remove()">✕ 取消</button>' +
'</div>'
);
}
// ============================================================
// ★★★ 自定义新增明细行 ★★★
// ============================================================
function addDetailRow() {
var customHtml = '' +
'<tr class="row-editing">' +
' <td><span class="auto-id-label">自动编号</span></td>' +
' <td><input type="text" class="fld-ProductID" placeholder="商品编号" style="width:100%;padding:4px 6px;border:1px solid #2b6cff;border-radius:3px;" /></td>' +
' <td><input type="number" class="fld-Qty" placeholder="数量" style="width:100%;padding:4px 6px;border:1px solid #2b6cff;border-radius:3px;" /></td>' +
' <td><input type="number" step="0.01" class="fld-Price" placeholder="单价" style="width:100%;padding:4px 6px;border:1px solid #2b6cff;border-radius:3px;" /></td>' +
' <td><input type="number" step="0.01" class="fld-Amount" placeholder="金额" style="width:100%;padding:4px 6px;border:1px solid #2b6cff;border-radius:3px;" /></td>' +
' <td><input type="text" class="fld-Memo" placeholder="备注" style="width:100%;padding:4px 6px;border:1px solid #2b6cff;border-radius:3px;" /></td>' +
' <td>' +
' <button class="btn-save-row" onclick="saveDetailRow(this)">💾 保存</button>' +
' <button class="btn-cancel-row" onclick="$(this).closest(\'tr\').remove()">✕ 取消</button>' +
' </td>' +
'</tr>';
$('#detailGrid tbody').append(customHtml);
}
// ★★★ 保存明细行 ★★★
function saveDetailRow(btn) {
var $row = $(btn).closest('tr');
var id = $row.find('.auto-id-label').text().trim();
var ProductID = $row.find('.fld-ProductID').val();
var Qty = $row.find('.fld-Qty').val();
var Price = $row.find('.fld-Price').val();
var Amount = $row.find('.fld-Amount').val();
var Memo = $row.find('.fld-Memo').val();
if (!ProductID) { alert('请输入商品编号!'); return; }
var data = {
id: id,
OrderID: parseInt(mOrderId) || 0,
ProductID: ProductID,
Qty: parseInt(Qty) || 0,
Price: parseFloat(Price) || 0,
Amount: parseFloat(Amount) || 0,
Memo: Memo || ''
};
// 存储数据
detailDataList.push(data);
// 行变为已保存样式
$row.removeClass('row-editing').addClass('row-saved');
$row.find('input').css('border-color', '#d0d8e4').prop('readonly', true);
$row.find('.btn-save-row, .btn-cancel-row').remove();
$row.find('td:last').html(
'<span style="color:#1f9a6e;font-size:13px;">✅ 已保存</span>' +
' <button class="btn-edit-row" onclick="editDetailRow(this)">✎ 编辑</button>'
);
alert('✅ 明细行已保存!\n\n' + JSON.stringify(data, null, 2));
}
// ★★★ 编辑明细行 ★★★
function editDetailRow(btn) {
var $row = $(btn).closest('tr');
$row.removeClass('row-saved').addClass('row-editing');
$row.find('input').prop('readonly', false).css('border-color', '#2b6cff');
$row.find('.btn-edit-row, span:contains("已保存")').remove();
$row.find('td:last').html(
'<button class="btn-save-row" onclick="saveDetailRow(this)">💾 保存</button>' +
' <button class="btn-cancel-row" onclick="$(this).closest(\'tr\').remove()">✕ 取消</button>'
);
}
// ============================================================
// ★★★ 全局保存:弹出所有数据 ★★★
// ============================================================
function saveAllData() {
// 收集当前所有已保存的行(从存储列表中取)
var masterJson = masterDataList.length > 0 ? masterDataList[0] : {};
var detailJson = detailDataList;
// 检查是否有正在编辑的行未保存(提示用户)
if ($('#masterGrid .row-editing').length > 0) {
if (!confirm('有主表行正在编辑未保存,确定要提交吗?')) return;
}
if ($('#detailGrid .row-editing').length > 0) {
if (!confirm('有明细行正在编辑未保存,确定要提交吗?')) return;
}
var result = {
master: masterJson,
details: detailJson
};
alert('📦 保存数据\n\n' +
'【主表】\n' + JSON.stringify(result.master, null, 2) + '\n\n' +
'【明细表】共 ' + result.details.length + ' 条\n' + JSON.stringify(result.details, null, 2)
);
console.log('保存数据:', result);
}
</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>
<button class="webmis-bill-btn" style="background:#2b6cff;color:#fff;" onclick="addMasterRow();">📝 新增主表</button>
<button class="webmis-bill-btn" style="background:#1f9a6e;color:#fff;font-weight:bold;" onclick="saveAllData();">💾 保存</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;margin-bottom:8px;">
<div class="webmis-card" style="padding:12px 16px;">
<div class="webmis-bill-row">
<span class="webmis-bill-label">订单编号</span>
<span class="webmis-bill-value"><templatefield>order_id</templatefield></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;" readonly />
</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;" readonly />
</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;" readonly />
</span>
</div>
<div style="display:flex;gap:8px;justify-content:flex-end;margin-top:8px;padding-top:8px;border-top:1px solid #e8ecf1;">
<span style="color:#1f9a6e;font-size:13px;">✅ 已加载</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:#2b6cff;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:13px;">➕ 新增明细</button>
</div>
<table id="detailGrid" class="webmis-bill-detail">
<thead>
<tr>
<th width="10%">明细ID</th>
<th width="20%">商品编号</th>
<th width="10%">数量</th>
<th width="15%">单价</th>
<th width="15%">金额</th>
<th width="20%">备注</th>
<th width="10%">操作</th>
</tr>
</thead>
<tbody>
<tr templaterow="true" data-src="rows">
<td><input type="text" value="<templatefield>id</templatefield>" style="width:100%;padding:4px 6px;border:1px solid #d0d8e4;border-radius:3px;" readonly /></td>
<td><input type="text" value="<templatefield>ProductID</templatefield>" style="width:100%;padding:4px 6px;border:1px solid #d0d8e4;border-radius:3px;" readonly /></td>
<td><input type="number" value="<templatefield>Qty</templatefield>" style="width:100%;padding:4px 6px;border:1px solid #d0d8e4;border-radius:3px;" readonly /></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;" readonly /></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;" readonly /></td>
<td><input type="text" value="<templatefield>Memo</templatefield>" style="width:100%;padding:4px 6px;border:1px solid #d0d8e4;border-radius:3px;" readonly /></td>
<td><span style="color:#1f9a6e;font-size:13px;">✅ 已加载</span></td>
</tr>
</tbody>
</table>
</div>
<script>
$(document).ready(function () {
$('#orderIdDisplay').text(mOrderId);
});
</script>
</body>
</html>
UserBaoDataGrid引擎,解决进销存ERP表单、单据、报表,不必要重资产
让组件迁就你的数据,而不是你迁就组件。
本文由 csdn 长江支流-咏方舟 原创,欢迎转载,源码免费索取! 📩 需要UserBaoDataGrid前端引擎完整源码的朋友,欢迎私信或评论区留言,转载和粉丝免费!