案例 https://www.minihello.com/tools/gold-price/,下面是代码:
HTML部分
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>实时金价</title>
<style>
body {
font-family: 'Arial', sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f5f5f5;
}
.container {
text-align: center;
background-color: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
max-width: 500px;
width: 90%;
}
h1 {
color: #333;
margin-bottom: 20px;
}
h2 {
color: #333;
margin-bottom: 20px;
}
.api-time {
color: #666;
font-size: 0.9rem;
margin-bottom: 15px;
font-style: italic;
}
.price-container {
margin: 20px 0;
padding: 20px;
background-color: #f9f9f9;
border-radius: 10px;
border-left: 4px solid #ffd700;
}
.price-section {
margin: 15px 0;
padding: 15px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.currency-label {
font-size: 1.2rem;
font-weight: bold;
color: #333;
margin-bottom: 10px;
text-align: center;
}
.price {
font-size: 2.2rem;
font-weight: bold;
margin: 10px 0;
text-align: center;
}
.price-usd {
font-size: 2.2rem;
font-weight: bold;
margin: 10px 0;
text-align: center;
}
.price-up {
color: #ff0000;
/* 红色 - 上涨 */
}
.price-down {
color: #4caf50;
/* 绿色 - 下跌 */
}
.price-neutral {
color: #333;
/* 黑色 - 持平 */
}
.price-details {
margin-top: 15px;
}
.detail-item {
display: flex;
justify-content: space-between;
margin: 8px 0;
padding: 5px 0;
border-bottom: 1px solid #eee;
}
.detail-item:last-child {
border-bottom: none;
}
.detail-label {
font-weight: bold;
color: #666;
}
.detail-value {
font-weight: bold;
color: #333;
}
.update-time {
color: #666;
font-size: 0.9rem;
}
.price-change {
font-size: 1.2rem;
font-weight: bold;
margin: 5px 0;
}
</style>
</head>
<body>
<div class="container">
<h2>实时金价</h2>
<div class="api-time" id="api-time">--</div>
<div class="price-container">
<div class="price-section">
<div class="currency-label">人民币/克</div>
<div class="price" id="price-display">加载中...</div>
<div class="price-details">
<div class="detail-item">
<span class="detail-label">当前价格:</span>
<span class="detail-value" id="current-price-cny">--</span>
</div>
<div class="detail-item">
<span class="detail-label">今日涨幅:</span>
<span class="detail-value" id="change-cny">--</span>
</div>
<div class="detail-item">
<span class="detail-label">昨日收盘:</span>
<span class="detail-value" id="close-price-cny">--</span>
</div>
</div>
</div>
<div class="price-section">
<div class="currency-label">美元/盎司</div>
<div class="price-usd" id="price-usd-display"></div>
<div class="price-details">
<div class="detail-item">
<span class="detail-label">当前价格:</span>
<span class="detail-value" id="current-price-usd">--</span>
</div>
<div class="detail-item">
<span class="detail-label">今日涨幅:</span>
<span class="detail-value" id="change-usd">--</span>
</div>
<div class="detail-item">
<span class="detail-label">昨日收盘:</span>
<span class="detail-value" id="close-price-usd">--</span>
</div>
</div>
</div>
<div class="price-change" id="price-change"></div>
</div>
<div class="update-time" id="update-time">更新时间: --</div>
<div id="status-message" style="margin-top: 10px; color: #666;">正在连接服务器...</div>
</div>
<script src="app.js"></script>
</body>
</html>
JS部分app.js
// 黄金价格实时监控应用
// 页面元素
const priceDisplay = document.getElementById('price-display');
const priceUsdDisplay = document.getElementById('price-usd-display');
const priceChangeElement = document.getElementById('price-change');
const updateTimeElement = document.getElementById('update-time');
const statusMessage = document.getElementById('status-message');
const apiTimeElement = document.getElementById('api-time');
// 详细价格信息元素
const currentPriceCny = document.getElementById('current-price-cny');
const changeCny = document.getElementById('change-cny');
const closePriceCny = document.getElementById('close-price-cny');
const currentPriceUsd = document.getElementById('current-price-usd');
const changeUsd = document.getElementById('change-usd');
const closePriceUsd = document.getElementById('close-price-usd');
// 配置
const REFRESH_INTERVAL = 5000; // 10秒刷新一次
// API URLs
const GOLD_API_URL_USD = 'https://data-asg.goldprice.org/dbXRates/USD'; // 美元黄金价格API
const GOLD_API_URL_CNY = 'https://data-asg.goldprice.org/dbXRates/CNY'; // 人民币黄金价格API
// 备注: 其他备用API已被移除,因为它们不可用或需要API密钥
const USD_TO_RMB_API_URL = 'https://api.exchangerate-api.com/v4/latest/USD'; // 汇率API
const BACKUP_USD_TO_RMB_API_URL = 'https://open.er-api.com/v6/latest/USD'; // 备用汇率API
// 状态变量
let lastUsdToRmbRate = 7.2; // 默认汇率,以防API失败
let isFirstLoad = true;
let lastGoldPriceUsd = null; // 上次获取的黄金价格(美元/盎司)
let lastGoldPriceCny = null; // 上次获取的黄金价格(人民币/盎司)
let lastPriceChangePercent = null; // 上次获取的涨跌幅
let debugInfo = {}; // 用于存储调试信息
let lastSuccessfulData = null; // 存储最后一次成功获取的数据,用于API失败时显示
// 格式化价格显示
function formatPrice(price) {
return price.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, '');
}
// 格式化API时间为24小时制中文格式
function formatApiTime(apiTimeString) {
try {
console.log('原始API时间字符串:', apiTimeString);
// 使用更简单的方法,避免复杂的正则表达式
// 先尝试简单的字符串分割方法
if (apiTimeString && typeof apiTimeString === 'string') {
// 方法1: 使用简单的字符串操作
const parts = apiTimeString.split(' ');
if (parts.length >= 6) {
const month = parts[0];
const day = parts[1].replace(/[^\d]/g, ''); // 移除st, nd, rd, th
const year = parts[2].replace(',', '');
const timePart = parts[3];
const ampm = parts[4];
if (timePart && timePart.includes(':')) {
const timeParts = timePart.split(':');
if (timeParts.length >= 3) {
const hour = timeParts[0];
const minute = timeParts[1];
const second = timeParts[2];
console.log('解析结果:', { month, day, year, hour, minute, second, ampm });
return formatTimeComponents(month, day, year, hour, minute, second, ampm);
}
}
}
// 方法2: 如果方法1失败,尝试正则表达式
console.log('尝试正则表达式解析');
const timeMatch = apiTimeString.match(/(\w+)\s+(\d+)(?:st|nd|rd|th)?\s+(\d+),\s+(\d+):(\d+):(\d+)\s+(am|pm)/i);
if (timeMatch) {
console.log('正则匹配成功:', timeMatch);
const [, month, day, year, hour, minute, second, ampm] = timeMatch;
return formatTimeComponents(month, day, year, hour, minute, second, ampm);
}
}
console.log('所有解析方法都失败,返回原始字符串');
return apiTimeString;
} catch (error) {
console.error('时间格式转换失败:', error);
return apiTimeString;
}
}
// 格式化时间组件的辅助函数
function formatTimeComponents(month, day, year, hour, minute, second, ampm) {
try {
console.log('格式化时间组件:', { month, day, year, hour, minute, second, ampm });
// 月份映射
const monthMap = {
'Jan': '1月', 'Feb': '2月', 'Mar': '3月', 'Apr': '4月',
'May': '5月', 'Jun': '6月', 'Jul': '7月', 'Aug': '8月',
'Sep': '9月', 'Oct': '10月', 'Nov': '11月', 'Dec': '12月'
};
// 转换为24小时制 - 使用更兼容的方法
let hour24 = parseInt(hour, 10);
const ampmLower = ampm ? ampm.toLowerCase() : '';
if (ampmLower === 'pm' && hour24 !== 12) {
hour24 += 12;
} else if (ampmLower === 'am' && hour24 === 12) {
hour24 = 0;
}
// 格式化为中文24小时制 - 使用更兼容的方法
const chineseMonth = monthMap[month] || month;
// 使用更兼容的字符串填充方法
const formattedHour = hour24 < 10 ? '0' + hour24 : hour24.toString();
const formattedMinute = minute.length === 1 ? '0' + minute : minute;
const formattedSecond = second.length === 1 ? '0' + second : second;
const result = year + '年' + chineseMonth + day + '日 ' + formattedHour + ':' + formattedMinute + ':' + formattedSecond + ' 纽约时间';
console.log('格式化结果:', result);
return result;
} catch (error) {
console.error('时间组件格式化失败:', error);
return '时间解析失败';
}
}
// 更新时间显示
function updateTimeDisplay() {
const now = new Date();
const timeString = now.toLocaleTimeString('zh-CN', { hour12: false });
const dateString = now.toLocaleDateString('zh-CN');
updateTimeElement.textContent = `更新时间: ${dateString} ${timeString}`;
}
// 获取美元兑人民币汇率
async function getUsdToRmbRate() {
try {
statusMessage.textContent = '正在获取最新汇率...';
// 添加时间戳参数避免缓存
const timestamp = Date.now();
const rateApiUrlWithTimestamp = `${USD_TO_RMB_API_URL}?t=${timestamp}`;
// 尝试主要API
try {
const response = await fetch(rateApiUrlWithTimestamp, {
mode: 'cors',
headers: {
'Accept': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
if (data && data.rates && data.rates.CNY) {
lastUsdToRmbRate = data.rates.CNY;
debugInfo.rateSource = 'primary';
debugInfo.rate = lastUsdToRmbRate;
return lastUsdToRmbRate;
}
}
throw new Error('主要汇率API返回无效数据');
} catch (primaryError) {
console.warn('主要汇率API失败,尝试备用API', primaryError);
// 尝试备用API
const backupApiUrlWithTimestamp = `${BACKUP_USD_TO_RMB_API_URL}?t=${timestamp}`;
const backupResponse = await fetch(backupApiUrlWithTimestamp);
if (backupResponse.ok) {
const backupData = await backupResponse.json();
if (backupData && backupData.rates && backupData.rates.CNY) {
lastUsdToRmbRate = backupData.rates.CNY;
debugInfo.rateSource = 'backup';
debugInfo.rate = lastUsdToRmbRate;
return lastUsdToRmbRate;
}
}
throw new Error('备用汇率API也失败了');
}
} catch (error) {
console.error('获取汇率失败', error);
statusMessage.textContent = '汇率API暂时不可用,使用默认汇率';
debugInfo.rateSource = 'default';
debugInfo.rate = lastUsdToRmbRate;
return lastUsdToRmbRate; // 使用上次成功的汇率或默认值
}
}
// 获取美元黄金价格数据
async function getGoldPriceUsd() {
try {
statusMessage.textContent = '正在获取最新美元黄金价格...';
// 添加时间戳参数避免缓存
const timestamp = Date.now();
const goldApiUrlWithTimestamp = `${GOLD_API_URL_USD}?t=${timestamp}`;
const response = await fetch(goldApiUrlWithTimestamp, {
mode: 'cors',
headers: {
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`美元黄金价格API返回错误: ${response.status}`);
}
const data = await response.json();
if (!data || !data.items || !data.items[0] || typeof data.items[0].xauPrice !== 'number') {
throw new Error('美元黄金价格API返回的数据格式无效');
}
return data;
} catch (error) {
console.error('获取美元黄金价格失败', error);
throw error;
}
}
// 获取人民币黄金价格数据
async function getGoldPriceCny() {
try {
statusMessage.textContent = '正在获取最新人民币黄金价格...';
// 添加时间戳参数避免缓存
const timestamp = Date.now();
const goldApiUrlWithTimestamp = `${GOLD_API_URL_CNY}?t=${timestamp}`;
const response = await fetch(goldApiUrlWithTimestamp, {
mode: 'cors',
headers: {
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`人民币黄金价格API返回错误: ${response.status}`);
}
const data = await response.json();
if (!data || !data.items || !data.items[0] || typeof data.items[0].xauPrice !== 'number') {
throw new Error('人民币黄金价格API返回的数据格式无效');
}
// 存储成功获取的数据
lastSuccessfulData = data;
return data;
} catch (error) {
console.error('获取人民币黄金价格失败', error);
statusMessage.textContent = '获取数据失败,显示缓存数据';
// 如果有缓存数据,则使用缓存数据
if (lastSuccessfulData) {
return lastSuccessfulData;
}
throw error; // 如果没有缓存数据,则继续抛出错误
}
}
// 更新页面显示
async function updateDisplay() {
try {
// 并行获取美元和人民币黄金价格数据
const [goldDataUsd, goldDataCny] = await Promise.all([
getGoldPriceUsd(),
getGoldPriceCny()
]);
// 提取黄金价格数据
const goldPriceUsd = goldDataUsd.items[0].xauPrice; // 美元/盎司
const goldPriceCny = goldDataCny.items[0].xauPrice; // 人民币/盎司
// 提取价格变化数据
const changeUsdAmount = goldDataUsd.items[0].chgXau || 0; // 美元变化金额
const changeCnyAmount = goldDataCny.items[0].chgXau || 0; // 人民币变化金额
const changeUsdPercent = goldDataUsd.items[0].pcXau || 0; // 美元变化百分比
const changeCnyPercent = goldDataCny.items[0].pcXau || 0; // 人民币变化百分比
// 提取昨日收盘价
const closePriceUsdValue = goldDataUsd.items[0].xauClose || 0; // 美元昨日收盘
const closePriceCnyValue = goldDataCny.items[0].xauClose || 0; // 人民币昨日收盘
// 计算人民币每克价格
// 1盎司 = 31.1035克
const goldPriceRmbPerGram = goldPriceCny / 31.1035;
const closePriceRmbPerGram = closePriceCnyValue / 31.1035;
const changeRmbPerGram = changeCnyAmount / 31.1035;
// 计算相对于上次刷新的价格变化
let refreshChangeUsdPercent = 0;
let refreshChangeCnyPercent = 0;
if (lastGoldPriceUsd !== null && lastGoldPriceCny !== null) {
// 计算美元价格变化百分比
refreshChangeUsdPercent = ((goldPriceUsd - lastGoldPriceUsd) / lastGoldPriceUsd) * 100;
// 计算人民币价格变化百分比
refreshChangeCnyPercent = ((goldPriceCny - lastGoldPriceCny) / lastGoldPriceCny) * 100;
}
// 更新上次价格记录
lastGoldPriceUsd = goldPriceUsd;
lastGoldPriceCny = goldPriceCny;
lastPriceChangePercent = refreshChangeUsdPercent;
// 更新主要价格显示
priceDisplay.textContent = `${formatPrice(goldPriceRmbPerGram)}/克`;
priceUsdDisplay.textContent = `${formatPrice(goldPriceUsd)}/盎司`;
// 根据与昨日收盘价对比设置价格颜色
// 人民币价格颜色
const cnyPriceChange = goldPriceCny - closePriceCnyValue;
if (cnyPriceChange > 0) {
priceDisplay.className = 'price price-up';
} else if (cnyPriceChange < 0) {
priceDisplay.className = 'price price-down';
} else {
priceDisplay.className = 'price price-neutral';
}
// 美元价格颜色
const usdPriceChange = goldPriceUsd - closePriceUsdValue;
if (usdPriceChange > 0) {
priceUsdDisplay.className = 'price-usd price-up';
} else if (usdPriceChange < 0) {
priceUsdDisplay.className = 'price-usd price-down';
} else {
priceUsdDisplay.className = 'price-usd price-neutral';
}
// 更新详细价格信息 - 人民币
currentPriceCny.textContent = `${formatPrice(goldPriceRmbPerGram)}/克`;
const changeCnyPrefix = changeRmbPerGram >= 0 ? '+' : '';
const changeCnyClass = changeRmbPerGram >= 0 ? 'positive' : 'negative';
changeCny.innerHTML = `${changeCnyPrefix}${formatPrice(Math.abs(changeRmbPerGram))}/克 <span class="${changeCnyClass}">(${changeCnyPrefix}${changeCnyPercent.toFixed(2)}%)</span>`;
closePriceCny.textContent = `${formatPrice(closePriceRmbPerGram)}/克`;
// 更新详细价格信息 - 美元
currentPriceUsd.textContent = `${formatPrice(goldPriceUsd)}/盎司`;
const changeUsdPrefix = changeUsdAmount >= 0 ? '+' : '';
const changeUsdClass = changeUsdAmount >= 0 ? 'positive' : 'negative';
changeUsd.innerHTML = `${changeUsdPrefix}${formatPrice(Math.abs(changeUsdAmount))}/盎司 <span class="${changeUsdClass}">(${changeUsdPrefix}${changeUsdPercent.toFixed(2)}%)</span>`;
closePriceUsd.textContent = `${formatPrice(closePriceUsdValue)}/盎司`;
// 更新总体价格变化显示 - 显示相对于上次刷新的变化
const refreshChangePrefix = refreshChangeUsdPercent >= 0 ? '+' : '';
const refreshChangeClass = refreshChangeUsdPercent >= 0 ? 'positive' : 'negative';
priceChangeElement.textContent = `${refreshChangePrefix}${refreshChangeUsdPercent.toFixed(2)}%`;
priceChangeElement.className = `price-change ${refreshChangeClass}`;
// 更新API时间显示
const apiTime = goldDataUsd.date || goldDataCny.date || '--';
console.log('获取到的API时间:', apiTime);
let formattedApiTime = '--';
if (apiTime !== '--') {
formattedApiTime = formatApiTime(apiTime);
console.log('格式化后的时间:', formattedApiTime);
// Safari特殊处理:如果格式化失败,显示原始时间
if (formattedApiTime === apiTime && apiTime !== '--') {
console.log('Safari检测到格式化失败,显示原始时间');
formattedApiTime = apiTime;
}
}
apiTimeElement.textContent = formattedApiTime;
// 更新时间显示
updateTimeDisplay();
// 更新状态消息
statusMessage.textContent = '数据已更新';
// 第一次加载完成
isFirstLoad = false;
} catch (error) {
console.error('更新显示失败', error);
if (isFirstLoad) {
// 如果是第一次加载就失败,显示错误消息
priceDisplay.textContent = '数据加载失败';
priceDisplay.className = 'price price-neutral';
priceUsdDisplay.textContent = '数据加载失败';
priceUsdDisplay.className = 'price-usd price-neutral';
currentPriceCny.textContent = '--';
changeCny.textContent = '--';
closePriceCny.textContent = '--';
currentPriceUsd.textContent = '--';
changeUsd.textContent = '--';
closePriceUsd.textContent = '--';
apiTimeElement.textContent = '--';
statusMessage.textContent = '无法连接到服务器,请稍后再试';
} else {
// 如果之前有成功加载过,则保持上次的数据,只更新状态消息
statusMessage.textContent = '更新失败,显示的是上次成功获取的数据';
// 仍然更新时间,表明我们尝试了更新
updateTimeDisplay();
}
}
}
// 页面加载完成后立即更新一次
document.addEventListener('DOMContentLoaded', () => {
updateDisplay();
// 设置定时更新
setInterval(updateDisplay, REFRESH_INTERVAL);
});
// 添加CSS样式
const styleElement = document.createElement('style');
styleElement.textContent = `
.positive {
color: #4caf50;
}
.negative {
color: #f44336;
}
`;
document.head.appendChild(styleElement);
油猴脚本(右下角浮框显示)
// ==UserScript==
// @name Gold Price
// @namespace http://tampermonkey.net/
// @version 1.0.1
// @description 实时显示人民币和美元金价,包含当天最高最低值
// @author You
// @match *://*/*
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
// 配置
const REFRESH_INTERVAL = 5000; // 5秒刷新一次
// API URLs
const GOLD_API_URL_USD = 'https://data-asg.goldprice.org/dbXRates/USD';
const GOLD_API_URL_CNY = 'https://data-asg.goldprice.org/dbXRates/CNY';
// 跨域存储辅助函数 - 使用 GM API 实现跨域共享
function getStorage(key, defaultValue) {
try {
// 优先使用 GM API(跨域),如果不支持则回退到 localStorage
if (typeof GM_getValue !== 'undefined' && typeof GM_getValue === 'function') {
const value = GM_getValue(key);
return value !== undefined ? value : defaultValue;
}
const value = localStorage.getItem(key);
return value ? JSON.parse(value) : defaultValue;
} catch (e) {
return defaultValue;
}
}
function setStorage(key, value) {
try {
// 优先使用 GM API(跨域),如果不支持则回退到 localStorage
if (typeof GM_setValue !== 'undefined' && typeof GM_setValue === 'function') {
GM_setValue(key, value);
return;
}
localStorage.setItem(key, JSON.stringify(value));
} catch (e) {
console.error('保存失败:', e);
}
}
// 全局变量
let priceHistory = getStorage('goldPriceHistory', []);
let lastUpdate = getStorage('goldPriceLastUpdate', null);
let today = new Date().toDateString();
let currentPrices = { cny: 0, usd: 0 }; // 存储当前价格
// 创建悬浮框容器
function createFloatBox() {
const box = document.createElement('div');
box.id = 'gold-price-float-box';
box.innerHTML = `
<div class="gold-price-header">
<span class="gold-price-title">Realtime</span>
<div class="gold-price-buttons">
<button class="gold-price-toggle" id="toggle-button" title="Expand/Collapse">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
</button>
<button class="gold-price-close" title="Close">×</button>
</div>
</div>
<div class="gold-price-content">
<div class="gold-price-row">
<span class="gold-label">CNY:</span>
<div style="display: flex; align-items: center; gap: 6px;">
<span class="gold-value" id="cny-price">loading...</span>
<span class="gold-change" id="cny-change">--</span>
</div>
</div>
<div class="gold-price-row">
<span class="gold-label">USD:</span>
<div style="display: flex; align-items: center; gap: 6px;">
<span class="gold-value" id="usd-price">loading...</span>
<span class="gold-change" id="usd-change">--</span>
</div>
</div>
<div class="gold-price-collapsible">
<div class="gold-price-separator"></div>
<div class="gold-price-row">
<span class="gold-label">High:</span>
<div>
<span class="gold-value" id="today-high-cny">--</span>
<span class="gold-time" id="today-high-time">--</span>
</div>
</div>
<div class="gold-price-row">
<span class="gold-label">Low:</span>
<div>
<span class="gold-value" id="today-low-cny">--</span>
<span class="gold-time" id="today-low-time">--</span>
</div>
</div>
<div class="gold-price-separator"></div>
<div class="gold-price-row">
<span class="gold-label">Update:</span>
<span class="gold-value" id="update-time">--</span>
</div>
</div>
</div>
`;
// 添加样式
const style = document.createElement('style');
style.textContent = `
#gold-price-float-box {
position: fixed;
bottom: 20px;
right: 20px;
width: 210px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
z-index: 999999;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
color: white;
overflow: hidden;
transition: all 0.3s ease;
opacity: 0.85;
}
span#update-time {
font-size: 14px;
font-weight: unset;
}
#gold-price-float-box:hover {
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
transform: translateY(-2px);
}
.gold-price-header {
background: rgba(255, 255, 255, 0.15);
padding: 12px 15px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
.gold-price-title {
font-weight: bold;
font-size: 16px;
letter-spacing: 0.5px;
}
.gold-price-buttons {
display: flex;
gap: 2px;
}
.gold-price-toggle,
.gold-price-close {
background: none;
border: none;
color: white;
font-size: 18px;
cursor: pointer;
padding: 2px;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: all 0.2s;
}
.gold-price-toggle {
font-size: 0;
}
.gold-price-toggle svg {
width: 16px;
height: 16px;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.gold-price-close {
font-size: 18px;
}
.gold-price-toggle:hover,
.gold-price-close:hover {
background: rgba(255, 255, 255, 0.15);
transform: scale(1.05);
}
#gold-price-float-box.collapsed .gold-price-toggle svg {
transform: rotate(180deg);
}
.gold-price-content {
padding: 8px;
}
.gold-price-row {
display: flex;
justify-content: space-between;
margin: 2px 0;
align-items: center;
}
.gold-price-row.small {
margin: 8px 0;
font-size: 12px;
}
.gold-label {
font-size: 14px;
opacity: 0.9;
font-weight: 500;
}
.gold-value {
font-size: 16px;
font-weight: bold;
text-align: right;
}
.gold-change {
font-size: 13px;
font-weight: bold;
opacity: 0.9;
}
.gold-change.positive {
color: #ffeb3b;
}
.gold-change.negative {
color: #81c784;
}
.gold-time {
font-size: 10px;
font-weight: 400;
opacity: 0.7;
text-align: right;
margin-top: 2px;
}
.gold-price-row.small .gold-label,
.gold-price-row.small .gold-value {
font-size: 12px;
}
.gold-price-separator {
height: 1px;
background: rgba(255, 255, 255, 0.2);
margin: 6px 0;
}
.price-up {
color: #ffeb3b !important;
text-shadow: 0 0 8px rgba(255, 235, 59, 0.5);
}
.price-down {
color: #81c784 !important;
}
.gold-price-collapsible {
max-height: 500px;
overflow: hidden;
transition: max-height 0.3s ease;
}
#gold-price-float-box.collapsed .gold-price-collapsible {
max-height: 0;
transition: max-height 0.3s ease;
}
#gold-price-float-box.collapsed {
width: 180px;
opacity: 0.85;
}
#gold-price-float-box.collapsed .gold-price-content {
display: none;
}
#gold-price-float-box.collapsed .gold-price-title {
font-size: 12px;
font-weight: 500;
display: flex;
align-items: center;
gap: 4px;
}
#gold-price-float-box.collapsed .gold-price-header {
padding: 6px 8px;
}
`;
document.head.appendChild(style);
document.body.appendChild(box);
// 添加折叠/展开功能
const toggleButton = box.querySelector('#toggle-button');
const closeButton = box.querySelector('.gold-price-close');
// 默认折叠状态
box.classList.add('collapsed');
// 折叠/展开按钮事件
toggleButton.addEventListener('click', () => {
const isCurrentlyCollapsed = box.classList.contains('collapsed');
const titleElement = box.querySelector('.gold-price-title');
if (isCurrentlyCollapsed) {
// 展开
box.classList.remove('collapsed');
if (titleElement) titleElement.textContent = 'Realtime';
} else {
// 收缩
box.classList.add('collapsed');
// 在标题中显示当前价格(使用紧凑格式)
if (titleElement && currentPrices.cny > 0 && currentPrices.usd > 0) {
const cnyCompact = currentPrices.cny.toFixed(2);
const usdCompact = currentPrices.usd.toFixed(2);
titleElement.textContent = `¥${cnyCompact}/$${usdCompact}`;
}
}
});
// 关闭按钮事件
closeButton.addEventListener('click', () => {
box.style.display = 'none';
});
return box;
}
// 格式化价格
function formatPrice(price) {
return price.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, '');
}
// 格式化时间
function formatTime() {
const now = new Date();
return now.toLocaleTimeString('zh-CN', { hour12: false });
}
// 格式化完整时间(包含日期)
function formatFullTime() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// 更新当天最高最低值
function updateDayHighLow(cnyPricePerGram, usdPrice) {
// 检查是否是新的一天
const currentDate = new Date().toDateString();
if (currentDate !== today) {
// 清除旧数据
priceHistory = [];
today = currentDate;
}
const currentTime = Date.now();
// 添加当前价格到历史记录
priceHistory.push({
timestamp: currentTime,
cny: cnyPricePerGram,
usd: usdPrice
});
// 只保留当天的数据
const todayStart = new Date().setHours(0, 0, 0, 0);
priceHistory = priceHistory.filter(p => p.timestamp >= todayStart);
// 计算最高最低值及其对应的时间
const cnyPrices = priceHistory.map(p => p.cny).filter(p => p > 0);
let high = 0, low = 0, highTime = 0, lowTime = 0;
if (cnyPrices.length > 0) {
high = Math.max(...cnyPrices);
low = Math.min(...cnyPrices);
// 找到最高最低值对应的时间
const highRecord = priceHistory.find(p => p.cny === high);
const lowRecord = priceHistory.find(p => p.cny === low);
highTime = highRecord ? highRecord.timestamp : currentTime;
lowTime = lowRecord ? lowRecord.timestamp : currentTime;
}
// 保存到本地存储
setStorage('goldPriceHistory', priceHistory);
setStorage('goldPriceLastUpdate', today);
setStorage('goldPriceHighLow', {
high,
low,
highTime,
lowTime,
date: today
});
return { high, low, highTime, lowTime };
}
// 获取金价数据
async function fetchGoldPrices() {
try {
const timestamp = Date.now();
const [usdResponse, cnyResponse] = await Promise.all([
fetch(`${GOLD_API_URL_USD}?t=${timestamp}`),
fetch(`${GOLD_API_URL_CNY}?t=${timestamp}`)
]);
if (!usdResponse.ok || !cnyResponse.ok) {
throw new Error('API请求失败');
}
const [usdData, cnyData] = await Promise.all([
usdResponse.json(),
cnyResponse.json()
]);
const goldPriceUsd = usdData.items[0].xauPrice;
const goldPriceCny = cnyData.items[0].xauPrice;
// 获取收盘价和涨跌幅(参考 app.js 的实现)
const closePriceUsd = usdData.items[0].xauClose || 0;
const closePriceCny = cnyData.items[0].xauClose || 0;
const changeUsdPercent = usdData.items[0].pcXau || 0; // 美元变化百分比
const changeCnyPercent = cnyData.items[0].pcXau || 0; // 人民币变化百分比
// 转换为每克(人民币)
// 1盎司 = 31.1035克
const goldPriceCnyPerGram = goldPriceCny / 31.1035;
const closePriceCnyPerGram = closePriceCny / 31.1035;
return {
cny: goldPriceCnyPerGram,
usd: goldPriceUsd,
closeCny: closePriceCnyPerGram,
closeUsd: closePriceUsd,
changeCny: changeCnyPercent,
changeUsd: changeUsdPercent
};
} catch (error) {
console.error('获取金价失败:', error);
return null;
}
}
// 更新显示
async function updateDisplay() {
const prices = await fetchGoldPrices();
const cnyElement = document.getElementById('cny-price');
const usdElement = document.getElementById('usd-price');
if (!prices || !cnyElement || !usdElement) {
if (cnyElement) cnyElement.textContent = '数据加载失败';
if (usdElement) usdElement.textContent = '数据加载失败';
return;
}
// 保存当前价格到全局变量
currentPrices = prices;
// 更新价格显示
// 获取上次价格来判断涨跌
const lastPrices = priceHistory.length > 0 ? priceHistory[priceHistory.length - 1] : null;
if (lastPrices) {
// CNY:根据涨跌添加动画效果
if (prices.cny > lastPrices.cny) {
cnyElement.classList.add('price-up');
cnyElement.classList.remove('price-down');
} else if (prices.cny < lastPrices.cny) {
cnyElement.classList.add('price-down');
cnyElement.classList.remove('price-up');
}
// USD:根据涨跌添加动画效果
if (prices.usd > lastPrices.usd) {
usdElement.classList.add('price-up');
usdElement.classList.remove('price-down');
} else if (prices.usd < lastPrices.usd) {
usdElement.classList.add('price-down');
usdElement.classList.remove('price-up');
}
}
cnyElement.textContent = '¥' + formatPrice(prices.cny) + '/g';
usdElement.textContent = '$' + formatPrice(prices.usd) + '/oz';
// 更新涨跌幅显示
const cnyChangeElement = document.getElementById('cny-change');
const usdChangeElement = document.getElementById('usd-change');
if (cnyChangeElement && prices.changeCny !== undefined) {
// changeCny 已经是百分比,直接格式化
const changePercent = prices.changeCny.toFixed(2);
const sign = prices.changeCny > 0 ? '+' : '';
cnyChangeElement.textContent = `${sign}${changePercent}%`;
cnyChangeElement.classList.remove('positive', 'negative');
if (prices.changeCny > 0) {
cnyChangeElement.classList.add('positive');
} else if (prices.changeCny < 0) {
cnyChangeElement.classList.add('negative');
}
}
if (usdChangeElement && prices.changeUsd !== undefined) {
// changeUsd 已经是百分比,直接格式化
const changePercent = prices.changeUsd.toFixed(2);
const sign = prices.changeUsd > 0 ? '+' : '';
usdChangeElement.textContent = `${sign}${changePercent}%`;
usdChangeElement.classList.remove('positive', 'negative');
if (prices.changeUsd > 0) {
usdChangeElement.classList.add('positive');
} else if (prices.changeUsd < 0) {
usdChangeElement.classList.add('negative');
}
}
// 检查是否折叠,如果是,在标题中显示价格
const floatBox = document.getElementById('gold-price-float-box');
const titleElement2 = document.querySelector('.gold-price-title');
if (floatBox && floatBox.classList.contains('collapsed') && titleElement2) {
// 折叠状态下使用更紧凑的格式(不带千位分隔符)
const cnyCompact = prices.cny.toFixed(2);
const usdCompact = prices.usd.toFixed(2);
titleElement2.textContent = `¥${cnyCompact}/$${usdCompact}`;
}
// 更新最高最低值
const { high, low, highTime, lowTime } = updateDayHighLow(prices.cny, prices.usd);
const highElement = document.getElementById('today-high-cny');
const lowElement = document.getElementById('today-low-cny');
const highTimeElement = document.getElementById('today-high-time');
const lowTimeElement = document.getElementById('today-low-time');
const updateTimeElement = document.getElementById('update-time');
// 检查当前价格是否创历史新高或新低
if (highElement) {
if (prices.cny === high) {
highElement.classList.add('price-up');
} else {
highElement.classList.remove('price-up');
}
highElement.textContent = '¥' + formatPrice(high) + 'g';
}
if (lowElement) {
if (prices.cny === low) {
lowElement.classList.add('price-down');
} else {
lowElement.classList.remove('price-down');
}
lowElement.textContent = '¥' + formatPrice(low) + 'g';
}
// 显示最高最低值的时间
if (highTimeElement && highTime > 0) {
const highDate = new Date(highTime);
// 如果同一天,只显示时间;否则显示日期和时间
const isSameDay = highDate.toDateString() === new Date().toDateString();
if (isSameDay) {
highTimeElement.textContent = highDate.toLocaleTimeString('zh-CN', { hour12: false });
} else {
highTimeElement.textContent = `${highDate.toLocaleDateString('zh-CN')} ${highDate.toLocaleTimeString('zh-CN', { hour12: false })}`;
}
}
if (lowTimeElement && lowTime > 0) {
const lowDate = new Date(lowTime);
// 如果同一天,只显示时间;否则显示日期和时间
const isSameDay = lowDate.toDateString() === new Date().toDateString();
if (isSameDay) {
lowTimeElement.textContent = lowDate.toLocaleTimeString('zh-CN', { hour12: false });
} else {
lowTimeElement.textContent = `${lowDate.toLocaleDateString('zh-CN')} ${lowDate.toLocaleTimeString('zh-CN', { hour12: false })}`;
}
}
// 更新时间(使用完整格式)
if (updateTimeElement) {
updateTimeElement.textContent = formatFullTime();
}
}
// 初始化
function init() {
// 等待DOM加载完成
if (document.body) {
const floatBox = createFloatBox();
// 立即更新一次
updateDisplay();
// 设置定时更新
setInterval(updateDisplay, REFRESH_INTERVAL);
} else {
setTimeout(init, 100);
}
}
// 开始初始化
init();
})();

