fix: refresh confirmed order pages and correct backup navigation

This commit is contained in:
Codex 2026-10-09 18:39:14 +08:00
parent 5cfc056c6b
commit 5c252fb272
2 changed files with 135 additions and 6 deletions

View File

@ -4476,6 +4476,11 @@ function loadPage(page) {
}); });
if (typeof loadSuppliers === 'function') loadSuppliers(1); if (typeof loadSuppliers === 'function') loadSuppliers(1);
} }
// 国内订单可能已被其他人确认;切回缓存页面时重新查询,保留当前筛选和页码。
if (page === 'orders_domestic' && typeof domesticLoadOrders === 'function') {
invalidateOrderColumnCache('domestic');
domesticLoadOrders(domesticOrderPage || 1);
}
// 先销毁原有 Chart 实例(canvas detach 后 chart 可能状态错乱) // 先销毁原有 Chart 实例(canvas detach 后 chart 可能状态错乱)
if (typeof Chart !== 'undefined') { if (typeof Chart !== 'undefined') {
const canvases = cached.querySelectorAll('canvas'); const canvases = cached.querySelectorAll('canvas');
@ -16160,22 +16165,36 @@ function renderPendingConfirmModal(modal, item, bomMaterials, onConfirm) {
} }
function _refreshLinkedOrderLists() { function _refreshLinkedOrderLists() {
// 只重新请求当前已挂载的订单表,沿用各自现有的筛选、表头筛选和页码状态。 // 未挂载的页面容器仍可能保留下单前的数据,切回时必须重新加载。
['orders_domestic', 'orders_overseas', 'orders_packaging', 'purchase_ready'].forEach(function(page) {
var container = window._pageCache && window._pageCache[page];
if (container && !container.isConnected) {
delete window._pageCache[page];
if (window._pageDataCache) delete window._pageDataCache[page];
}
});
['domestic', 'overseas', 'packaging', 'backup'].forEach(function(type) {
invalidateOrderColumnCache(type);
});
// 当前已挂载的订单表保留筛选和页码,立即重新请求。
if (document.getElementById('domesticOrdersTbody') && typeof domesticLoadOrders === 'function') domesticLoadOrders(domesticOrderPage || 1); if (document.getElementById('domesticOrdersTbody') && typeof domesticLoadOrders === 'function') domesticLoadOrders(domesticOrderPage || 1);
if (document.getElementById('overseasOrdersTbody') && typeof overseasLoadOrders === 'function') overseasLoadOrders(overseasOrderPage || 1); if (document.getElementById('overseasOrdersTbody') && typeof overseasLoadOrders === 'function') overseasLoadOrders(overseasOrderPage || 1);
if (document.getElementById('packagingOrdersTbody') && typeof packagingLoadOrders === 'function') packagingLoadOrders(packagingOrderPage || 1); if (document.getElementById('packagingOrdersTbody') && typeof packagingLoadOrders === 'function') packagingLoadOrders(packagingOrderPage || 1);
if (document.getElementById('orderTbody') && typeof loadBackupList === 'function') loadBackupList(false);
} }
function showConfirmRedirect(hasProduct, hasPackaging) { function showConfirmRedirect(hasProduct, hasPackaging, productPage) {
var links = []; var links = [];
if (hasProduct) { if (hasProduct) {
links.push({ label: '📦 国内/海外订单管理', page: '/orders/domestic' }); var overseas = productPage === 'orders_overseas';
links.push({ label: overseas ? '📦 海外订单管理' : '📦 国内订单管理',
page: overseas ? '/orders/overseas' : '/orders/domestic' });
} }
if (hasPackaging) { if (hasPackaging) {
links.push({ label: '📦 包材订单管理', page: '/orders/packaging' }); links.push({ label: '📦 包材订单管理', page: '/orders/packaging' });
} }
// 下单备份始终存在(已标记为ordered的记录) // 下单备份始终存在(已标记为ordered的记录)
links.push({ label: '📋 下单备份管理', page: '/purchase' }); links.push({ label: '📋 下单备份管理', page: '/purchase/ready' });
if (links.length === 0) return; if (links.length === 0) return;
@ -16231,7 +16250,7 @@ function pendingBatchConfirm() {
if (res.code === 0) { if (res.code === 0) {
_refreshLinkedOrderLists(); _refreshLinkedOrderLists();
showCenteredAlert('操作成功', '✅ 批量下单成功,共 ' + checkedCodes.length + ' 个', function() { showCenteredAlert('操作成功', '✅ 批量下单成功,共 ' + checkedCodes.length + ' 个', function() {
showConfirmRedirect(res.has_product_order, res.has_packaging_order); showConfirmRedirect(res.has_product_order, res.has_packaging_order, res.redirect);
}); });
loadPendingList(); loadPendingList();
} else { } else {
@ -16323,7 +16342,7 @@ function pendingConfirmOrder(productCode) {
_refreshLinkedOrderLists(); _refreshLinkedOrderLists();
showCenteredAlert('操作成功', '✅ 下单成功', { showCenteredAlert('操作成功', '✅ 下单成功', {
zIndex: 100000, zIndex: 100000,
onConfirm: function() { showConfirmRedirect(res.has_product_order, res.has_packaging_order); } onConfirm: function() { showConfirmRedirect(res.has_product_order, res.has_packaging_order, res.redirect); }
}); });
loadPendingList(); loadPendingList();
} else { } else {

View File

@ -0,0 +1,110 @@
/* Run with node: actual page functions in a minimal browser context. */
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const source = fs.readFileSync(path.join(__dirname, '../static/js/app.js'), 'utf8');
function functionSource(start, end) {
const offset = source.indexOf(start);
assert.ok(offset >= 0, start);
const finish = source.indexOf(end, offset);
assert.ok(finish > offset, end);
return source.slice(offset, finish);
}
function context() {
const calls = [];
const overlays = [];
const mainContent = {
querySelectorAll: () => [],
appendChild: element => { element.isConnected = true; }
};
const elements = { mainContent };
const environment = {
window: { _pageCache: {}, _pageDataCache: {}, _pageReloaders: {} },
document: {
getElementById: id => elements[id] || null,
querySelectorAll: () => [],
querySelector: () => null,
createElement: () => ({ style: {}, innerHTML: '' }),
body: { appendChild: element => overlays.push(element) }
},
domesticOrderPage: 3,
overseasOrderPage: 2,
packagingOrderPage: 4,
domesticLoadOrders: page => calls.push(['domestic', page]),
overseasLoadOrders: page => calls.push(['overseas', page]),
packagingLoadOrders: page => calls.push(['packaging', page]),
loadBackupList: reset => calls.push(['backup', reset]),
invalidateOrderColumnCache: type => calls.push(['invalidate', type]),
requestAnimationFrame: callback => callback(),
setTimeout: callback => callback(),
};
vm.createContext(environment);
vm.runInContext(functionSource('function _refreshLinkedOrderLists()', '// 防重复提交锁'), environment);
vm.runInContext(functionSource('function loadPage(page)', '// ============ SPA 导航系统'), environment);
return { environment, calls, overlays, elements };
}
// Returning to a previously visited domestic order page queries current records,
// even when the order was confirmed in another user's browser.
{
const { environment: env, calls } = context();
const cached = { style: {}, isConnected: false, querySelectorAll: () => [] };
env.window._pageCache.orders_domestic = cached;
env.loadPage('orders_domestic');
assert.equal(cached.isConnected, true);
assert.deepEqual(calls, [['invalidate', 'domestic'], ['domestic', 3]]);
}
// Confirming an order while its destination pages are detached removes their
// old containers/data, while unrelated pages remain intact.
{
const { environment: env, calls } = context();
const pages = ['orders_domestic', 'orders_overseas', 'orders_packaging', 'purchase_ready'];
pages.forEach(page => {
env.window._pageCache[page] = { isConnected: false };
env.window._pageDataCache[page] = { old: true };
});
const unrelated = { isConnected: false };
env.window._pageCache.planning = unrelated;
env._refreshLinkedOrderLists();
pages.forEach(page => {
assert.equal(env.window._pageCache[page], undefined);
assert.equal(env.window._pageDataCache[page], undefined);
});
assert.equal(env.window._pageCache.planning, unrelated);
assert.deepEqual(calls, ['domestic', 'overseas', 'packaging', 'backup'].map(type => ['invalidate', type]));
}
// A currently mounted list stays mounted and refreshes with its current page.
{
const { environment: env, elements, calls } = context();
const current = { isConnected: true };
env.window._pageCache.orders_domestic = current;
elements.domesticOrdersTbody = {};
env._refreshLinkedOrderLists();
assert.equal(env.window._pageCache.orders_domestic, current);
assert.deepEqual(calls.at(-1), ['domestic', 3]);
}
// Finished-product navigation follows the server's route and the backup link
// actually opens the backup menu rather than returning to pending orders.
for (const [route, url] of [['orders_domestic', '/orders/domestic'], ['orders_overseas', '/orders/overseas']]) {
const { environment: env, overlays } = context();
env.showConfirmRedirect(true, false, route);
const html = overlays[0].innerHTML;
assert.ok(html.includes(`href="${url}"`));
assert.ok(html.includes('href="/purchase/ready"'));
assert.ok(!html.includes('href="/purchase"'));
}
{
const { environment: env, overlays } = context();
env.showConfirmRedirect(false, true, null);
assert.ok(overlays[0].innerHTML.includes('href="/orders/packaging"'));
assert.ok(!overlays[0].innerHTML.includes('href="/orders/domestic"'));
}
console.log('Order navigation regression tests passed (6 scenarios).');