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 @@
+
+ + +
+
+ + +
+
+ +
+ + + +
+
- +