diff --git a/web/templates/index.html b/web/templates/index.html
index ff7d37d..006a6f1 100644
--- a/web/templates/index.html
+++ b/web/templates/index.html
@@ -466,6 +466,23 @@
.ma-config-btn-secondary:hover {
background-color: #5c636a;
}
+
+ .ma-line-preview {
+ width: 100%;
+ height: 30px;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ margin-top: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #f8f9fa;
+ }
+
+ .ma-line-preview svg {
+ width: 80%;
+ height: 20px;
+ }
@@ -6820,19 +6837,48 @@
// 显示均线配置弹窗
function showMAConfig() {
+ console.log('📂 显示均线配置弹窗,设置为添加模式');
$('#maConfigModal').css('display', 'flex');
+
// 重置表单
$('#maType').val('SMA');
$('#maLength').val(20);
$('#maSource').val('close');
$('#maSmoothType').val('none');
$('#maSmoothLength').val(3).prop('disabled', true);
+ $('#maLineWidth').val(2);
+ $('#maLineStyle').val(0);
$('#maColor').val(getRandomColor());
+
+ // 移除所有现有的点击事件,避免事件冲突
+ $('#maConfigSubmitBtn').off('click');
+
+ // 设置按钮为添加模式
+ $('#maConfigSubmitBtn').text('添加').on('click', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ console.log('🖱️ 点击了添加按钮(来自showMAConfig),准备添加新均线');
+ addMovingAverage();
+ });
+
+ console.log('🔄 按钮已设置为添加模式');
+
+ // 更新预览
+ setTimeout(updateLinePreview, 50);
}
// 隐藏均线配置弹窗
function hideMAConfig() {
+ console.log('🔒 关闭均线配置弹窗');
$('#maConfigModal').css('display', 'none');
+
+ // 移除所有现有的点击事件
+ $('#maConfigSubmitBtn').off('click');
+
+ // 恢复按钮为添加模式 - 但不立即绑定事件,防止意外触发
+ $('#maConfigSubmitBtn').text('添加');
+
+ console.log('🔄 按钮已重置为添加模式,等待用户主动点击"添加均线"按钮');
}
// 获取随机颜色
@@ -6844,6 +6890,10 @@
// 添加均线
function addMovingAverage() {
+ console.log('🚨🚨🚨 ➕ 警告:addMovingAverage函数被调用了!!!');
+ console.log('📊 添加前均线配置数量:', movingAverages.length);
+ console.log('🔍 调用堆栈:', new Error().stack);
+
const config = {
id: ++maIdCounter,
type: $('#maType').val(),
@@ -6851,10 +6901,20 @@
source: $('#maSource').val(),
smoothType: $('#maSmoothType').val(),
smoothLength: parseInt($('#maSmoothLength').val()),
+ lineWidth: parseInt($('#maLineWidth').val()),
+ lineStyle: parseInt($('#maLineStyle').val()),
color: $('#maColor').val(),
visible: true
};
+ console.log('📝 新均线配置:', {
+ id: config.id,
+ type: config.type,
+ length: config.length,
+ source: config.source,
+ color: config.color
+ });
+
// 验证输入
if (config.length < 1 || config.length > 200) {
alert('均线长度必须在1-200之间');
@@ -6867,11 +6927,15 @@
}
movingAverages.push(config);
+
+ console.log('📊 添加后均线配置数量:', movingAverages.length);
+
hideMAConfig();
- // 重新绘制图表以包含新均线
- if (typeof updateChart === 'function') {
- updateChart();
+ // 直接使用前端数据计算和添加均线,不请求后台
+ if (tvWidget.mainChart && currentData && currentData.kline_data) {
+ const candles = getCurrentCandleData();
+ addMovingAveragesToChart(candles);
}
}
@@ -7003,6 +7067,11 @@
const smoothText = ma.smoothType !== 'none' ?
`, ${ma.smoothType}(${ma.smoothLength})` : '';
+ // 获取线条样式描述
+ const lineStyleNames = ['实线', '点线', '虚线', '大虚线'];
+ const styleText = ma.lineWidth && ma.lineStyle !== undefined ?
+ ` ${ma.lineWidth}px ${lineStyleNames[ma.lineStyle] || '实线'}` : '';
+
// 显示所有均线(包括隐藏的),但用不同样式区分
const itemStyle = ma.visible ? '' : 'opacity: 0.5;';
const eyeIcon = ma.visible ? 'bi-eye' : 'bi-eye-slash';
@@ -7011,17 +7080,17 @@
-
${ma.type}(${ma.length})${smoothText}
+
${ma.type}(${ma.length})${smoothText}${styleText}
${ma.visible ? latestValue : '--'}
-
@@ -7043,221 +7112,232 @@
// 切换均线可见性
function toggleMAVisibility(maId) {
- const ma = movingAverages.find(m => m.id === maId);
+ console.log('👁️ 切换均线可见性,ID:', maId, 'Type:', typeof maId);
+
+ const ma = movingAverages.find(m => m.id == maId); // 使用==而不是===来兼容类型转换
if (ma) {
+ console.log('📝 找到均线,当前可见性:', ma.visible);
ma.visible = !ma.visible;
+ console.log('✅ 切换后可见性:', ma.visible);
- // 如果图表已初始化,直接更新均线显示
+ // 直接使用前端数据重新计算均线
if (tvWidget.mainChart && currentData && currentData.kline_data) {
- // 获取K线数据
- const useElementPeriod = $('#elementPeriodKline').is(':checked') &&
- currentData.element_kline_data &&
- Array.isArray(currentData.element_kline_data);
-
- let candles = [];
- if (useElementPeriod) {
- candles = currentData.element_kline_data.map((kline) => {
- const date = new Date(kline.date);
- const timestamp = date.getTime() / 1000;
- return {
- time: timestamp,
- open: parseFloat(kline.open),
- high: parseFloat(kline.high),
- low: parseFloat(kline.low),
- close: parseFloat(kline.close),
- };
- });
- } else {
- candles = currentData.kline_data.map((kline) => {
- const date = new Date(kline.date);
- const timestamp = date.getTime() / 1000;
- return {
- time: timestamp,
- open: parseFloat(kline.open),
- high: parseFloat(kline.high),
- low: parseFloat(kline.low),
- close: parseFloat(kline.close),
- };
- });
- }
-
- // 重新添加均线到图表
+ const candles = getCurrentCandleData();
addMovingAveragesToChart(candles);
- } else {
- // 如果图表未初始化,重新加载整个图表
- if (typeof updateChart === 'function') {
- updateChart();
- }
}
+ } else {
+ console.error('❌ 未找到要切换可见性的均线,ID:', maId);
}
}
// 配置均线
function configMA(maId) {
- const ma = movingAverages.find(m => m.id === maId);
- if (ma) {
- // 填充表单
- $('#maType').val(ma.type);
- $('#maLength').val(ma.length);
- $('#maSource').val(ma.source);
- $('#maSmoothType').val(ma.smoothType);
- $('#maSmoothLength').val(ma.smoothLength);
- $('#maColor').val(ma.color);
-
- // 启用/禁用平滑长度输入
- $('#maSmoothLength').prop('disabled', ma.smoothType === 'none');
-
- // 修改按钮为更新模式
- $('.ma-config-btn-primary').text('更新').off('click').on('click', function() {
- updateMovingAverage(maId);
- });
-
- $('#maConfigModal').css('display', 'flex');
+ console.log('⚙️ 开始配置均线,ID:', maId, 'Type:', typeof maId);
+ console.log('📊 当前所有均线ID:', movingAverages.map(m => ({id: m.id, type: typeof m.id})));
+
+ const ma = movingAverages.find(m => m.id == maId); // 使用==而不是===来兼容类型转换
+ if (!ma) {
+ console.error('❌ 未找到要配置的均线,ID:', maId);
+ return;
}
+
+ console.log('📝 找到要配置的均线:', ma.type, ma.length, ma.color);
+
+ // 填充表单
+ $('#maType').val(ma.type);
+ $('#maLength').val(ma.length);
+ $('#maSource').val(ma.source);
+ $('#maSmoothType').val(ma.smoothType);
+ $('#maSmoothLength').val(ma.smoothLength);
+ $('#maLineWidth').val(ma.lineWidth || 2);
+ $('#maLineStyle').val(ma.lineStyle || 0);
+ $('#maColor').val(ma.color);
+
+ // 启用/禁用平滑长度输入
+ $('#maSmoothLength').prop('disabled', ma.smoothType === 'none');
+
+ // 移除所有现有的点击事件,避免事件冲突
+ $('#maConfigSubmitBtn').off('click');
+
+ // 修改按钮为更新模式
+ $('#maConfigSubmitBtn').text('更新').on('click', (function(capturedMaId) {
+ return function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ console.log('🖱️ 点击了更新按钮,准备更新ID:', capturedMaId);
+ console.log('🔍 捕获的ID类型:', typeof capturedMaId);
+ updateMovingAverage(capturedMaId);
+ };
+ })(maId));
+
+ console.log('🔄 按钮已切换为更新模式');
+
+ $('#maConfigModal').css('display', 'flex');
+
+ // 更新预览
+ setTimeout(updateLinePreview, 50);
}
// 更新均线
function updateMovingAverage(maId) {
- const ma = movingAverages.find(m => m.id === maId);
- if (ma) {
- // 验证输入
- const newLength = parseInt($('#maLength').val());
- const newSmoothLength = parseInt($('#maSmoothLength').val());
- const newSmoothType = $('#maSmoothType').val();
-
- if (newLength < 1 || newLength > 200) {
- alert('均线长度必须在1-200之间');
- return;
- }
-
- if (newSmoothType !== 'none' && (newSmoothLength < 1 || newSmoothLength > 50)) {
- alert('平滑长度必须在1-50之间');
- return;
- }
-
- // 更新配置
- ma.type = $('#maType').val();
- ma.length = newLength;
- ma.source = $('#maSource').val();
- ma.smoothType = newSmoothType;
- ma.smoothLength = newSmoothLength;
- ma.color = $('#maColor').val();
-
- hideMAConfig();
-
- // 恢复按钮为添加模式
- $('.ma-config-btn-primary').text('添加').off('click').on('click', addMovingAverage);
-
- // 如果图表已初始化,直接更新均线显示
- if (tvWidget.mainChart && currentData && currentData.kline_data) {
- const useElementPeriod = $('#elementPeriodKline').is(':checked') &&
- currentData.element_kline_data &&
- Array.isArray(currentData.element_kline_data);
-
- let candles = [];
- if (useElementPeriod) {
- candles = currentData.element_kline_data.map((kline) => {
- const date = new Date(kline.date);
- const timestamp = date.getTime() / 1000;
- return {
- time: timestamp,
- open: parseFloat(kline.open),
- high: parseFloat(kline.high),
- low: parseFloat(kline.low),
- close: parseFloat(kline.close),
- };
- });
- } else {
- candles = currentData.kline_data.map((kline) => {
- const date = new Date(kline.date);
- const timestamp = date.getTime() / 1000;
- return {
- time: timestamp,
- open: parseFloat(kline.open),
- high: parseFloat(kline.high),
- low: parseFloat(kline.low),
- close: parseFloat(kline.close),
- };
- });
- }
-
- addMovingAveragesToChart(candles);
- } else {
- if (typeof updateChart === 'function') {
- updateChart();
- }
- }
+ console.log('🔄 开始更新均线,ID:', maId, 'Type:', typeof maId);
+ console.log('📊 更新前均线配置数量:', movingAverages.length);
+ console.log('📊 当前所有均线:', movingAverages.map(m => ({id: m.id, type: typeof m.id, maType: m.type, length: m.length, color: m.color})));
+
+ const ma = movingAverages.find(m => m.id == maId); // 使用==而不是===来兼容类型转换
+ if (!ma) {
+ console.error('❌ 未找到要更新的均线配置,ID:', maId);
+ console.error('❌ 可用的均线ID:', movingAverages.map(m => m.id));
+ alert('错误:未找到要更新的均线配置!');
+ return;
+ }
+
+ console.log('📝 找到要更新的均线:', ma.type, ma.length, ma.color);
+
+ // 验证输入
+ const newLength = parseInt($('#maLength').val());
+ const newSmoothLength = parseInt($('#maSmoothLength').val());
+ const newSmoothType = $('#maSmoothType').val();
+
+ if (newLength < 1 || newLength > 200) {
+ alert('均线长度必须在1-200之间');
+ return;
+ }
+
+ if (newSmoothType !== 'none' && (newSmoothLength < 1 || newSmoothLength > 50)) {
+ alert('平滑长度必须在1-50之间');
+ return;
+ }
+
+ // 记录更新前的配置
+ console.log('🔧 更新前配置:', {
+ type: ma.type,
+ length: ma.length,
+ source: ma.source,
+ color: ma.color
+ });
+
+ // 更新配置
+ ma.type = $('#maType').val();
+ ma.length = newLength;
+ ma.source = $('#maSource').val();
+ ma.smoothType = newSmoothType;
+ ma.smoothLength = newSmoothLength;
+ ma.lineWidth = parseInt($('#maLineWidth').val());
+ ma.lineStyle = parseInt($('#maLineStyle').val());
+ ma.color = $('#maColor').val();
+
+ // 记录更新后的配置
+ console.log('✅ 更新后配置:', {
+ type: ma.type,
+ length: ma.length,
+ source: ma.source,
+ color: ma.color
+ });
+ console.log('📊 更新后均线配置数量:', movingAverages.length);
+
+ hideMAConfig();
+
+ // 直接使用前端数据重新计算均线
+ if (tvWidget.mainChart && currentData && currentData.kline_data) {
+ const candles = getCurrentCandleData();
+ addMovingAveragesToChart(candles);
}
}
// 删除均线
function deleteMA(maId) {
- movingAverages = movingAverages.filter(m => m.id !== maId);
+ console.log('🗑️ 删除均线,ID:', maId, 'Type:', typeof maId);
+ console.log('📊 删除前均线配置数量:', movingAverages.length);
- // 如果图表已初始化,直接更新均线显示
+ const beforeCount = movingAverages.length;
+ movingAverages = movingAverages.filter(m => m.id != maId); // 使用!=而不是!==来兼容类型转换
+
+ console.log('📊 删除后均线配置数量:', movingAverages.length);
+ console.log('✅ 是否成功删除:', beforeCount > movingAverages.length);
+
+ // 直接使用前端数据重新计算均线
if (tvWidget.mainChart && currentData && currentData.kline_data) {
- const useElementPeriod = $('#elementPeriodKline').is(':checked') &&
- currentData.element_kline_data &&
- Array.isArray(currentData.element_kline_data);
-
- let candles = [];
- if (useElementPeriod) {
- candles = currentData.element_kline_data.map((kline) => {
- const date = new Date(kline.date);
- const timestamp = date.getTime() / 1000;
- return {
- time: timestamp,
- open: parseFloat(kline.open),
- high: parseFloat(kline.high),
- low: parseFloat(kline.low),
- close: parseFloat(kline.close),
- };
- });
- } else {
- candles = currentData.kline_data.map((kline) => {
- const date = new Date(kline.date);
- const timestamp = date.getTime() / 1000;
- return {
- time: timestamp,
- open: parseFloat(kline.open),
- high: parseFloat(kline.high),
- low: parseFloat(kline.low),
- close: parseFloat(kline.close),
- };
- });
- }
-
+ const candles = getCurrentCandleData();
addMovingAveragesToChart(candles);
- } else {
- if (typeof updateChart === 'function') {
- updateChart();
- }
}
}
+ // 获取当前K线数据的辅助函数
+ function getCurrentCandleData() {
+ if (!currentData || !currentData.kline_data) {
+ return [];
+ }
+
+ // 检查是否使用小周期数据
+ const useElementPeriod = $('#elementPeriodKline').is(':checked') &&
+ currentData.element_kline_data &&
+ Array.isArray(currentData.element_kline_data);
+
+ let candles = [];
+ if (useElementPeriod) {
+ candles = currentData.element_kline_data.map((kline) => {
+ const date = new Date(kline.date);
+ const timestamp = date.getTime() / 1000;
+ return {
+ time: timestamp,
+ open: parseFloat(kline.open),
+ high: parseFloat(kline.high),
+ low: parseFloat(kline.low),
+ close: parseFloat(kline.close),
+ };
+ });
+ } else {
+ candles = currentData.kline_data.map((kline) => {
+ const date = new Date(kline.date);
+ const timestamp = date.getTime() / 1000;
+ return {
+ time: timestamp,
+ open: parseFloat(kline.open),
+ high: parseFloat(kline.high),
+ low: parseFloat(kline.low),
+ close: parseFloat(kline.close),
+ };
+ });
+ }
+
+ return candles;
+ }
+
// 添加均线到图表
function addMovingAveragesToChart(candleData) {
if (!tvWidget.mainChart || !candleData || candleData.length === 0) {
return;
}
- // 清除现有的均线系列
- if (tvWidget.series.maSeries) {
- tvWidget.series.maSeries.forEach(series => {
+ console.log('🔄 重新绘制均线,当前均线配置数量:', movingAverages.length);
+
+ // 强制清除现有的均线系列
+ if (tvWidget.series.maSeries && tvWidget.series.maSeries.length > 0) {
+ console.log('🗑️ 清除现有均线系列:', tvWidget.series.maSeries.length);
+ tvWidget.series.maSeries.forEach((series, index) => {
try {
tvWidget.mainChart.removeSeries(series);
+ console.log('✅ 移除均线系列:', index);
} catch (e) {
- console.debug('移除均线系列时出错:', e);
+ console.error('❌ 移除均线系列失败:', index, e);
}
});
}
+ // 重置均线系列数组
tvWidget.series.maSeries = [];
// 为每个均线配置创建系列
- movingAverages.forEach(maConfig => {
- if (!maConfig.visible) return;
+ let addedCount = 0;
+ movingAverages.forEach((maConfig, index) => {
+ if (!maConfig.visible) {
+ console.log(`⏭️ 跳过隐藏的均线 [${index}]:`, maConfig.type, maConfig.length);
+ return;
+ }
try {
+ console.log(`📈 开始创建均线 [${index}]:`, maConfig.type, maConfig.length, maConfig.color);
+
// 计算均线数据
const maData = calculateMA(candleData, maConfig.type, maConfig.length, maConfig.source);
@@ -7269,7 +7349,8 @@
// 创建线系列
const maSeries = tvWidget.mainChart.addLineSeries({
color: maConfig.color,
- lineWidth: 2,
+ lineWidth: maConfig.lineWidth || 2,
+ lineStyle: maConfig.lineStyle || 0, // 0=实线, 1=点线, 2=虚线, 3=大虚线
title: `${maConfig.type}(${maConfig.length})`,
lastValueVisible: false,
priceLineVisible: false,
@@ -7284,12 +7365,18 @@
// 保存系列引用
tvWidget.series.maSeries.push(maSeries);
+ addedCount++;
+
+ console.log(`✅ 均线创建成功 [${index}]:`, maConfig.type, maConfig.length, '数据点:', smoothedData.length);
} catch (e) {
- console.error('添加均线时出错:', e, maConfig);
+ console.error('❌ 添加均线时出错:', e, maConfig);
}
});
+ console.log(`🎯 均线添加完成,共添加 ${addedCount} 条均线`);
+ console.log('📊 当前图表中的均线系列数量:', tvWidget.series.maSeries.length);
+
// 更新均线面板
updateMAPanel();
}
@@ -7300,6 +7387,36 @@
$('#maSmoothLength').prop('disabled', isNone);
});
+ // 更新线条预览
+ function updateLinePreview() {
+ const color = $('#maColor').val();
+ const width = $('#maLineWidth').val();
+ const style = $('#maLineStyle').val();
+
+ const line = $('#previewLine');
+ line.attr('stroke', color);
+ line.attr('stroke-width', width);
+
+ // 设置线条样式
+ switch(parseInt(style)) {
+ case 0: // 实线
+ line.attr('stroke-dasharray', 'none');
+ break;
+ case 1: // 点线
+ line.attr('stroke-dasharray', '2,3');
+ break;
+ case 2: // 虚线
+ line.attr('stroke-dasharray', '5,5');
+ break;
+ case 3: // 大虚线
+ line.attr('stroke-dasharray', '10,5');
+ break;
+ }
+ }
+
+ // 监听配置变化以更新预览
+ $(document).on('change', '#maColor, #maLineWidth, #maLineStyle', updateLinePreview);
+
// 点击弹窗外部关闭
$(document).on('click', '#maConfigModal', function(e) {
if (e.target === this) {
@@ -7409,14 +7526,41 @@
+