修复:防止异步响应覆盖与打印资源泄漏
This commit is contained in:
@@ -657,7 +657,20 @@ test('六个基础资料页面逐页满足 API、权限、分页和安全删除
|
||||
assert.match(printHandler, /document\.createElement\(['"](?:h1|img|style)['"]\)/)
|
||||
assert.match(printHandler, /\.textContent\s*=/)
|
||||
assert.match(printHandler, /['"]load['"][\s\S]*?printWindow\.print\(\)/)
|
||||
assert.match(printHandler, /['"]afterprint['"][\s\S]*?URL\.revokeObjectURL/)
|
||||
assert.match(printHandler, /let closeMonitorId;/)
|
||||
assert.match(printHandler, /let cleanupFinished = false;/)
|
||||
assert.match(printHandler, /const cleanupPrint = \(\) => \{\s*if \(cleanupFinished\) return;\s*cleanupFinished = true;/)
|
||||
assert.match(printHandler, /window\.clearInterval\(closeMonitorId\)/)
|
||||
assert.equal((printHandler.match(/URL\.revokeObjectURL\(objectUrl\)/g) || []).length, 1)
|
||||
assert.equal((printHandler.match(/setPrinting\(row\.palletId, false\)/g) || []).length, 1)
|
||||
assert.match(printHandler, /closeMonitorId = window\.setInterval\(\(\) => \{\s*if \(printWindow\.closed\) cleanupPrint\(\);\s*}, \d+\)/)
|
||||
assert.ok(
|
||||
printHandler.indexOf('window.setInterval(') < printHandler.indexOf('getPalletQrcode('),
|
||||
'打印窗口关闭监测必须在异步二维码请求前启动'
|
||||
)
|
||||
assert.match(printHandler, /['"]error['"][\s\S]*?cleanupPrint\(\)[\s\S]*?printWindow\.close\(\)/)
|
||||
assert.match(printHandler, /['"]afterprint['"][\s\S]*?cleanupPrint\(\)[\s\S]*?printWindow\.close\(\)/)
|
||||
assert.match(printHandler, /\.catch\(\(\) => \{\s*cleanupPrint\(\)/)
|
||||
assert.doesNotMatch(printHandler, /document\.write|innerHTML|BarcodeDetector|getUserMedia|camera/i)
|
||||
assert.match(source, /v-if="isEdit"[\s\S]*?v-model="form\.palletCode"[\s\S]*?readonly/)
|
||||
assert.match(source, /v-if="isEdit"[\s\S]*?v-model="form\.qrToken"[\s\S]*?readonly/)
|
||||
@@ -682,6 +695,17 @@ test('业务用户分配组件使用固定映射并允许空数组完整替换',
|
||||
assert.match(source, /const resourceApiMap = Object\.freeze\(\{[\s\S]*?site:\s*Object\.freeze\(\{\s*getUsers:\s*getSiteUsers,\s*assignUsers:\s*assignSiteUsers\s*}\)[\s\S]*?warehouse:\s*Object\.freeze\(\{\s*getUsers:\s*getWarehouseUsers,\s*assignUsers:\s*assignWarehouseUsers\s*}\)[\s\S]*?}\)/)
|
||||
assert.doesNotMatch(source, /defineProps\(/)
|
||||
assert.match(source, /function show\(\{ resourceType, resourceId, resourceName }\)/)
|
||||
assert.match(source, /let loadGeneration = 0;/)
|
||||
assert.match(source, /function isCurrentUserLoad\(loadContext\)\s*{[\s\S]*?loadContext\.generation === loadGeneration[\s\S]*?loadContext\.resourceType === resourceType\.value[\s\S]*?loadContext\.resourceId === resourceId\.value[\s\S]*?}/)
|
||||
const loadUsersSource = source.slice(source.indexOf('function loadUsers()'), source.indexOf('function resetDialogState()'))
|
||||
const staleLoadGuard = 'if \\(!isCurrentUserLoad\\(loadContext\\)\\) \\{\\s*return;\\s*}'
|
||||
assert.match(loadUsersSource, /const loadContext = \{\s*generation: \+\+loadGeneration,\s*resourceType: resourceType\.value,\s*resourceId: resourceId\.value\s*};/)
|
||||
assert.match(loadUsersSource, /resourceApi\.getUsers\(loadContext\.resourceId\)/)
|
||||
assert.match(loadUsersSource, new RegExp(`\\.then\\(\\(response\\) => \\{\\s*${staleLoadGuard}`))
|
||||
assert.match(loadUsersSource, new RegExp(`\\.catch\\(\\(\\) => \\{\\s*${staleLoadGuard}`))
|
||||
assert.match(loadUsersSource, new RegExp(`\\.finally\\(\\(\\) => \\{\\s*${staleLoadGuard}`))
|
||||
const resetDialogSource = source.slice(source.indexOf('function resetDialogState()'), source.indexOf('function show('))
|
||||
assert.match(resetDialogSource, /loadGeneration \+= 1;/)
|
||||
assert.match(source, /response\.data/)
|
||||
assert.match(source, /userId:\s*row\.userId,\s*userName:\s*row\.userName,\s*nickName:\s*row\.nickName,\s*assigned:\s*row\.assigned === true/)
|
||||
assert.doesNotMatch(source, /\.\.\.row|email:\s*row\.|phonenumber:\s*row\.|status:\s*row\./)
|
||||
|
||||
@@ -104,6 +104,7 @@ const users = ref([]);
|
||||
const selectedUserIds = ref([]);
|
||||
const userTableRef = ref();
|
||||
const restoringSelection = ref(false);
|
||||
let loadGeneration = 0;
|
||||
|
||||
const dialogTitle = computed(() => {
|
||||
const label = resourceType.value === "site" ? "客户现场" : "仓库";
|
||||
@@ -171,12 +172,26 @@ function handleRowClick(row, column) {
|
||||
restoreSelection();
|
||||
}
|
||||
|
||||
function isCurrentUserLoad(loadContext) {
|
||||
return loadContext.generation === loadGeneration
|
||||
&& loadContext.resourceType === resourceType.value
|
||||
&& loadContext.resourceId === resourceId.value;
|
||||
}
|
||||
|
||||
function loadUsers() {
|
||||
const resourceApi = resourceApiMap[resourceType.value];
|
||||
const loadContext = {
|
||||
generation: ++loadGeneration,
|
||||
resourceType: resourceType.value,
|
||||
resourceId: resourceId.value
|
||||
};
|
||||
const resourceApi = resourceApiMap[loadContext.resourceType];
|
||||
loading.value = true;
|
||||
loadFailed.value = false;
|
||||
loadError.value = "";
|
||||
resourceApi.getUsers(resourceId.value).then((response) => {
|
||||
resourceApi.getUsers(loadContext.resourceId).then((response) => {
|
||||
if (!isCurrentUserLoad(loadContext)) {
|
||||
return;
|
||||
}
|
||||
const rows = Array.isArray(response.data) ? response.data : [];
|
||||
users.value = normalizeUserRows(rows);
|
||||
selectedUserIds.value = users.value
|
||||
@@ -184,16 +199,24 @@ function loadUsers() {
|
||||
.map((user) => user.userId);
|
||||
restoreSelection();
|
||||
}).catch(() => {
|
||||
if (!isCurrentUserLoad(loadContext)) {
|
||||
return;
|
||||
}
|
||||
users.value = [];
|
||||
selectedUserIds.value = [];
|
||||
loadError.value = "用户列表加载失败,请稍后重试";
|
||||
loadFailed.value = true;
|
||||
}).finally(() => {
|
||||
if (!isCurrentUserLoad(loadContext)) {
|
||||
return;
|
||||
}
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function resetDialogState() {
|
||||
loadGeneration += 1;
|
||||
loading.value = false;
|
||||
loadError.value = "";
|
||||
keyword.value = "";
|
||||
resourceType.value = "";
|
||||
|
||||
@@ -255,14 +255,25 @@ function handlePrint(row) {
|
||||
}
|
||||
|
||||
let objectUrl;
|
||||
let closeMonitorId;
|
||||
let cleanupFinished = false;
|
||||
setPrinting(row.palletId, true);
|
||||
const revokeObjectUrl = () => {
|
||||
const cleanupPrint = () => {
|
||||
if (cleanupFinished) return;
|
||||
cleanupFinished = true;
|
||||
if (closeMonitorId !== undefined) {
|
||||
window.clearInterval(closeMonitorId);
|
||||
closeMonitorId = undefined;
|
||||
}
|
||||
if (objectUrl) {
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
objectUrl = undefined;
|
||||
}
|
||||
setPrinting(row.palletId, false);
|
||||
};
|
||||
closeMonitorId = window.setInterval(() => {
|
||||
if (printWindow.closed) cleanupPrint();
|
||||
}, 250);
|
||||
|
||||
getPalletQrcode(row.palletId).then((qrCodeBlob) => {
|
||||
if (printWindow.closed) throw new Error("打印窗口已关闭");
|
||||
@@ -282,16 +293,12 @@ function handlePrint(row) {
|
||||
printWindow.print();
|
||||
}, { once: true });
|
||||
imageElement.addEventListener("error", () => {
|
||||
revokeObjectUrl();
|
||||
cleanupPrint();
|
||||
printWindow.close();
|
||||
proxy.$modal.msgError("二维码图片加载失败");
|
||||
}, { once: true });
|
||||
printWindow.addEventListener("afterprint", () => {
|
||||
if (objectUrl) {
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
objectUrl = undefined;
|
||||
}
|
||||
setPrinting(row.palletId, false);
|
||||
cleanupPrint();
|
||||
printWindow.close();
|
||||
}, { once: true });
|
||||
|
||||
@@ -299,7 +306,7 @@ function handlePrint(row) {
|
||||
printWindow.document.head.append(styleElement);
|
||||
printWindow.document.body.replaceChildren(titleElement, imageElement);
|
||||
}).catch(() => {
|
||||
revokeObjectUrl();
|
||||
cleanupPrint();
|
||||
if (!printWindow.closed) printWindow.close();
|
||||
proxy.$modal.msgError("二维码获取失败");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user