您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Convert RMB to NTD on Taobao and Tmall with real-time exchange rate
// ==UserScript== // @name 淘寶人民幣轉新台幣(僅供參考) // @namespace http://tampermonkey.net/ // @version 1.5 // @description Convert RMB to NTD on Taobao and Tmall with real-time exchange rate // @author Grok // @match *://*.taobao.com/* // @match *://*.tmall.com/* // @exclude https://buy.taobao.com/auction/order/confirm_order.htm // @grant GM_xmlhttpRequest // @license MIT // ==/UserScript== (async function() { 'use strict'; const DEFAULT_RATE = 4.5; let exchangeRate = DEFAULT_RATE; const selectors = { rightItem: { unit: '[class*="right-item-label"]', value: '.right-item-amount' }, priceWrapper: { unit: '[class*="price-unit"]', value: '.price-value' }, genericWrapper: { unit: '[class*="unit--"]', wrapper: '[class*="innerPriceWrapper--"]', value: '[class*="priceInt--"]' }, highlightPrice: { container: '[class*="--highlightPrice--"]', unit: '[class*="--symbol--"]', value: '[class*="--text--"' }, businessEntry: { unit: '.business-entry-item-card-content-coin-title', value: '.business-entry-item-card-content-coin-title + span' }, tradePrice: { container: '.trade-price-container', unit: '.trade-price-symbol', integer: '.trade-price-integer', point: '.trade-price-point', decimal: '.trade-price-decimal' }, priceWrap: { container: '[class*="priceWrap--"]', unit: '[class*="symbol--"]', wrapper: '[class*="price--"]' } }; // Utility functions const log = (...args) => console.log('[RMB-NTD]', ...args); const fetchExchangeRate = () => new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: 'https://api.exchangerate-api.com/v4/latest/CNY', onload: res => resolve(JSON.parse(res.responseText).rates.TWD), onerror: reject }); }); const convertPrice = (rmb) => { return Math.round(rmb * exchangeRate * 100) / 100; }; const processPriceElement = (unitEl, valueEl, context) => { if (!valueEl || (unitEl.textContent !== '¥' && unitEl.textContent !== '¥')) return false; const priceText = valueEl.textContent.trim(); const priceMatch = priceText.match(/^(\d+(\.\d+)?)$/); if (!priceMatch) return false; const rmb = parseFloat(priceMatch[1]); const ntd = convertPrice(rmb); unitEl.textContent = '$'; valueEl.textContent = ntd; valueEl.dataset.originalRmb = rmb; valueEl.dataset.convertedNtd = ntd; logcki(`Converted ¥${rmb} to $${ntd} in ${context}`); return true; }; const processTradePriceElement = (container) => { const unit = container.querySelector(selectors.tradePrice.unit); const integer = container.querySelector(selectors.tradePrice.integer); const point = container.querySelector(selectors.tradePrice.point); const decimal = container.querySelector(selectors.tradePrice.decimal); if (!unit || (unit.textContent !== '¥' && unit.textContent !== '¥') || !integer) return false; let priceStr = integer.textContent; if (decimal && point) { priceStr += `${point.textContent}${decimal.textContent}`; } const rmb = parseFloat(priceStr); if (isNaN(rmb)) return false; const ntd = convertPrice(rmb); const [newInteger, newDecimal] = ntd.toString().split('.'); unit.textContent = '$'; integer.textContent = newInteger; integer.dataset.originalRmb = rmb; integer.dataset.convertedNtd = ntd; if (decimal && point) { point.textContent = '.'; decimal.textContent = newDecimal || '00'; decimal.dataset.originalRmb = rmb; decimal.dataset.convertedNtd = ntd; } log(`Converted ¥${rmb} to $${ntd} in trade-price`); return true; }; const updateTradePriceElement = (container) => { const unit = container.querySelector(selectors.tradePrice.unit); const integer = container.querySelector(selectors.tradePrice.integer); const point = container.querySelector(selectors.tradePrice.point); const decimal = container.querySelector(selectors.tradePrice.decimal); if (!unit || !integer) return false; let currentPriceStr = integer.textContent; if (decimal && point) { currentPriceStr += `.${decimal.textContent}`; } const currentNum = parseFloat(currentPriceStr); if (isNaN(currentNum)) return false; if (integer.dataset.convertedNtd) { const expectedNtd = parseFloat(integer.dataset.convertedNtd); if (Math.abs(currentNum - expectedNtd) < 0.01) return false; } const rmb = currentNum; const ntd = convertPrice(rmb); const [newInteger, newDecimal] = ntd.toString().split('.'); unit.textContent = '$'; integer.textContent = newInteger; integer.dataset.originalRmb = rmb; integer.dataset.convertedNtd = ntd; if (decimal && point) { point.textContent = '.'; decimal.textContent = newDecimal || '00'; decimal.dataset.originalRmb = rmb; decimal.dataset.convertedNtd = ntd; } log(`Updated ¥${rmb} to $${ntd} in trade-price`); return true; }; const setupTradePriceObserver = () => { const tradeContainers = document.querySelectorAll(selectors.tradePrice.container); tradeContainers.forEach(container => { const unit = container.querySelector(selectors.tradePrice.unit); const integer = container.querySelector(selectors.tradePrice.integer); const decimal = container.querySelector(selectors.tradePrice.decimal); const observerConfig = { childList: true, characterData: true, subtree: true }; const handleChange = () => { if (!integer) return; if (unit.textContent === '¥' || unit.textContent === '¥') { processTradePriceElement(container); } else { updateTradePriceElement(container); } }; if (integer) { new MutationObserver(handleChange).observe(integer, observerConfig); } if (decimal) { new MutationObserver(handleChange).observe(decimal, observerConfig); } if (unit) { new MutationObserver(handleChange).observe(unit, observerConfig); } }); }; const convertPrices = () => { let convertedCount = 0; document.querySelectorAll(selectors.rightItem.unit).forEach(unit => { if (processPriceElement(unit, unit.nextElementSibling, 'right-item')) { convertedCount++; } }); document.querySelectorAll(selectors.priceWrapper.unit).forEach(unit => { if (processPriceElement(unit, unit.nextElementSibling, 'price-wrapper')) { convertedCount++; } }); document.querySelectorAll(selectors.genericWrapper.unit).forEach(unit => { const wrapper = unit.nextElementSibling; if (wrapper?.matches(selectors.genericWrapper.wrapper)) { const value = wrapper.querySelector(selectors.genericWrapper.value); if (processPriceElement(unit, value, 'generic-wrapper')) { convertedCount++; } } }); document.querySelectorAll(selectors.highlightPrice.container).forEach(container => { const unit = container.querySelector(selectors.highlightPrice.unit); const value = container.querySelector(selectors.highlightPrice.value); if (processPriceElement(unit, value, 'highlight-price')) { convertedCount++; } }); document.querySelectorAll(selectors.businessEntry.unit).forEach(unit => { const value = unit.nextElementSibling; if (processPriceElement(unit, value, 'business-entry')) { convertedCount++; } }); document.querySelectorAll(selectors.tradePrice.container).forEach(container => { if (processTradePriceElement(container)) { convertedCount++; } }); document.querySelectorAll(selectors.priceWrap.container).forEach(container => { const wrapper = container.querySelector(selectors.priceWrap.wrapper); const unit = wrapper?.querySelector(selectors.priceWrap.unit); if (unit && (unit.textContent === '¥' || unit.textContent === '¥')) { const priceTextNode = wrapper?.childNodes[1]; if (priceTextNode?.nodeType === Node.TEXT_NODE) { const priceText = priceTextNode.textContent.trim(); const priceMatch = priceText.match(/^(\d+(\.\d+)?)$/); if (priceMatch) { const rmb = parseFloat(priceMatch[1]); const ntd = convertPrice(rmb); unit.textContent = '$'; priceTextNode.textContent = ntd; wrapper.dataset.originalRmb = rmb; wrapper.dataset.convertedNtd = ntd; log(`Converted ¥${rmb} to $${ntd} in price-wrap`); convertedCount++; } } } }); log(`Converted ${convertedCount} prices`); return convertedCount; }; try { exchangeRate = await fetchExchangeRate(); log('Exchange rate:', exchangeRate); } catch (error) { log('Using default rate:', exchangeRate, error); } convertPrices(); setupTradePriceObserver(); let timeoutId; const debouncedConvert = () => { clearTimeout(timeoutId); timeoutId = setTimeout(() => { log('Updating prices...'); convertPrices(); setupTradePriceObserver(); }, 500); }; new MutationObserver(debouncedConvert).observe(document.body, { childList: true, subtree: true }); setInterval(debouncedConvert, 2000); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址