CoffeeMonsterz-AfterDiscount-v3.6

显示 After Discount = Subtotal - Discount(自动追踪位置,始终在 Discount 下方)

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         CoffeeMonsterz-AfterDiscount-v3.6
// @namespace    https://coffeemonsterz.dev
// @version      3.6
// @description  显示 After Discount = Subtotal - Discount(自动追踪位置,始终在 Discount 下方)
// @author       Alex Yuan
// @match        https://admin.shopify.com/store/thecoffeemonsterzco/*/*
// @match        https://admin.shopify.com/store/thecoffeemonsterzco/*
// @match        https://admin.shopify.com/store/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';
  console.log('☕ CoffeeMonsterz-AfterDiscount v3.6 已加载');

  let lastSubtotal = null;
  let lastDiscount = null;
  let updateTimer = null;
  let inserted = false;
  let lastCheckTime = 0;

  const cleanText = (t) =>
    t.replace(/[\u200B-\u200F\u202A-\u202E\u2060\uFEFF]/g, '').trim();

  function findLabelNode(regex) {
    const allTextSpans = document.querySelectorAll('.Polaris-Text--root');
    for (const el of allTextSpans) {
      const text = cleanText(el.textContent || '').toLowerCase();
      if (regex.test(text)) return el;
    }
    return null;
  }

  function extractAmountFromRow(rowEl) {
    if (!rowEl) return null;
    const numEl = rowEl.querySelector('.Polaris-Text--numeric');
    if (!numEl) return null;
    return parseMoney(numEl.textContent);
  }

  function parseMoney(text) {
    if (!text) return null;
    const cleaned = cleanText(text);
    const m = cleaned.match(/-?\$?\s*(\d+(?:\.\d+)?)/);
    if (!m) return null;
    const value = parseFloat(m[1]);
    const isNegative = cleaned.includes('-');
    return isNegative ? -value : value;
  }

  function getSubtotal() {
    const label = findLabelNode(/^(subtotal|小计|小計)\b/);
    const row = label ? label.closest('.Polaris-InlineGrid') : null;
    const amt = extractAmountFromRow(row);
    return amt ?? 0;
  }

  function getDiscount() {
    const label = findLabelNode(/^(discount|discounts|折扣)/);
    if (label) {
      const row = label.closest('.Polaris-InlineGrid');
      if (row) {
        const amtRaw = extractAmountFromRow(row);
        if (amtRaw !== null) return Math.abs(amtRaw);
      }
    }
    const spans = document.querySelectorAll('span');
    for (const el of spans) {
      const txt = cleanText(el.textContent || '');
      if (!txt.includes('$')) continue;
      if (!/-\s*\$?\s*\d/.test(txt)) continue;
      const v = parseMoney(txt);
      if (v !== null && v < 0) return Math.abs(v);
    }
    return 0;
  }

  function ensurePosition(rowEl, discountRow, shippingRow, subtotalRow) {
    if (!rowEl || !document.body.contains(rowEl)) return false;

    // 检查 Discount 出现后位置是否正确
    if (discountRow) {
      const pos = rowEl.compareDocumentPosition(discountRow);
      if (pos & Node.DOCUMENT_POSITION_FOLLOWING) {
        // AfterDiscount 在 Discount 上方 → 移动
        discountRow.insertAdjacentElement('afterend', rowEl);
        console.log('♻️ 调整 After Discount 到 Discount 下方');
        return true;
      }
    }
    // 若 Discount 不存在但 Shipping 出现,则确保在 Shipping 上方
    if (!discountRow && shippingRow) {
      const pos = rowEl.compareDocumentPosition(shippingRow);
      if (pos & Node.DOCUMENT_POSITION_PRECEDING) {
        shippingRow.parentNode.insertBefore(rowEl, shippingRow);
        console.log('♻️ 调整 After Discount 到 Shipping 上方');
        return true;
      }
    }
    return false;
  }

  function renderRow() {
    const subtotal = getSubtotal();
    const discount = getDiscount();
    const after = subtotal - discount;

    if (subtotal === lastSubtotal && discount === lastDiscount) return;
    lastSubtotal = subtotal;
    lastDiscount = discount;

    const color = discount > 0 ? '#0a7' : '#888';
    const note = discount > 0 ? '' : '<span style="color:#aaa;font-size:0.8em;">(no discount)</span>';

    const rowHTML = `
      <div id="after-discount-row" class="Polaris-InlineGrid"
           style="--pc-inline-grid-grid-template-columns-xs:1fr auto;--pc-inline-grid-gap-xs:var(--p-space-200);margin-top:4px;">
        <div class="Polaris-InlineStack" style="--pc-inline-stack-block-align:baseline;">
          <span class="Polaris-Text--root Polaris-Text--bodyMd Polaris-Text--semibold">After Discount ${note}</span>
        </div>
        <div>
          <div class="Polaris-InlineStack"
               style="--pc-inline-stack-align:space-between;--pc-inline-stack-block-align:center;">
            <span class="Polaris-Text--root Polaris-Text--bodyMd Polaris-Text--semibold Polaris-Text--numeric"
                  style="color:${color};">$${after.toFixed(2)}</span>
          </div>
        </div>
      </div>
    `;

    const shippingRow = findLabelNode(/^(shipping|运费|送料)\b/)?.closest('.Polaris-InlineGrid');
    const discountRow = findLabelNode(/^(discount|discounts|折扣)/)?.closest('.Polaris-InlineGrid');
    const subtotalRow = findLabelNode(/^(subtotal|小计|小計)\b/)?.closest('.Polaris-InlineGrid');

    let rowEl = document.querySelector('#after-discount-row');
    if (!rowEl || !document.body.contains(rowEl)) {
      const container = document.createElement('div');
      container.innerHTML = rowHTML.trim();
      rowEl = container.firstElementChild;

      if (discountRow && discountRow.parentNode) {
        discountRow.insertAdjacentElement('afterend', rowEl);
        console.log('✅ 插入 Discount 下方');
      } else if (shippingRow && shippingRow.parentNode) {
        shippingRow.parentNode.insertBefore(rowEl, shippingRow);
        console.log('✅ 插入 Shipping 上方');
      } else if (subtotalRow && subtotalRow.parentNode) {
        subtotalRow.insertAdjacentElement('afterend', rowEl);
        console.log('✅ 插入 Subtotal 后面 (fallback)');
      } else {
        console.warn('⚠️ 未找到插入锚点,等待下一轮');
        return;
      }
      inserted = true;
    } else {
      const priceEl = rowEl.querySelector('.Polaris-Text--numeric');
      const labelEl = rowEl.querySelector('.Polaris-Text--bodyMd');
      if (priceEl) priceEl.textContent = `$${after.toFixed(2)}`;
      if (labelEl) labelEl.innerHTML = `After Discount ${note}`;
    }

    // 定期确保位置正确(防止 Discount 后出现)
    ensurePosition(rowEl, discountRow, shippingRow, subtotalRow);
  }

  const observer = new MutationObserver(() => {
    if (updateTimer) return;
    updateTimer = setTimeout(() => {
      updateTimer = null;
      renderRow();
    }, 400);
  });
  observer.observe(document.body, { childList: true, subtree: true });

  // 连续检测 Discount 是否出现(防止错位)
  setInterval(() => {
    const now = Date.now();
    if (now - lastCheckTime < 1000) return;
    lastCheckTime = now;
    const discountRow = findLabelNode(/^(discount|discounts|折扣)/)?.closest('.Polaris-InlineGrid');
    const rowEl = document.querySelector('#after-discount-row');
    const shippingRow = findLabelNode(/^(shipping|运费|送料)\b/)?.closest('.Polaris-InlineGrid');
    const subtotalRow = findLabelNode(/^(subtotal|小计|小計)\b/)?.closest('.Polaris-InlineGrid');
    ensurePosition(rowEl, discountRow, shippingRow, subtotalRow);
  }, 1000);

  // 初始阶段多轮尝试(应对懒加载)
  let tries = 0;
  const boot = setInterval(() => {
    renderRow();
    if (++tries > 25) clearInterval(boot);
  }, 800);

  // 对外暴露更新接口
  window.updateAfterDiscount = renderRow;
})();