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 = `
- 合同预期价格设置 + 合同二次确认设置 ×
@@ -1641,7 +1690,7 @@ const { SCXXCS, PROFIT_PER_BUILDING_LEVEL, RETAIL_ADJUSTMENT } = state; } // 初始化规则列表 - 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":""}'); document.getElementById('sc-contract-global-val').value = settings.global || ''; const lines = (settings.individual || '').split('\n');