From 8c229bc162a07d52b7aa813d91358d9bc29246ac Mon Sep 17 00:00:00 2001 From: Codex Date: Mon, 12 Oct 2026 11:54:11 +0800 Subject: [PATCH] feat: add multiple arrival batch rows --- static/js/app.js | 48 +++++++++++++++++++++----- tests/test_order_arrival_multi_rows.py | 34 ++++++++++++++++++ 2 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 tests/test_order_arrival_multi_rows.py diff --git a/static/js/app.js b/static/js/app.js index 20ff566..c4ead29 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -20075,7 +20075,7 @@ function showOrderDetail(encodedStr, title) { html += '加载中...'; } else { html += '
'; - html += '
'; + html += '
'; html += '
'; var showDefectiveQty = orderType === 'packaging'; html += ''; @@ -20256,19 +20256,17 @@ function showOrderDetail(encodedStr, title) { })(); } -function addArrivalBatchRow(btn) { - btn.disabled = true; - btn.textContent = '⋯'; +function appendArrivalBatchEditingRow(btn) { var overlay = btn.closest('.modal-overlay'); if (!overlay) { var tbl = btn.closest('table'); if (tbl) overlay = tbl.closest('.modal-overlay'); } + if (!overlay) return null; var orderId = btn.getAttribute('data-order-id'); var orderType = btn.getAttribute('data-order-type') || 'domestic'; var tbody = overlay.querySelector('#arrivalBatchBody'); - if (!tbody) { btn.disabled = false; btn.textContent = '➕ 添加一行'; return; } - if (tbody.querySelector('.batch-editing')) { btn.disabled = false; btn.textContent = '➕ 添加一行'; return; } + if (!tbody) return null; // 计算下一个批次编号 var lastNum = 0; @@ -20299,10 +20297,37 @@ function addArrivalBatchRow(btn) { // 追加到表格末尾(合计行上方) tbody.appendChild(tr); recalcArrivalUnfinished(); + return tr; +} + +function addArrivalBatchRow(btn) { + var tr = appendArrivalBatchEditingRow(btn); + if (!tr) return; var inp = tr.querySelector('input[type="date"]'); if (inp) inp.focus(); } +function promptAddArrivalBatchRows(btn) { + showCenteredPrompt('添加多行', '请输入需要添加的到货批次行数(1–100行):', '3', { + type: 'number', min: 1, max: 100, step: 1, inputMode: 'numeric' + }).then(function(value) { + if (value === null) return; + var count = Number(value); + if (!Number.isInteger(count) || count < 1 || count > 100) { + showCenteredAlert('输入有误', '请输入1到100之间的整数'); + return; + } + var firstRow = null; + for (var i = 0; i < count; i++) { + var row = appendArrivalBatchEditingRow(btn); + if (!row) break; + if (!firstRow) firstRow = row; + } + var firstInput = firstRow && firstRow.querySelector('input[type="date"]'); + if (firstInput) firstInput.focus(); + }); +} + /** * 取消新增行(删除未保存的行) */ @@ -27330,15 +27355,22 @@ window.showCenteredConfirm = function(title, message) { }; // 通用居中输入弹窗(返回Promise,resolve(输入值)=确认, resolve(null)=取消) -window.showCenteredPrompt = function(title, message, defaultValue) { +window.showCenteredPrompt = function(title, message, defaultValue, options) { return new Promise(function(resolve) { + options = options || {}; + var inputType = options.type === 'number' ? 'number' : 'text'; + var inputAttrs = ''; + if (options.min !== undefined) inputAttrs += ' min="' + escHtml(String(options.min)) + '"'; + if (options.max !== undefined) inputAttrs += ' max="' + escHtml(String(options.max)) + '"'; + if (options.step !== undefined) inputAttrs += ' step="' + escHtml(String(options.step)) + '"'; + if (options.inputMode) inputAttrs += ' inputmode="' + escHtml(String(options.inputMode)) + '"'; var modal = document.createElement('div'); modal.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.45);z-index:100000;display:flex;align-items:center;justify-content:center;overflow-y:auto'; modal.innerHTML = '
' + '
' + '

' + escHtml(title) + '

' + (message ? '

' + message + '

' : '') + - '' + + '' + '
' + '
' + '' + diff --git a/tests/test_order_arrival_multi_rows.py b/tests/test_order_arrival_multi_rows.py new file mode 100644 index 0000000..7b19713 --- /dev/null +++ b/tests/test_order_arrival_multi_rows.py @@ -0,0 +1,34 @@ +"""订单详情到货批次批量加行回归测试。""" + +import unittest +from pathlib import Path + + +APP_JS = (Path(__file__).resolve().parents[1] / 'static/js/app.js').read_text(encoding='utf-8') + + +class OrderArrivalMultiRowsTest(unittest.TestCase): + def test_detail_toolbar_places_multi_row_button_before_single_row_button(self): + multi_pos = APP_JS.index('id="addMultiBatchBtn"') + single_pos = APP_JS.index('id="addBatchBtn"', multi_pos) + self.assertLess(multi_pos, single_pos) + self.assertIn('onclick="promptAddArrivalBatchRows(this)"', APP_JS[multi_pos:single_pos]) + + def test_multi_row_prompt_requires_bounded_integer(self): + self.assertIn("showCenteredPrompt('添加多行'", APP_JS) + self.assertIn("type: 'number', min: 1, max: 100, step: 1", APP_JS) + self.assertIn('!Number.isInteger(count) || count < 1 || count > 100', APP_JS) + + def test_shared_row_builder_allows_repeated_appends(self): + self.assertIn('function appendArrivalBatchEditingRow(btn)', APP_JS) + self.assertIn('for (var i = 0; i < count; i++)', APP_JS) + self.assertIn('var row = appendArrivalBatchEditingRow(btn)', APP_JS) + self.assertNotIn("tbody.querySelector('.batch-editing')", APP_JS) + + def test_centered_prompt_supports_numeric_inputs_without_breaking_text_default(self): + self.assertIn('window.showCenteredPrompt = function(title, message, defaultValue, options)', APP_JS) + self.assertIn("var inputType = options.type === 'number' ? 'number' : 'text'", APP_JS) + + +if __name__ == '__main__': + unittest.main()