Skip to content

Commit 5dfbfef

Browse files
committed
fix(executive): 现任高管详情面板支持切换更新与稳定插入定位
1 parent 5c2a20f commit 5dfbfef

2 files changed

Lines changed: 102 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
- 修复领域数据自动更新中“天气过期”判定读取错误字段,天气有效期结束后会在每分钟检查时触发刷新。
1010
- 合同高价规则入口改名为“二次确认设置”;入库页面没有合同时也显示设置按钮。
1111
- 二次确认设置默认全局规则为 `-0`,未自定义时合同价格高于 MP 即触发二次确认。
12+
- 现任高管详情面板改为插入到培训课程进度汇总块之后,定位不再依赖固定 CSS 类名和游戏文案。
13+
- 修复切换现任高管后面板仍显示上一位高管数据的问题;切换后同步更新并重新插入到当前高管的培训汇总块之后。
1214

1315
## [1.33.2] - 2026-08-11
1416

src/features/executiveTrainingModule.js

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { registerExportInfo } from '../core/exportInfo.js';
44

55
const ExecutiveTrainingModule = (function () {
66
let panelRelocateTimer = null;
7+
let currentPanelRenderTimer = null;
78

89
const OFFERS_URL = "/api/v2/companies/executives/my-offers/";
910
const NOTIFICATIONS_KEYWORD = "/game-notifications/";
@@ -64,6 +65,74 @@ import { registerExportInfo } from '../core/exportInfo.js';
6465
const getCurrentExecPanelContainer = () =>
6566
document.querySelector('#page .row > .col-lg-6') || null;
6667

68+
const getReactFiber = (el) => {
69+
const key = Object.keys(el).find(k => k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$'));
70+
return key ? el[key] : null;
71+
};
72+
73+
const getHostNodeFromFiber = (fiber) => {
74+
const queue = [fiber];
75+
while (queue.length) {
76+
const node = queue.pop();
77+
if (!node) continue;
78+
if (node.stateNode instanceof Element) return node.stateNode;
79+
if (node.sibling) queue.push(node.sibling);
80+
if (node.child) queue.push(node.child);
81+
}
82+
return null;
83+
};
84+
85+
// 定位培训课程进度汇总块,不依赖 CSS 类名和游戏文案。
86+
const getCurrentExecTrainingSummaryNode = (execId) => {
87+
const page = document.getElementById('page');
88+
if (!page || execId == null) return null;
89+
90+
const findKeyedTop = (rootFiber) => {
91+
const queue = [rootFiber];
92+
while (queue.length) {
93+
const node = queue.pop();
94+
if (!node) continue;
95+
if (node.key === 'top') {
96+
const host = getHostNodeFromFiber(node);
97+
const hasProgress = !!(host && host.querySelector('[role="progressbar"]'));
98+
if (hasProgress) {
99+
return host;
100+
}
101+
}
102+
if (node.sibling) queue.push(node.sibling);
103+
if (node.child) queue.push(node.child);
104+
}
105+
return null;
106+
};
107+
108+
for (const el of page.querySelectorAll('div')) {
109+
const fiber = getReactFiber(el);
110+
if (!fiber) continue;
111+
112+
let node = fiber;
113+
while (node) {
114+
const props = node.memoizedProps;
115+
if (props && props.executive && String(props.executive.id) === String(execId)) {
116+
const found = findKeyedTop(node);
117+
if (found) return found;
118+
break;
119+
}
120+
node = node.return;
121+
}
122+
}
123+
124+
const pageFound = findKeyedTop(getReactFiber(page));
125+
return pageFound;
126+
};
127+
128+
const clearCurrentPanelRenderTimer = () => {
129+
if (currentPanelRenderTimer) {
130+
clearInterval(currentPanelRenderTimer);
131+
currentPanelRenderTimer = null;
132+
}
133+
};
134+
window.addEventListener('pagehide', clearCurrentPanelRenderTimer, { once: true });
135+
67136
const ensureAgencyPanelRelocated = () => {
68137
const panel = document.getElementById('sc-plugin-panel');
69138
if (!panel) {
@@ -88,6 +157,7 @@ import { registerExportInfo } from '../core/exportInfo.js';
88157
clearInterval(panelRelocateTimer);
89158
panelRelocateTimer = null;
90159
}
160+
clearCurrentPanelRenderTimer();
91161
}, { once: true });
92162
};
93163

@@ -124,10 +194,12 @@ import { registerExportInfo } from '../core/exportInfo.js';
124194
function renderSkillPanel(data, isError = false, mode = 'agency') {
125195
const targetContainer = mode === 'current' ? getCurrentExecPanelContainer() : getValidTargetContainer();
126196
const panelId = mode === 'current' ? 'sc-current-exec-panel' : 'sc-plugin-panel';
127-
if (!targetContainer || document.getElementById(panelId)) return;
197+
const existingPanel = document.getElementById(panelId);
198+
if (!targetContainer || (mode !== 'current' && existingPanel)) return;
199+
if (mode === 'current') clearCurrentPanelRenderTimer();
128200

129201
const d14 = DM();
130-
const panel = document.createElement('div');
202+
const panel = existingPanel || document.createElement('div');
131203
panel.id = panelId;
132204
const baseStyle = `margin-top: 12px; padding: 12px; border-radius: 4px; font-family: sans-serif; font-size: 14px; background-color: ${d14 ? '#2c2c2c' : '#f2f2f2'}; border: 1px solid ${d14 ? '#555' : '#d1d1d1'}; color: ${d14 ? '#efefef' : '#333'};${mode === 'current' ? ' width:100%; box-sizing:border-box;' : ''}`;
133205

@@ -273,7 +345,24 @@ import { registerExportInfo } from '../core/exportInfo.js';
273345
panel.style = baseStyle;
274346
panel.innerHTML = contentHtml;
275347
if (mode === 'current') {
276-
targetContainer.appendChild(panel);
348+
panel.dataset.scExecId = data?.id != null ? String(data.id) : '';
349+
const anchor = getCurrentExecTrainingSummaryNode(data?.id);
350+
if (anchor) {
351+
anchor.after(panel);
352+
} else {
353+
const startedAt = Date.now();
354+
clearCurrentPanelRenderTimer();
355+
currentPanelRenderTimer = setInterval(() => {
356+
const retryAnchor = getCurrentExecTrainingSummaryNode(data?.id);
357+
if (retryAnchor) {
358+
clearCurrentPanelRenderTimer();
359+
retryAnchor.after(panel);
360+
} else if (Date.now() - startedAt > 5000) {
361+
clearCurrentPanelRenderTimer();
362+
if (targetContainer) targetContainer.appendChild(panel);
363+
}
364+
}, 200);
365+
}
277366
} else {
278367
targetContainer.after(panel);
279368
startAgencyPanelRelocateWatch();
@@ -297,7 +386,14 @@ import { registerExportInfo } from '../core/exportInfo.js';
297386
} else {
298387
const current = getCurrentExecRecord();
299388
const execMatch = url.match(EXEC_API_REGEX);
300-
if (current && execMatch && Number(execMatch[1]) === current.id && getCurrentExecPanelContainer()) {
389+
const currentSlot = getCurrentExecSlot();
390+
const detailPosition = d?.currentWorkHistory?.position;
391+
const detailSlot = detailPosition != null ? SLOT_MAP[detailPosition] : null;
392+
const matchesSlot = currentSlot != null && (
393+
detailSlot === currentSlot ||
394+
(current && execMatch && Number(execMatch[1]) === Number(current.id))
395+
);
396+
if (execMatch && matchesSlot && getCurrentExecPanelContainer()) {
301397
if (isExecutiveHistoryEnabled()) renderSkillPanel(d, false, 'current');
302398
}
303399
}

0 commit comments

Comments
 (0)