导入问题修复

This commit is contained in:
andy committed 2026-09-15 11:34:59 +08:00
1 parent 0282c30a2a
commit ede1d47c47
26 files changed
+934 -45

No files matched your search

@@ -11,9 +11,13 @@ Chrome Manifest V3 扩展,在用户已登录的 LTJT ERP 页面内执行经过
## 当前版本
当前源码版本为 `0.5.183`。版本化 ZIP、文件哈希和 Skill/DOCX 基线见 [`../../dist/release-manifest.json`](../../dist/release-manifest.json)。旧版本实现流水已冻结在 [`../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md`](../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md)。
当前源码版本为 `0.5.184`。版本化 ZIP、文件哈希和 Skill/DOCX 基线见 [`../../dist/release-manifest.json`](../../dist/release-manifest.json)。旧版本实现流水已冻结在 [`../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md`](../../archive/project-history/2026-08-16/chrome-extension-README.pre-governance.md)。
0.5.183 当前重点:
0.5.184 当前重点:
- ERP 列表就绪等待跟随当前 iframe 文档,仍要求加载完成、原生搜索函数与可用按钮,切换期间不在旧文档中搜索;失败回执明确区分页面未就绪,并记录不含业务值的条件与耗时。平台接单等待为 10 秒;明确未写入的迟到检索失败可在同执行编号、原连接范围内同步回平台。需同步更新平台/服务端与插件,无数据库迁移;不重新下发任务,真实写入不确定结果继续人工核验。
继续保留既有修复:
- 安排联动稳定检查将正常采样观察预算与定时回调延迟分开;长间隔后重新观察至少 1.5 秒并要求多个有效样本,30 秒总等待上限内恢复,持续变化、持续延迟、控件失效或原生加载未结束均不保存。新增失败回执区分检测延迟与字段变化,详细证据仅记录字段名、采样次数和时间,不记录业务值。保留原生联动、唯一空行、付款审核、其他行保护及保存前复核;不自动重试保存。合成回归覆盖,真实 ERP 验收待部署方执行。
@@ -2390,6 +2390,12 @@ function erpResolutionFailure(operation, report = {}) {
}
const navigationFailure = blockers.find((item) => item.startsWith('erp_resolution_navigation_failed:'));
if (navigationFailure) {
if (/native_list_search_not_ready|native_list_search_document_changed/.test(navigationFailure)) {
return {
errorCode: 'erp_list_search_not_ready',
message: 'ERP 列表搜索页面未就绪或在检索时发生切换,已停止,未写入 ERP。请待页面加载完成后再操作。'
};
}
return {
errorCode: 'erp_target_lookup_failed',
message: `ERP 检索${targetLabel}失败,请确认 ERP 已登录后重试。`
+72 -15
View File
@@ -1402,19 +1402,72 @@
});
}
async function waitForNativeListSearch(initialDocument, timeoutMs = 15000) {
const expectedPath = String(initialDocument?.location?.pathname || '').toLowerCase();
const framed = initialDocument !== document || Boolean(mainIframe());
const now = () => typeof performance !== 'undefined' ? performance.now() : Date.now();
const startedAt = now();
let samples = 0;
let replacements = 0;
let previousDocument = initialDocument;
let readiness;
while (true) {
// A navigating iframe can expose the destination URL before replacing
// its Document. Never continue waiting on that detached old Document.
let current = null;
let accessible = true;
try { current = framed ? mainIframe()?.contentDocument : document; }
catch { accessible = false; }
if (current && current !== previousDocument) replacements += 1;
previousDocument = current;
let pathMatched = false;
let complete = false;
let loaderReady = false;
let buttonReady = false;
try {
pathMatched = Boolean(current && expectedPath && String(current.location.pathname).toLowerCase() === expectedPath);
complete = current?.readyState === 'complete';
loaderReady = typeof current?.defaultView?.AjaxLoadData === 'function';
const button = current?.querySelector('#SearchButton, [name="SearchButton"]');
buttonReady = Boolean(button && button.isConnected !== false && button.disabled !== true);
} catch { accessible = false; }
samples += 1;
readiness = {
document_accessible: accessible, document_present: Boolean(current),
path_matched: pathMatched, document_complete: complete,
loader_ready: loaderReady, search_button_ready: buttonReady,
document_replacements: replacements, samples,
elapsed_ms: Math.round(now() - startedAt)
};
if (accessible && pathMatched && complete && loaderReady && buttonReady) {
return { document: current, readiness };
}
if (readiness.elapsed_ms >= timeoutMs) {
const missing = ['document_accessible', 'document_present', 'path_matched', 'document_complete', 'loader_ready', 'search_button_ready']
.filter((key) => !readiness[key]);
const error = new Error(`native_list_search_not_ready:${missing.join(',')}:elapsed_ms=${readiness.elapsed_ms}:document_replacements=${replacements}`);
error.nativeListReadiness = readiness;
throw error;
}
await sleep(100);
}
}
function assertNativeListDocumentCurrent(scopeDocument) {
const current = mainIframe() ? mainIframeDocument() : document;
if (current !== scopeDocument) throw new Error('native_list_search_document_changed');
}
async function searchMainPage(scopeDocument, values = {}) {
const nativeSearchReady = await waitForCondition(() => (
scopeDocument.readyState === 'complete'
&& typeof scopeDocument.defaultView?.AjaxLoadData === 'function'
&& Boolean(scopeDocument.querySelector('#SearchButton, [name="SearchButton"]'))
), 15000, 100);
if (!nativeSearchReady) throw new Error('native_list_search_not_ready');
const ready = await waitForNativeListSearch(scopeDocument);
scopeDocument = ready.document;
const searchResolution = resolveNativeListSearchValues(values);
const searchValues = searchResolution.values;
const jq = scopeDocument.defaultView?.jQuery || scopeDocument.defaultView?.$;
if (typeof jq !== 'function' || typeof jq.ajax !== 'function') throw new Error('native_list_search_ajax_not_ready');
const priorAjaxIdle = await waitForCondition(() => Number(jq.active || 0) === 0, 12000, 50);
if (!priorAjaxIdle) throw new Error('native_list_search_prior_ajax_not_idle');
assertNativeListDocumentCurrent(scopeDocument);
const setIfPresent = (name, value) => {
const element = scopeDocument.querySelector(`#${name}, [name="${name}"]`);
if (!element) return false;
@@ -1486,6 +1539,7 @@
), 3000, 50);
if (!statusApplied) throw new Error('native_status_filter_not_applied');
const clickRequestStarted = await waitForCondition(() => capturedXhr, 3000, 25);
assertNativeListDocumentCurrent(scopeDocument);
if (!clickRequestStarted) scopeDocument.defaultView.AjaxLoadData(true);
} else {
// Invoke the ERP's own list loader directly. The exact ajaxSend/ajaxComplete
@@ -1498,10 +1552,12 @@
const completed = await waitForCondition(() => requestCompleted, 15000, 50);
if (!completed) throw new Error('native_list_search_result_timeout');
if (httpStatus < 200 || httpStatus >= 300) throw new Error(`native_list_search_http_${httpStatus || 'error'}`);
assertNativeListDocumentCurrent(scopeDocument);
} finally {
eventTarget.off(eventNamespace);
}
scopeDocument.__ltjtNativeListSearchEvidence = {
readiness: ready.readiness,
date_strategy: searchResolution.date_strategy,
fallback_applied: searchResolution.fallback_applied,
date_range: searchResolution.date_range,
@@ -1819,7 +1875,7 @@
async function searchLifecycleList(path, pattern, criteria, listKind, statusOverride = '') {
const searchValues = lifecycleSearchValues(criteria, listKind, statusOverride);
let scopeDocument = await ensureMainPage(path, pattern, searchValues);
await searchMainPage(scopeDocument, searchValues);
scopeDocument = await searchMainPage(scopeDocument, searchValues);
let stable = await waitForCondition(() => {
const current = currentMainListDocument(pattern, scopeDocument);
return current?.readyState === 'complete' ? current : null;
@@ -1839,7 +1895,7 @@
if (independentBusinessLookup && (!visibleRows.length || !customerEvidence)) {
const primaryEvidence = stable?.__ltjtNativeListSearchEvidence || null;
const fallbackValues = { ...searchValues, S_kehuming: '' };
await searchMainPage(scopeDocument, fallbackValues);
scopeDocument = await searchMainPage(scopeDocument, fallbackValues);
stable = await waitForCondition(() => {
const current = currentMainListDocument(pattern, scopeDocument);
return current?.readyState === 'complete' ? current : null;
@@ -2463,6 +2519,7 @@
blockers: [`erp_resolution_navigation_failed:${error?.message || String(error)}`],
preflight: {
stage: 'erp_readonly_resolution',
...(error.nativeListReadiness ? { native_list_readiness: error.nativeListReadiness } : {}),
lookup_mode: criteria.mode,
identifier,
supplied_filters: {
@@ -2552,7 +2609,7 @@
let listDocument;
try {
listDocument = await ensureMainPage(listPath, listPattern, searchValues);
await searchMainPage(listDocument, searchValues);
listDocument = await searchMainPage(listDocument, searchValues);
} catch (error) {
return lifecycleRouteOutcome('lifecycle_route_blocked', operation, {
blockers: [`passenger_list_navigation_failed:${error?.message || String(error)}`]
@@ -2816,7 +2873,7 @@
let listDocument;
try {
listDocument = await ensureMainPage(listPath, listPattern, searchValues);
await searchMainPage(listDocument, searchValues);
listDocument = await searchMainPage(listDocument, searchValues);
} catch (error) {
return {
ok: false,
@@ -3893,7 +3950,7 @@
S_lingdui: '',
S_zhuangtai: ''
};
await searchMainPage(scopeDocument, searchValues);
scopeDocument = await searchMainPage(scopeDocument, searchValues);
const row = rowForIdentifier(scopeDocument, groupNumber);
return {
found: Boolean(row),
@@ -3939,7 +3996,7 @@
S_lingdui: '',
S_zhuangtai: ''
};
await searchMainPage(scopeDocument, searchValues);
scopeDocument = await searchMainPage(scopeDocument, searchValues);
const selectionExpected = options.fact_probe
? { ...expected, customer_name: '', passenger_counts: undefined, require_customer_match: false, date }
: { ...expected, date };
@@ -4584,7 +4641,7 @@
S_tuanxuhao: parentGroup,
S_zhuangtai: ''
});
await searchMainPage(listDocument, {
listDocument = await searchMainPage(listDocument, {
S_chufariqi: dateYyyyMD(dates[0] || ''),
S_chufarizhi: dateYyyyMD(dates[0] || ''),
S_tuanxuhao: parentGroup,
@@ -4945,7 +5002,7 @@
S_tuanxuhao: listIdentifier
};
let listDocument = await ensureMainPage(listPath, listPattern, searchValues);
await searchMainPage(listDocument, searchValues);
listDocument = await searchMainPage(listDocument, searchValues);
const sourceSearchEvidence = listDocument.__ltjtNativeListSearchEvidence || null;
const stableSourceDocument = await waitForCondition(() => {
const current = currentMainListDocument(listPattern, listDocument);
@@ -6543,7 +6600,7 @@
}
window.LTJTOrderAssistant = {
version: '0.5.183',
version: '0.5.184',
resolveNativeListSearchValues,
lookupKeywordMatchesText,
inspectLifecycleSearchCriteria: lifecycleSearchCriteria,
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "联泰下单助手",
"version": "0.5.183",
"version": "0.5.184",
"description": "在已登录 LTJT ERP 页面内规划并受控执行联泰 ERP 业务操作。",
"permissions": [
"activeTab",
@@ -944,7 +944,7 @@
window.LTJTOrderAssistant = {
...(window.LTJTOrderAssistant || {}),
version: '0.5.183',
version: '0.5.184',
openTeamBatchForm,
pingTeamBatchFrame,
preflightTeamBatchNative,