From 04b497c70e59f919d43a52ee200ad15ae57e9574 Mon Sep 17 00:00:00 2001 From: gangbaruby <40230962+gangbaRuby@users.noreply.github.com> Date: Wed, 12 Aug 2026 23:30:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(contracts):=20=E4=BA=8C=E6=AC=A1=E7=A1=AE?= =?UTF-8?q?=E8=AE=A4=E8=AE=BE=E7=BD=AE=E9=BB=98=E8=AE=A4=20-0=EF=BC=8C?= =?UTF-8?q?=E5=85=A5=E5=BA=93=E9=A1=B5=E6=97=A0=E5=90=88=E5=90=8C=E4=B9=9F?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 + src/features/incomingContractsHandler.js | 219 ++++++++++++++--------- 2 files changed, 136 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d8a0e8..9862f25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ ### 已变更 - 修复领域数据自动更新中“天气过期”判定读取错误字段,天气有效期结束后会在每分钟检查时触发刷新。 +- 合同高价规则入口改名为“二次确认设置”;入库页面没有合同时也显示设置按钮。 +- 二次确认设置默认全局规则为 `-0`,未自定义时合同价格高于 MP 即触发二次确认。 ## [1.33.2] - 2026-08-11 diff --git a/src/features/incomingContractsHandler.js b/src/features/incomingContractsHandler.js index 2c06f63..73ac3a5 100644 --- a/src/features/incomingContractsHandler.js +++ b/src/features/incomingContractsHandler.js @@ -716,6 +716,8 @@ const { SCXXCS, PROFIT_PER_BUILDING_LEVEL, RETAIL_ADJUSTMENT } = state; insertWarningNotice(); processAllCards([...contractCards]); startMutationObserver(); + } else { + insertWarningNotice(); } }, 500); } @@ -1212,10 +1214,138 @@ const { SCXXCS, PROFIT_PER_BUILDING_LEVEL, RETAIL_ADJUSTMENT } = state; injectHourlyProfitLegacy(card, profitValue, mpPercent, mpValue, mpNotes); } + function getReactFiberFromElement(el) { + const key = Object.keys(el).find(k => k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$')); + return key ? el[key] : null; + } + + function getHostNodeFromFiber(fiber) { + const queue = [fiber]; + while (queue.length) { + const node = queue.pop(); + if (!node) continue; + if (node.stateNode instanceof Element) return node.stateNode; + if (node.sibling) queue.push(node.sibling); + if (node.child) queue.push(node.child); + } + return null; + } + + function getIncomingContractsRoot() { + const page = document.getElementById('page'); + if (!page) return null; + + for (const el of page.querySelectorAll('div')) { + const fiber = getReactFiberFromElement(el); + if (!fiber) continue; + + let node = fiber; + while (node) { + const props = node.memoizedProps; + if (props && props.incoming === true && Object.prototype.hasOwnProperty.call(props, 'currentContracts')) { + return getHostNodeFromFiber(node); + } + node = node.return; + } + } + return null; + } + + function createWarningNotice() { + const isNarrow8 = window.innerWidth <= 576; + const d8 = DM(); + const tip = document.createElement('div'); + tip.style.cssText = ` + display: flex; + flex-wrap: wrap; + align-items: center; + gap: ${isNarrow8 ? '6px 8px' : '8px'}; + color: ${d8 ? '#aaa' : '#777'}; + font-size: ${isNarrow8 ? '11px' : '13px'}; + width: 100%; + `; + tip.dataset.warningText = 'true'; + + // 1. 插入文本提示 + const textSpan = document.createElement('span'); + textSpan.textContent = '自动更新数据有延迟,左下可手动更新'; + textSpan.style.cssText = ` + white-space: ${isNarrow8 ? 'normal' : 'nowrap'}; + flex: ${isNarrow8 ? '1 1 100%' : '0 0 auto'}; + `; + tip.appendChild(textSpan); + + // 2. 按钮组容器 + const btnGroup = document.createElement('div'); + btnGroup.style.cssText = ` + display: flex; + flex-wrap: wrap; + align-items: center; + gap: ${isNarrow8 ? '4px 6px' : '6px'}; + flex: 1 1 auto; + `; + + // 2a. 开关按钮 + const toggle = createGlobalCustomToggle( + 'executiveCustomToggle', + '自定义', + { buttonClass: 'btn btn-primary' }, + (isEnabled) => { + refreshAllContractProfits(); + } + ); + toggle.wrapper.style.marginLeft = "0"; + btnGroup.appendChild(toggle.wrapper); + + // 2b. 自定义数据功能按钮 + const customBtn = document.createElement('button'); + customBtn.type = 'button'; + customBtn.textContent = '自定义高管数据'; + customBtn.style.cssText = ` + padding: 4px 10px; background: #2196f3; + color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; + font-weight: bold; white-space: nowrap; flex-shrink: 0; + `; + customBtn.onclick = () => executiveCustomButton.show(); + btnGroup.appendChild(customBtn); + + // 2c. 显示更多信息开关按钮 + const marketToggle = createGlobalCustomToggle( + 'marketMaxProfitToggle', + '显示更多', + {}, + () => { + refreshAllContractProfits(); + } + ); + marketToggle.wrapper.style.marginLeft = "0"; + btnGroup.appendChild(marketToggle.wrapper); + + // 2d. 二次确认设置按钮 + const priceSetBtn = document.createElement('button'); + priceSetBtn.type = 'button'; + priceSetBtn.textContent = '二次确认设置'; + priceSetBtn.style.cssText = ` + padding: 4px 10px; background: #9c27b0; + color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; + font-weight: bold; white-space: nowrap; flex-shrink: 0; + `; + priceSetBtn.onclick = () => showContractPriceModal(); + btnGroup.appendChild(priceSetBtn); + + tip.appendChild(btnGroup); + return tip; + } + function insertWarningNotice() { if (document.querySelector('[data-warning-text]')) return; const cards = document.querySelectorAll('div[tabindex="0"]'); + if (cards.length === 0) { + const root = getIncomingContractsRoot(); + if (root) root.insertBefore(createWarningNotice(), root.firstChild); + return; + } cards.forEach(card => { let parent = card.parentElement; @@ -1227,88 +1357,7 @@ const { SCXXCS, PROFIT_PER_BUILDING_LEVEL, RETAIL_ADJUSTMENT } = state; const insertTarget = grandParent.firstElementChild; if (!insertTarget || insertTarget === parent) return; - const isNarrow8 = window.innerWidth <= 576; - const d8 = DM(); - const tip = document.createElement('div'); - tip.style.cssText = ` - display: flex; - flex-wrap: wrap; - align-items: center; - gap: ${isNarrow8 ? '6px 8px' : '8px'}; - color: ${d8 ? '#aaa' : '#777'}; - font-size: ${isNarrow8 ? '11px' : '13px'}; - width: 100%; - `; - tip.dataset.warningText = 'true'; - - // 1. 插入文本提示 - const textSpan = document.createElement('span'); - textSpan.textContent = '自动更新数据有延迟,左下可手动更新'; - textSpan.style.cssText = ` - white-space: ${isNarrow8 ? 'normal' : 'nowrap'}; - flex: ${isNarrow8 ? '1 1 100%' : '0 0 auto'}; - `; - tip.appendChild(textSpan); - - // 2. 按钮组容器 - const btnGroup = document.createElement('div'); - btnGroup.style.cssText = ` - display: flex; - flex-wrap: wrap; - align-items: center; - gap: ${isNarrow8 ? '4px 6px' : '6px'}; - flex: 1 1 auto; - `; - - // 2a. 开关按钮 - const toggle = createGlobalCustomToggle( - 'executiveCustomToggle', - '自定义', - { buttonClass: 'btn btn-primary' }, - (isEnabled) => { - refreshAllContractProfits(); - } - ); - toggle.wrapper.style.marginLeft = "0"; - btnGroup.appendChild(toggle.wrapper); - - // 2b. 自定义数据功能按钮 - const customBtn = document.createElement('button'); - customBtn.type = 'button'; - customBtn.textContent = '自定义高管数据'; - customBtn.style.cssText = ` - padding: 4px 10px; background: #2196f3; - color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; - font-weight: bold; white-space: nowrap; flex-shrink: 0; - `; - customBtn.onclick = () => executiveCustomButton.show(); - btnGroup.appendChild(customBtn); - - // 2c. 显示更多信息开关按钮 - const marketToggle = createGlobalCustomToggle( - 'marketMaxProfitToggle', - '显示更多', - {}, - () => { - refreshAllContractProfits(); - } - ); - marketToggle.wrapper.style.marginLeft = "0"; - btnGroup.appendChild(marketToggle.wrapper); - - // 2d. 预期价格设置按钮 - const priceSetBtn = document.createElement('button'); - priceSetBtn.type = 'button'; - priceSetBtn.textContent = '预期价格'; - priceSetBtn.style.cssText = ` - padding: 4px 10px; background: #9c27b0; - color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; - font-weight: bold; white-space: nowrap; flex-shrink: 0; - `; - priceSetBtn.onclick = () => showContractPriceModal(); - btnGroup.appendChild(priceSetBtn); - - tip.appendChild(btnGroup); + const tip = createWarningNotice(); insertTarget.appendChild(tip); }); @@ -1325,7 +1374,7 @@ const { SCXXCS, PROFIT_PER_BUILDING_LEVEL, RETAIL_ADJUSTMENT } = state; const EXCLUDED_IDS = [91, 94, 95, 96, 97, 99]; function getParsedRules() { - const settings = JSON.parse(localStorage.getItem('SC_Contract_HighPrice_Settings') || '{"global":"","individual":""}'); + const settings = JSON.parse(localStorage.getItem('SC_Contract_HighPrice_Settings') || '{"global":"-0","individual":""}'); const parsed = { global: null, individual: new Map() @@ -1541,7 +1590,7 @@ const { SCXXCS, PROFIT_PER_BUILDING_LEVEL, RETAIL_ADJUSTMENT } = state; wrapper.innerHTML = `