24、钢铁厂峰谷电价策略优化分析 - /能源管理组件/steel-plant-tou-optimization
76个工业组件库示例汇总
钢铁厂峰谷电价策略优化分析组件
1. 组件概述
本组件旨在模拟钢铁厂(特别是电弧炉 EAF 等高耗能工序)在峰谷电价机制下的用电成本,并通过模拟调整生产计划(负荷转移)来分析潜在的成本优化空间。
用户可以设定负荷转移策略(转移多少负荷、从哪个高峰时段转移、转移到哪个谷/平时段、持续多久),然后组件会计算并对比优化前后的总用电成本,并在图表中直观展示负荷曲线的变化以及与电价时段的关系。
设计风格遵循苹果科技工业美学,注重数据的清晰呈现和交互的便捷性。
2. 主要功能
- 电价信息展示: 清晰列出预设的峰、平、谷时段及其对应的电价费率。
- 基准负荷模拟: 基于设定的工厂基础负荷和高耗能设备(如 EAF)的典型运行时间,生成一个 24 小时的基准负荷曲线。
- 优化策略配置: 用户可以输入想要模拟的负荷转移量 (MW)、选择负荷转移的起始高峰时段、目标谷/平时段以及转移的持续时长 (小时)。
- 成本对比分析: 计算并显示模拟的 24 小时内,基准负荷下的总用电成本和执行优化策略后的总用电成本,以及明确的成本节省金额和百分比。
- 可视化图表: 使用 Chart.js 图表展示:
- 基准负荷曲线(例如,蓝色实线)。
- 优化后的负荷曲线(例如,橙色虚线)。
- 代表峰、平、谷电价时段的背景色块,方便用户直观理解负荷与电价的关系。
- 交互式分析: 用户调整优化策略参数后,点击按钮即可重新计算并更新成本和图表。
- 响应式布局: 界面适应不同屏幕宽度。
3. 技术栈
- HTML5
- CSS3 (Flexbox, CSS Variables, Media Queries)
- JavaScript (ES6+)
- Chart.js (用于绘制图表)
- Day.js (本次模拟未使用,但已包含在 HTML 中以备将来扩展)
4. 运行与使用
- 将
steel-plant-tou-optimization
文件夹放置在能源管理组件
目录下。 - 在支持 HTML5 和 JavaScript 的浏览器中打开
index.html
文件。 - 组件加载后会显示预设的电价信息、基准 EAF 运行计划,并进行一次初始成本计算和图表绘制。
- 在左侧"优化策略模拟"区域:
- 修改"转移负荷量"、“从高峰时段”、"转移至谷/平时段"和"持续时长"等参数。
- 点击"执行优化分析"按钮。
- 观察左下侧"成本分析概要"面板中更新的成本数据(优化前、优化后、节省额)。
- 观察右侧图表中负荷曲线(橙色虚线)的变化,以及它与电价背景色的对应关系。
5. 模拟逻辑说明
- 峰谷电价: 在
script.js
的config.touTariff
中预定义。getTariffForHour(hour)
函数根据小时确定对应的电价类型和费率。 - 基准负荷: 由固定基础负荷 (
config.baseLoadMW
) 和在特定高峰时段 (config.eafBaselineRunHours
) 叠加的高耗能设备负荷 (config.eafLoadMW
) 组成。 - 成本计算:
calculateCost(loadProfile)
函数遍历 24 小时负荷曲线,将每小时的负荷 (MW) 乘以对应时段的电价费率(此处简化假设费率为 元/MWh),累加得到总成本。 - 负荷转移:
generateOptimizedLoadProfile()
函数根据用户输入的参数,从指定的fromHour
开始,在duration
小时内,尝试将每小时shiftAmount
MW 的负荷(仅转移超出基础负荷的部分)减少,并增加到对应的toHour
开始的时段。如果源时段没有足够的可转移负荷,会进行部分转移并在分析说明中提示。 - 图表可视化: 使用 Chart.js 的组合图表类型。基准和优化后的负荷用
line
类型绘制,电价时段用bar
类型作为背景色块绘制,通过order
属性控制绘制顺序。工具提示 (Tooltip) 会同时显示负荷值和当前时段的电价信息。
6. 注意事项
- 这是一个概念性模拟,用于演示峰谷电价优化策略的基本思路和效果。实际钢铁厂的负荷构成、生产调度约束和电价结构要复杂得多。
- 负荷转移模型非常简化,未考虑生产工艺的连续性、设备启停限制等实际约束。
- 成本计算假设电价费率单位为 元/MWh,是为了简化计算(MW * 元/MWh = 元)。如果实际费率是 元/kWh,则需要在计算时乘以 1000。
- 所有数据均为程序生成。
效果展示
源码
index.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><link rel="stylesheet" href="styles.css"><!-- Chart.js for visualization --><script src="https://cdn.jsdelivr.net/npm/chart.js"></script><!-- Using Day.js for potential time formatting if needed --><script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script>
</head>
<body><div class="container"><header class="header-bar"><h1>峰谷电价策略优化分析</h1></header><main class="main-content"><section class="config-control-section"><div class="panel tou-tariff-panel"><h2>峰谷电价时段与费率 (元/kWh)</h2><ul id="tariffList"><!-- Tariff details loaded by JS --><li>加载电价信息...</li></ul></div><div class="panel schedule-panel"><h2>基准生产计划 ()</h2><p>假设主要高耗能设备 (如电弧炉 EAF) 的运行时间段如下:</p><div id="baselineScheduleDisplay"><!-- Baseline schedule info loaded by JS --><p>加载基准计划...</p></div></div><div class="panel optimization-panel"><h2>优化策略</h2><div class="control-group"><label for="shiftLoadAmount">转移负荷量 (MW):</label><input type="number" id="shiftLoadAmount" value="50" min="0" step="5"></div><div class="control-group"><label for="shiftFromHour">从高峰时段 (小时):</label><select id="shiftFromHour"><!-- Options populated by JS based on peak hours --></select></div><div class="control-group"><label for="shiftToHour">转移至谷/平时段 (小时):</label><select id="shiftToHour"><!-- Options populated by JS based on off-peak hours --></select></div><div class="control-group"><label for="shiftDuration">持续时长 (小时):</label><input type="number" id="shiftDuration" value="3" min="1" max="8" step="1"></div><button id="optimizeBtn">执行优化分析</button></div><div class="panel cost-summary-panel"><h2>成本分析概要 (24小时)</h2><div class="summary-item"><span>优化前总成本:</span><strong id="baselineCost">-- 元</strong></div><div class="summary-item"><span>优化后总成本:</span><strong id="optimizedCost">-- 元</strong></div><div class="summary-item savings"><span>节省成本:</span><strong id="costSavings">-- 元</strong><span id="savingsPercentage"></span></div></div></section><section class="visualization-section"><div class="chart-container load-chart-container"><h2>负荷曲线与电价 (24小时)</h2><canvas id="loadProfileChart"></canvas></div><div class="analysis-details-panel panel"><h2>分析说明</h2><p>此展示了通过将高峰时段的部分高能耗生产(如电弧炉运行)转移到电价较低的谷/平时段,可以实现的潜在成本节约。</p><p>上方图表显示:</p><ul><li>蓝色实线: 基准负荷曲线</li><li>橙色虚线: 优化后负荷曲线</li><li>背景色块: 不同电价时段 (峰/平/谷)</li></ul><p id="analysisNotes">请调整左侧优化策略参数,点击"执行优化分析"查看结果。</p></div></section></main></div><script src="script.js"></script>
</body>
</html>
styles.css
:root {--bg-color: #f5f5f7;--panel-bg-color: #ffffff;--border-color: #d2d2d7;--text-color-primary: #1d1d1f;--text-color-secondary: #6e6e73;--accent-blue: #007aff;--accent-green: #34c759;--accent-red: #ff3b30;--accent-orange: #ff9500; /* For optimized line */--tariff-peak-bg: rgba(255, 59, 48, 0.1);--tariff-shoulder-bg: rgba(255, 204, 0, 0.1);--tariff-offpeak-bg: rgba(52, 199, 89, 0.1);--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;--border-radius: 8px;--container-padding: 20px;--panel-padding: 15px;--header-height: 50px;
}* {box-sizing: border-box;margin: 0;padding: 0;
}body {font-family: var(--font-family);background-color: var(--bg-color);color: var(--text-color-primary);line-height: 1.5;display: flex;justify-content: center;align-items: flex-start;min-height: 100vh;padding: 20px;
}.container {width: 100%;max-width: 1300px; /* Adjust max width as needed */background-color: var(--panel-bg-color);border-radius: var(--border-radius);border: 1px solid var(--border-color);box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);overflow: hidden;display: flex;flex-direction: column;
}/* Header */
.header-bar {padding: 0 var(--container-padding);height: var(--header-height);border-bottom: 1px solid var(--border-color);display: flex;align-items: center;
}.header-bar h1 {font-size: 1.15em;font-weight: 600;
}/* Main Content */
.main-content {display: flex;flex: 1;padding: var(--container-padding);gap: var(--container-padding);min-height: 500px; /* Ensure reasonable height */
}.config-control-section {flex: 2; /* Left side takes less space */display: flex;flex-direction: column;gap: var(--panel-padding);
}.visualization-section {flex: 3; /* Right side takes more space */display: flex;flex-direction: column;gap: var(--panel-padding);flex-grow: 1;
}/* Panels */
.panel {background-color: #ffffff;border: 1px solid var(--border-color);border-radius: var(--border-radius);padding: var(--panel-padding);box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}.panel h2 {font-size: 0.95em;font-weight: 600;margin-bottom: 12px;color: var(--text-color-primary);
}/* Left Panel Specifics */
.tou-tariff-panel ul {list-style: none;padding: 0;font-size: 0.85em;
}.tou-tariff-panel li {padding: 5px 0;border-bottom: 1px dashed #eee;display: flex;justify-content: space-between;
}
.tou-tariff-panel li:last-child {border-bottom: none;
}
.tou-tariff-panel .tariff-type {font-weight: 500;
}
.tou-tariff-panel .tariff-hours {color: var(--text-color-secondary);font-size: 0.9em;
}
.tou-tariff-panel .tariff-rate {font-weight: 600;color: var(--accent-blue);
}.schedule-panel p {font-size: 0.9em;margin-bottom: 8px;color: var(--text-color-secondary);
}#baselineScheduleDisplay p {font-size: 0.9em;color: var(--text-color-primary);font-style: italic;
}.optimization-panel .control-group {margin-bottom: 12px;display: flex;align-items: center;gap: 10px;font-size: 0.9em;
}.optimization-panel label {flex-basis: 120px; /* Align labels */text-align: right;color: var(--text-color-secondary);font-size: 0.95em;
}.optimization-panel input[type="number"],
.optimization-panel select {flex-grow: 1;padding: 6px 8px;border: 1px solid var(--border-color);border-radius: 4px;font-size: 0.95em;max-width: 150px; /* Limit width of inputs/selects */
}.optimization-panel button {display: block;width: 100%;padding: 10px 15px;margin-top: 10px;background-color: var(--accent-blue);color: white;border: none;border-radius: 5px;cursor: pointer;font-size: 1em;font-weight: 500;transition: background-color 0.2s ease;
}.optimization-panel button:hover {background-color: #0056b3;
}.cost-summary-panel .summary-item {display: flex;justify-content: space-between;padding: 6px 0;font-size: 0.9em;border-bottom: 1px dashed #eee;
}
.cost-summary-panel .summary-item:last-child {border-bottom: none;
}.cost-summary-panel .summary-item span {color: var(--text-color-secondary);
}
.cost-summary-panel .summary-item strong {color: var(--text-color-primary);font-weight: 600;
}
.cost-summary-panel .summary-item.savings strong {color: var(--accent-green);
}
.cost-summary-panel .summary-item #savingsPercentage {font-size: 0.9em;margin-left: 5px;color: var(--accent-green);
}/* Right Panel Specifics */
.load-chart-container {flex-grow: 3; /* Chart takes more space vertically compared to details */padding: var(--panel-padding);border: 1px solid var(--border-color);border-radius: var(--border-radius);box-shadow: 0 1px 2px rgba(0,0,0,0.05);display: flex; /* Use flex to help canvas resize */flex-direction: column;height: 500px; /* Suggest a fixed height */max-height: 800px; /* Absolute maximum height */min-height: 300px; /* Minimum reasonable height */
}.load-chart-container canvas {max-width: 100%;/* Let Chart.js handle canvas resizing within the container */
}.analysis-details-panel {flex-grow: 1; /* Details panel takes less space */flex-shrink: 0; /* Prevent shrinking too much */font-size: 0.85em;line-height: 1.6;color: var(--text-color-secondary);max-height: 500px; /* Also limit details panel height */overflow-y: auto;
}
.analysis-details-panel h2 {margin-bottom: 10px;
}
.analysis-details-panel ul {margin-left: 20px;margin-bottom: 10px;
}
.analysis-details-panel p {margin-bottom: 10px;
}
#analysisNotes {font-style: italic;color: var(--text-color-secondary);
}/* Responsive Adjustments */
@media (max-width: 1100px) {.main-content {flex-direction: column;min-height: auto;}.config-control-section,.visualization-section {flex: none;width: 100%;}
}@media (max-width: 768px) {body {padding: 10px;}.container {border-radius: 0;border-left: none;border-right: none;}.main-content {padding: var(--panel-padding);}.config-control-section, .visualization-section {gap: var(--panel-padding);}.optimization-panel .control-group {flex-direction: column;align-items: stretch;gap: 5px;}.optimization-panel label {text-align: left;margin-bottom: 2px;}.optimization-panel input[type="number"],.optimization-panel select {max-width: none;}
}@media (max-width: 480px) {.header-bar h1 {font-size: 1em;}.panel h2 {font-size: 0.9em;}body {padding: 5px;}
}
script.js
document.addEventListener('DOMContentLoaded', () => {// --- DOM Elements ---const tariffListUl = document.getElementById('tariffList');const baselineScheduleDisplay = document.getElementById('baselineScheduleDisplay');const shiftLoadAmountInput = document.getElementById('shiftLoadAmount');const shiftFromHourSelect = document.getElementById('shiftFromHour');const shiftToHourSelect = document.getElementById('shiftToHour');const shiftDurationInput = document.getElementById('shiftDuration');const optimizeBtn = document.getElementById('optimizeBtn');const baselineCostSpan = document.getElementById('baselineCost');const optimizedCostSpan = document.getElementById('optimizedCost');const costSavingsSpan = document.getElementById('costSavings');const savingsPercentageSpan = document.getElementById('savingsPercentage');const loadProfileCanvas = document.getElementById('loadProfileChart');const analysisNotesP = document.getElementById('analysisNotes');// --- Configuration ---const config = {hoursInDay: 24,baseLoadMW: 20, // Base plant load constant throughout the dayeafLoadMW: 100, // Electric Arc Furnace load when runningeafBaselineRunHours: [8, 9, 10, 11, 13, 14, 15, 16], // Example peak hours EAF runs// Time-of-Use Tariff Structure (Example)touTariff: [{ type: '谷', startHour: 0, endHour: 7, rate: 0.40 }, // 00:00 - 07:59{ type: '平', startHour: 8, endHour: 11, rate: 0.85 }, // 08:00 - 11:59{ type: '峰', startHour: 12, endHour: 17, rate: 1.30 }, // 12:00 - 17:59{ type: '平', startHour: 18, endHour: 21, rate: 0.85 }, // 18:00 - 21:59{ type: '谷', startHour: 22, endHour: 23, rate: 0.40 }, // 22:00 - 23:59],chartColors: {baseline: 'rgba(0, 122, 255, 0.8)',optimized: 'rgba(255, 149, 0, 0.8)',peakBg: 'rgba(255, 59, 48, 0.1)',shoulderBg: 'rgba(255, 204, 0, 0.1)',offpeakBg: 'rgba(52, 199, 89, 0.1)'}};// --- State ---let baselineLoadProfile = []; // Array of 24 hourly load values (MW)let optimizedLoadProfile = []; // Array of 24 hourly load values (MW)let loadProfileChart = null;// --- Helper Functions ---function getTariffForHour(hour) {for (const period of config.touTariff) {// Handle wrap-around for endHour < startHour (e.g.,谷 period spanning midnight)if (period.endHour >= period.startHour) {if (hour >= period.startHour && hour <= period.endHour) {return period;}} else { // Period wraps around midnight (e.g., 22:00 - 07:00)if (hour >= period.startHour || hour <= period.endHour) {return period;}}}return { type: '未知', rate: 1.0 }; // Fallback}function generateBaselineLoadProfile() {const profile = Array(config.hoursInDay).fill(config.baseLoadMW);config.eafBaselineRunHours.forEach(hour => {if (hour >= 0 && hour < config.hoursInDay) {profile[hour] += config.eafLoadMW;}});return profile;}function calculateCost(loadProfile) {let totalCost = 0;for (let hour = 0; hour < config.hoursInDay; hour++) {const tariff = getTariffForHour(hour);const load = loadProfile[hour] || 0;// Assuming rate is effectively 元 per MWh for simulation simplicitytotalCost += load * tariff.rate;}return totalCost;}function generateOptimizedLoadProfile(baselineProfile, shiftAmount, fromHour, toHour, duration) {const optimized = [...baselineProfile]; // Create a copyfromHour = parseInt(fromHour);toHour = parseInt(toHour);duration = parseInt(duration);shiftAmount = parseFloat(shiftAmount);if (isNaN(fromHour) || isNaN(toHour) || isNaN(duration) || isNaN(shiftAmount) || duration <= 0 || shiftAmount <= 0) {console.error("Invalid optimization parameters");analysisNotesP.textContent = "错误:无效的优化参数。";return baselineProfile; // Return baseline if params are bad}analysisNotesP.textContent = `模拟:将 ${fromHour}:00 开始的 ${duration} 小时内,每小时 ${shiftAmount} MW 的负荷转移到 ${toHour}:00 开始的时段。`;for (let i = 0; i < duration; i++) {const currentFromHour = (fromHour + i) % config.hoursInDay;const currentToHour = (toHour + i) % config.hoursInDay;// Check if enough load exists at the source hour (above base load)const availableLoadToShift = Math.max(0, optimized[currentFromHour] - config.baseLoadMW);const actualShift = Math.min(shiftAmount, availableLoadToShift);if (actualShift > 0) {optimized[currentFromHour] -= actualShift;optimized[currentToHour] += actualShift;} else {console.warn(`Hour ${currentFromHour}: Not enough shiftable load (${availableLoadToShift.toFixed(1)} MW) to move ${shiftAmount} MW.`);analysisNotesP.textContent += ` (注意:在 ${currentFromHour}:00 时段可转移负荷不足)`;}}return optimized;}// --- Initialization Functions ---function displayTariffInfo() {tariffListUl.innerHTML = '';config.touTariff.forEach(period => {const li = document.createElement('li');li.innerHTML = `<span class="tariff-type">${period.type}时段</span><span class="tariff-hours">(${String(period.startHour).padStart(2, '0')}:00 - ${String(period.endHour).padStart(2, '0')}:59)</span><span class="tariff-rate">${period.rate.toFixed(2)} 元</span>`;tariffListUl.appendChild(li);});}function displayBaselineSchedule() {baselineScheduleDisplay.innerHTML = `<p>EAF 运行时段: ${config.eafBaselineRunHours.map(h => `${String(h).padStart(2, '0')}:00`).join(', ')}</p>`;}function populateShiftSelectOptions() {shiftFromHourSelect.innerHTML = '';shiftToHourSelect.innerHTML = '';const peakHours = [];const offPeakHours = [];for (let hour = 0; hour < config.hoursInDay; hour++) {const tariff = getTariffForHour(hour);const option = document.createElement('option');option.value = hour;option.textContent = `${String(hour).padStart(2, '0')}:00`;if (tariff.type === '峰') {shiftFromHourSelect.appendChild(option);peakHours.push(hour);} else {shiftToHourSelect.appendChild(option.cloneNode(true)); // Clone node for the other selectoffPeakHours.push(hour);}}// Set default selections if possibleif (shiftFromHourSelect.options.length > 0) shiftFromHourSelect.selectedIndex = 0;if (shiftToHourSelect.options.length > 0) shiftToHourSelect.selectedIndex = 0;}function initializeChart() {if (loadProfileChart) {loadProfileChart.destroy();}const ctx = loadProfileCanvas.getContext('2d');const labels = Array.from({ length: config.hoursInDay }, (_, i) => `${String(i).padStart(2, '0')}:00`);// Prepare background color array based on tariffconst backgroundColors = labels.map((_, index) => {const tariff = getTariffForHour(index);switch (tariff.type) {case '峰': return config.chartColors.peakBg;case '平': return config.chartColors.shoulderBg;case '谷': return config.chartColors.offpeakBg;default: return 'rgba(0,0,0,0.05)';}});loadProfileChart = new Chart(ctx, {type: 'bar', // Use bar chart to better show hourly load and background colordata: {labels: labels,datasets: [{label: '基准负荷 (MW)',data: baselineLoadProfile,backgroundColor: config.chartColors.baseline,borderColor: config.chartColors.baseline,borderWidth: 1,type: 'line', // Overlay line for baselinefill: false,tension: 0.1,pointRadius: 1,order: 1 // Draw line on top},{label: '优化负荷 (MW)',data: optimizedLoadProfile,backgroundColor: config.chartColors.optimized,borderColor: config.chartColors.optimized,borderWidth: 1,type: 'line', // Overlay line for optimizedfill: false,borderDash: [5, 5],tension: 0.1,pointRadius: 1,order: 0 // Draw optimized line first},{label: '电价时段',data: baselineLoadProfile, // Use any profile data just for positioning barsbackgroundColor: backgroundColors,borderColor: 'transparent',borderWidth: 0,type: 'bar', // Background barsorder: 2, // Draw bars behind linesbarPercentage: 1.0,categoryPercentage: 1.0,grouped: false // Ensure bars occupy the full category width}]},options: {responsive: true,maintainAspectRatio: false,interaction: { // Optimize hover/tooltipmode: 'index',intersect: false,},scales: {x: {stacked: true, // Needed for background barstitle: { display: true, text: '时间 (小时)' }},y: {beginAtZero: true,stacked: false,title: { display: true, text: '负荷 (MW)' }}},plugins: {tooltip: {mode: 'index',intersect: false,callbacks: {// Custom tooltip to show load and tarifflabel: function(context) {let label = context.dataset.label || '';if (label && context.dataset.type === 'line') {label += `: ${context.parsed.y.toFixed(1)} MW`;}// Add tariff info to tooltip (only once per x-index)if (context.datasetIndex === 0) { // Show only for the first dataset (baseline line)const hour = context.dataIndex;const tariff = getTariffForHour(hour);label += ` (${tariff.type}: ${tariff.rate.toFixed(2)} 元)`;}return label;},// Filter out the background bar dataset from tooltipfilter: function(tooltipItem) {return tooltipItem.dataset.label !== '电价时段';}}},legend: {labels: {// Filter out the background bar dataset from legendfilter: function(legendItem, chartData) {return legendItem.text !== '电价时段';}}}}}});}// --- Core Calculation and Update ---function runOptimizationAnalysis() {const shiftAmount = parseFloat(shiftLoadAmountInput.value);const fromHour = parseInt(shiftFromHourSelect.value);const toHour = parseInt(shiftToHourSelect.value);const duration = parseInt(shiftDurationInput.value);// Validate inputsif (isNaN(shiftAmount) || shiftAmount < 0 || isNaN(fromHour) || isNaN(toHour) || isNaN(duration) || duration <= 0) {analysisNotesP.textContent = "错误: 请输入有效的优化参数。";baselineCostSpan.textContent = '-- 元';optimizedCostSpan.textContent = '-- 元';costSavingsSpan.textContent = '-- 元';savingsPercentageSpan.textContent = '';return;}// Prevent shifting to the same hour or overlapping significantly (simple check)if (fromHour === toHour) {analysisNotesP.textContent = "错误: 不能将负荷转移到同一时段。";return;}// 1. Regenerate baseline (in case base config changes in future)baselineLoadProfile = generateBaselineLoadProfile();// 2. Generate optimized profileoptimizedLoadProfile = generateOptimizedLoadProfile(baselineLoadProfile, shiftAmount, fromHour, toHour, duration);// 3. Calculate costsconst baselineCost = calculateCost(baselineLoadProfile);const optimizedCost = calculateCost(optimizedLoadProfile);const savings = baselineCost - optimizedCost;const savingsPercent = baselineCost > 0 ? (savings / baselineCost) * 100 : 0;// 4. Update UIbaselineCostSpan.textContent = `${baselineCost.toFixed(0)} 元`;optimizedCostSpan.textContent = `${optimizedCost.toFixed(0)} 元`;costSavingsSpan.textContent = `${savings.toFixed(0)} 元`;savingsPercentageSpan.textContent = savings > 0 ? `(${savingsPercent.toFixed(1)}%)` : '';// Update Chart Dataif (loadProfileChart) {loadProfileChart.data.datasets[0].data = baselineLoadProfile;loadProfileChart.data.datasets[1].data = optimizedLoadProfile;// Update background bar data as well (though it might not change)loadProfileChart.data.datasets[2].data = baselineLoadProfile;loadProfileChart.update();} else {initializeChart(); // Initialize if it doesn't exist yet}}// --- Event Listeners ---optimizeBtn.addEventListener('click', runOptimizationAnalysis);// --- Initial Setup ---function initializeApp() {displayTariffInfo();displayBaselineSchedule();populateShiftSelectOptions();baselineLoadProfile = generateBaselineLoadProfile();optimizedLoadProfile = [...baselineLoadProfile]; // Initially, optimized is same as baselineinitializeChart();runOptimizationAnalysis(); // Run initial analysis to populate costs and chartanalysisNotesP.textContent = "请调整左侧优化策略参数,点击'执行优化分析'查看结果。"; // Reset notes after initial run}initializeApp();
});