Frappe框架:基于子表动态生成表格并实现数据持久化
在Frappe框架中实现动态表格数据持久化到数据库的解决方案
一、先搞定后台数据结构配置
你当前的问题核心是没有为动态表格配置合适的存储结构,直接用普通字段存JSON无法被Frappe正确持久化。需要创建子表Doctype来存储每一行数据:
新建子表Doctype,命名为
Order Sheet Row:- 勾选「Is Child Table」选项
- 添加以下字段:
字段名 类型 标签(Label) po_no Data PO NO store Data Store col_cd Data Col. Cd col_name Data Col. Name qty Float Qty delivery_date Date Delivery Date intercomes Data Intercomes forex_rate Float Forex Rate size_data JSON Size Data
在主Doctype
Order Sheet中添加子表字段:- 字段名:
order_sheet_rows - 类型:Table
- 选项(Options):
Order Sheet Row
- 字段名:
二、修正前端JS代码逻辑
你现有的代码只生成了前端UI,但没有正确关联到Frappe的子表数据结构,且存在属性名错误(比如data-size未设置)。以下是修正后的完整代码:
frappe.ui.form.on('Order Sheet', { refresh: function(frm) { var htmlField = frm.fields_dict['size_chart']; htmlField.$wrapper.empty(); // 固定列配置(对应子表字段名) var fixedColumns = [ { label: 'PO NO', field: 'po_no' }, { label: 'Store', field: 'store' }, { label: 'Col. Cd', field: 'col_cd' }, { label: 'Col. Name', field: 'col_name' }, { label: 'Qty', field: 'qty' }, { label: 'Delivery Date', field: 'delivery_date' }, { label: 'Intercomes', field: 'intercomes' }, { label: 'Forex Rate', field: 'forex_rate' } ]; // 获取Size子表的动态列值 var sizeValues = frm.doc.size.map(row => row.size).filter(Boolean); var allColumns = [...fixedColumns, ...sizeValues.map(size => ({ label: size, field: size }))]; if (allColumns.length > 0) { // 创建表格容器 var tableContainer = document.createElement('div'); tableContainer.style.overflow = 'auto'; tableContainer.style.maxHeight = '300px'; var htmlTable = document.createElement('table'); htmlTable.setAttribute('border', '1'); htmlTable.style.width = '100%'; // 生成表头 var thead = document.createElement('thead'); var headerRow = document.createElement('tr'); allColumns.forEach(col => { var th = document.createElement('th'); th.textContent = col.label; headerRow.appendChild(th); }); thead.appendChild(headerRow); htmlTable.appendChild(thead); // 生成表体(含已有数据回填) var tbody = document.createElement('tbody'); // 加载已保存的子表数据 if (frm.doc.order_sheet_rows && frm.doc.order_sheet_rows.length > 0) { frm.doc.order_sheet_rows.forEach((row, idx) => { addRowToTable(tbody, idx, allColumns, row, frm); }); } else { // 初始化3行空数据 for (let i = 0; i < 3; i++) { addRowToTable(tbody, i, allColumns, null, frm); } } htmlTable.appendChild(tbody); tableContainer.appendChild(htmlTable); htmlField.$wrapper.append(tableContainer); // 添加新增行按钮 var addButton = document.createElement('button'); addButton.textContent = 'Add Row'; addButton.style.marginTop = '10px'; addButton.addEventListener('click', function() { addRowToTable(tbody, tbody.childElementCount, allColumns, null, frm); }); htmlField.$wrapper.append(addButton); } } }); // 生成单一行(含数据回填和事件绑定) function addRowToTable(tbody, rowIndex, columns, savedRow, frm) { var row = document.createElement('tr'); // 初始化子表行数据 if (!savedRow) { savedRow = frappe.model.add_child(frm.doc, 'Order Sheet Row', 'order_sheet_rows'); // 初始化size_data为空对象 savedRow.size_data = {}; } columns.forEach(col => { var cell = document.createElement('td'); var input = document.createElement('input'); input.setAttribute('type', col.field === 'delivery_date' ? 'date' : 'text'); input.setAttribute('data-row-index', rowIndex); input.setAttribute('data-field', col.field); input.setAttribute('data-is-size', sizeValues.includes(col.label) ? 'true' : 'false'); // 回填已保存的数据 if (savedRow) { if (sizeValues.includes(col.label)) { input.value = savedRow.size_data[col.label] || ''; } else { input.value = savedRow[col.field] || ''; } } // 绑定输入变更事件,同步到子表数据 input.addEventListener('change', function() { updateRowData(frm, this, savedRow); }); cell.appendChild(input); row.appendChild(cell); }); tbody.appendChild(row); } // 更新子表行数据到frm.doc function updateRowData(frm, inputElement, savedRow) { var field = inputElement.getAttribute('data-field'); var isSize = inputElement.getAttribute('data-is-size') === 'true'; var value = inputElement.value; if (isSize) { // 动态size列存入size_data JSON字段 savedRow.size_data[field] = value; } else { // 固定列直接更新子表字段 savedRow[field] = value; } // 刷新子表字段,触发Frappe的数据同步 frm.refresh_field('order_sheet_rows'); }
三、关键逻辑说明
- 子表数据关联:通过
frappe.model.add_child创建子表行,所有输入变更直接修改子表行对象,Frappe会自动处理保存、更新、删除逻辑 - 动态列存储:把动态size列的键值对存在子表的
size_dataJSON字段中,避免提前创建大量动态字段 - 数据回填:页面刷新时从
frm.doc.order_sheet_rows读取已保存的数据,回填到动态表格中 - 字段类型适配:针对日期字段(如Delivery Date)使用
date类型输入框,提升用户体验
四、验证保存效果
完成配置和代码修改后:
- 在
Size子表录入动态列值,刷新页面会生成对应列的动态表格 - 在动态表格中输入数据,点击保存按钮
- 重新打开单据,数据会正确回填到表格中,说明已成功持久化到数据库
内容的提问来源于stack exchange,提问作者Aditya vig
相关产品推荐
相关产品推荐

