|
|
/**
|
|
|
* 股票信息区块JavaScript
|
|
|
* 处理腾讯股票API调用和数据显示
|
|
|
*/
|
|
|
|
|
|
(function($) {
|
|
|
'use strict';
|
|
|
|
|
|
// 股票信息管理器
|
|
|
window.stockInfoManager = {
|
|
|
// 配置选项
|
|
|
config: {
|
|
|
apiUrl: 'https://sqt.gtimg.cn/q=',
|
|
|
fallbackApiUrl: 'https://qt.gtimg.cn/q=', // 备用API
|
|
|
refreshInterval: 30000, // 30秒刷新一次
|
|
|
retryDelay: 5000, // 重试延迟5秒
|
|
|
maxRetries: 3,
|
|
|
useFallback: false // 是否使用备用API
|
|
|
},
|
|
|
|
|
|
// 当前状态
|
|
|
state: {
|
|
|
isLoading: false,
|
|
|
retryCount: 0,
|
|
|
refreshTimer: null,
|
|
|
lastUpdateTime: null
|
|
|
},
|
|
|
|
|
|
// 初始化
|
|
|
init: function() {
|
|
|
this.bindEvents();
|
|
|
this.loadAllStockData();
|
|
|
this.startAutoRefresh();
|
|
|
},
|
|
|
|
|
|
// 绑定事件
|
|
|
bindEvents: function() {
|
|
|
// 页面可见性变化时的处理
|
|
|
$(document).on('visibilitychange', this.handleVisibilityChange.bind(this));
|
|
|
|
|
|
// 窗口焦点变化时的处理
|
|
|
$(window).on('focus blur', this.handleFocusChange.bind(this));
|
|
|
},
|
|
|
|
|
|
// 加载所有股票数据
|
|
|
loadAllStockData: function() {
|
|
|
const self = this;
|
|
|
$('.stock-info-block').each(function() {
|
|
|
const $block = $(this);
|
|
|
const stockCode = $block.data('stock-code');
|
|
|
if (stockCode) {
|
|
|
self.loadStockData(stockCode, $block);
|
|
|
}
|
|
|
});
|
|
|
},
|
|
|
|
|
|
// 加载单个股票数据
|
|
|
loadStockData: function(stockCode, $block) {
|
|
|
const self = this;
|
|
|
|
|
|
if (self.state.isLoading) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
self.state.isLoading = true;
|
|
|
self.showLoading($block);
|
|
|
|
|
|
// 构建API URL
|
|
|
const apiUrl = (self.config.useFallback ? self.config.fallbackApiUrl : self.config.apiUrl) + stockCode;
|
|
|
|
|
|
// 使用JSONP方式调用API
|
|
|
$.ajax({
|
|
|
url: apiUrl,
|
|
|
method: 'GET',
|
|
|
dataType: 'script',
|
|
|
timeout: 10000,
|
|
|
success: function() {
|
|
|
self.handleApiSuccess(stockCode, $block);
|
|
|
},
|
|
|
error: function(xhr, status, error) {
|
|
|
// 如果主API失败且未使用备用API,尝试备用API
|
|
|
if (!self.config.useFallback && self.config.fallbackApiUrl) {
|
|
|
console.log('主API失败,尝试备用API...');
|
|
|
self.config.useFallback = true;
|
|
|
self.state.isLoading = false;
|
|
|
self.loadStockData(stockCode, $block);
|
|
|
return;
|
|
|
}
|
|
|
self.handleApiError(error, $block);
|
|
|
},
|
|
|
complete: function() {
|
|
|
self.state.isLoading = false;
|
|
|
}
|
|
|
});
|
|
|
},
|
|
|
|
|
|
// 处理API成功响应
|
|
|
handleApiSuccess: function(stockCode, $block) {
|
|
|
try {
|
|
|
// 腾讯API返回的数据格式: v_sz301046="股票名称~代码~当前价格~涨跌额~涨跌幅~..."
|
|
|
const varName = 'v_' + stockCode;
|
|
|
const stockDataString = window[varName];
|
|
|
|
|
|
if (!stockDataString) {
|
|
|
throw new Error('股票数据未找到');
|
|
|
}
|
|
|
|
|
|
const stockData = this.parseStockData(stockDataString);
|
|
|
this.updateStockDisplay(stockData, $block);
|
|
|
this.hideLoading($block);
|
|
|
this.state.retryCount = 0;
|
|
|
this.state.lastUpdateTime = new Date();
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error('解析股票数据失败:', error);
|
|
|
this.handleApiError(error.message, $block);
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 处理API错误
|
|
|
handleApiError: function(error, $block) {
|
|
|
console.error('股票数据加载失败:', error);
|
|
|
|
|
|
this.state.retryCount++;
|
|
|
|
|
|
if (this.state.retryCount < this.config.maxRetries) {
|
|
|
// 重试
|
|
|
setTimeout(() => {
|
|
|
const stockCode = $block.data('stock-code');
|
|
|
this.loadStockData(stockCode, $block);
|
|
|
}, this.config.retryDelay);
|
|
|
} else {
|
|
|
// 显示错误状态
|
|
|
this.showError($block);
|
|
|
this.state.retryCount = 0;
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 解析股票数据
|
|
|
parseStockData: function(dataString) {
|
|
|
const parts = dataString.split('~');
|
|
|
|
|
|
if (parts.length < 30) {
|
|
|
throw new Error('股票数据格式不正确');
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
name: parts[1], // 股票名称
|
|
|
code: parts[2], // 股票代码
|
|
|
currentPrice: parseFloat(parts[3]), // 当前价格
|
|
|
prevClose: parseFloat(parts[4]), // 昨收价
|
|
|
openPrice: parseFloat(parts[5]), // 今开价
|
|
|
volume: parseInt(parts[6]), // 成交量(手)
|
|
|
amount: parseFloat(parts[37]), // 成交额(万元)
|
|
|
highPrice: parseFloat(parts[33]), // 最高价
|
|
|
lowPrice: parseFloat(parts[34]), // 最低价
|
|
|
changeAmount: parseFloat(parts[31]), // 涨跌额
|
|
|
changePercent: parseFloat(parts[32]), // 涨跌幅
|
|
|
updateTime: parts[30] // 更新时间
|
|
|
};
|
|
|
},
|
|
|
|
|
|
// 更新股票显示
|
|
|
updateStockDisplay: function(data, $block) {
|
|
|
// 更新基本信息
|
|
|
$block.find('#current-price').text(data.currentPrice.toFixed(2)).addClass('updating');
|
|
|
$block.find('#volume-value').text(this.formatNumber(data.volume)).addClass('updating');
|
|
|
$block.find('#amount-value').text(this.formatNumber(data.amount)).addClass('updating');
|
|
|
|
|
|
// 更新涨跌信息
|
|
|
const changeValue = data.changeAmount > 0 ? '+' + data.changeAmount.toFixed(2) : data.changeAmount.toFixed(2);
|
|
|
const changePercent = data.changePercent > 0 ? '+' + data.changePercent.toFixed(2) + '%' : data.changePercent.toFixed(2) + '%';
|
|
|
|
|
|
$block.find('#price-change').text(changeValue);
|
|
|
$block.find('#change-percent').text(changePercent);
|
|
|
|
|
|
// 设置涨跌颜色
|
|
|
$block.removeClass('price-up price-down price-neutral');
|
|
|
if (data.changeAmount > 0) {
|
|
|
$block.addClass('price-up');
|
|
|
} else if (data.changeAmount < 0) {
|
|
|
$block.addClass('price-down');
|
|
|
} else {
|
|
|
$block.addClass('price-neutral');
|
|
|
}
|
|
|
|
|
|
// 更新详细数据
|
|
|
$block.find('#open-price').text(data.openPrice.toFixed(2));
|
|
|
$block.find('#prev-close').text(data.prevClose.toFixed(2));
|
|
|
$block.find('#high-price').text(data.highPrice.toFixed(2));
|
|
|
$block.find('#low-price').text(data.lowPrice.toFixed(2));
|
|
|
|
|
|
// 更新时间
|
|
|
$block.find('#update-time').text(this.formatUpdateTime(data.updateTime));
|
|
|
|
|
|
// 移除更新动画类
|
|
|
setTimeout(() => {
|
|
|
$block.find('.updating').removeClass('updating');
|
|
|
}, 300);
|
|
|
},
|
|
|
|
|
|
// 格式化数字显示
|
|
|
formatNumber: function(num) {
|
|
|
if (num >= 10000) {
|
|
|
return (num / 10000).toFixed(1) + '万';
|
|
|
} else if (num >= 1000) {
|
|
|
return (num / 1000).toFixed(1) + 'K';
|
|
|
}
|
|
|
return num.toString();
|
|
|
},
|
|
|
|
|
|
// 格式化更新时间
|
|
|
formatUpdateTime: function(timeString) {
|
|
|
if (!timeString || timeString.length < 14) {
|
|
|
return '数据更新中...';
|
|
|
}
|
|
|
|
|
|
// 格式: 20250926161424 -> 2025-09-26 16:14:24
|
|
|
const year = timeString.substr(0, 4);
|
|
|
const month = timeString.substr(4, 2);
|
|
|
const day = timeString.substr(6, 2);
|
|
|
const hour = timeString.substr(8, 2);
|
|
|
const minute = timeString.substr(10, 2);
|
|
|
const second = timeString.substr(12, 2);
|
|
|
|
|
|
return `${year}.${month}.${day} ${hour}:${minute}:${second}`;
|
|
|
},
|
|
|
|
|
|
// 显示加载状态
|
|
|
showLoading: function($block) {
|
|
|
$block.find('#loading-indicator').show();
|
|
|
$block.find('#error-indicator').hide();
|
|
|
$block.find('.stock-main-data').css('opacity', '0.6');
|
|
|
},
|
|
|
|
|
|
// 隐藏加载状态
|
|
|
hideLoading: function($block) {
|
|
|
$block.find('#loading-indicator').hide();
|
|
|
$block.find('.stock-main-data').css('opacity', '1');
|
|
|
},
|
|
|
|
|
|
// 显示错误状态
|
|
|
showError: function($block) {
|
|
|
$block.find('#loading-indicator').hide();
|
|
|
$block.find('#error-indicator').show();
|
|
|
$block.find('.stock-main-data').css('opacity', '0.3');
|
|
|
},
|
|
|
|
|
|
// 刷新数据
|
|
|
refreshData: function() {
|
|
|
this.state.retryCount = 0;
|
|
|
this.loadAllStockData();
|
|
|
},
|
|
|
|
|
|
// 开始自动刷新
|
|
|
startAutoRefresh: function() {
|
|
|
const self = this;
|
|
|
|
|
|
if (self.state.refreshTimer) {
|
|
|
clearInterval(self.state.refreshTimer);
|
|
|
}
|
|
|
|
|
|
self.state.refreshTimer = setInterval(function() {
|
|
|
if (!document.hidden && document.hasFocus()) {
|
|
|
self.loadAllStockData();
|
|
|
}
|
|
|
}, self.config.refreshInterval);
|
|
|
},
|
|
|
|
|
|
// 停止自动刷新
|
|
|
stopAutoRefresh: function() {
|
|
|
if (this.state.refreshTimer) {
|
|
|
clearInterval(this.state.refreshTimer);
|
|
|
this.state.refreshTimer = null;
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 处理页面可见性变化
|
|
|
handleVisibilityChange: function() {
|
|
|
if (document.hidden) {
|
|
|
this.stopAutoRefresh();
|
|
|
} else {
|
|
|
this.startAutoRefresh();
|
|
|
this.loadAllStockData(); // 页面重新可见时立即刷新
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 处理窗口焦点变化
|
|
|
handleFocusChange: function(event) {
|
|
|
if (event.type === 'focus') {
|
|
|
this.startAutoRefresh();
|
|
|
this.loadAllStockData();
|
|
|
} else if (event.type === 'blur') {
|
|
|
this.stopAutoRefresh();
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
|
|
|
// 文档就绪时初始化
|
|
|
$(document).ready(function() {
|
|
|
// 检查是否有股票信息区块
|
|
|
if ($('.stock-info-block').length > 0) {
|
|
|
stockInfoManager.init();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 页面卸载时清理
|
|
|
$(window).on('beforeunload', function() {
|
|
|
stockInfoManager.stopAutoRefresh();
|
|
|
});
|
|
|
|
|
|
})(typeof jQuery !== 'undefined' ? jQuery : undefined); |