feat: 添加分时段电量统计功能(尖/峰/平/谷)

Coze-Commit-Type: user
Coze-User-ID: 3722323274763196
Coze-Conversation-ID: 9894087
This commit is contained in:
user9994793890
2026-07-10 15:00:18 +08:00
parent d756ecd044
commit 619e628501
4 changed files with 461 additions and 6 deletions

View File

@@ -510,6 +510,167 @@
font-weight: 700;
}
/* 时段编辑器样式 */
.time-period-editor {
background: #f8f9fa;
border-radius: 10px;
padding: 16px;
border: 1px solid #e9ecef;
}
.time-period-header {
margin-bottom: 12px;
}
.time-period-hint {
font-size: 12px;
color: #6c757d;
}
.time-period-tools {
display: flex;
gap: 8px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.tp-tool-btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 6px 12px;
border: 1px solid #dee2e6;
border-radius: 6px;
background: white;
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
}
.tp-tool-btn:hover {
background: #f8f9fa;
border-color: #adb5bd;
}
.tp-tool-btn.active {
background: #3498db;
color: white;
border-color: #3498db;
}
.tp-tool-btn.tp-sharp { background: #e74c3c; color: white; border-color: #e74c3c; }
.tp-tool-btn.tp-peak { background: #f39c12; color: white; border-color: #f39c12; }
.tp-tool-btn.tp-flat { background: #27ae60; color: white; border-color: #27ae60; }
.tp-tool-btn.tp-valley { background: #3498db; color: white; border-color: #3498db; }
.time-period-grid-wrapper {
overflow-x: auto;
margin-bottom: 12px;
}
.tp-hours-header {
display: flex;
gap: 2px;
margin-bottom: 4px;
}
.tp-label-col {
width: 50px;
flex-shrink: 0;
}
.tp-hours-row {
display: flex;
gap: 2px;
flex: 1;
}
.tp-hour-label {
width: 28px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
color: #6c757d;
flex-shrink: 0;
}
.tp-period-rows {
display: flex;
flex-direction: column;
gap: 4px;
}
.tp-period-row {
display: flex;
gap: 2px;
align-items: center;
}
.tp-period-label {
width: 50px;
flex-shrink: 0;
font-size: 12px;
font-weight: 600;
padding: 4px 8px;
border-radius: 4px;
text-align: center;
}
.tp-period-label.sharp { background: #e74c3c; color: white; }
.tp-period-label.peak { background: #f39c12; color: white; }
.tp-period-label.flat { background: #27ae60; color: white; }
.tp-period-label.valley { background: #3498db; color: white; }
.tp-period-cells {
display: flex;
gap: 2px;
flex: 1;
}
.tp-cell {
width: 28px;
height: 28px;
border: 1px solid #dee2e6;
border-radius: 4px;
background: white;
cursor: pointer;
transition: all 0.15s;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
flex-shrink: 0;
}
.tp-cell:hover {
border-color: #adb5bd;
transform: scale(1.05);
}
.tp-cell.sharp { background: #e74c3c; border-color: #e74c3c; color: white; }
.tp-cell.peak { background: #f39c12; border-color: #f39c12; color: white; }
.tp-cell.flat { background: #27ae60; border-color: #27ae60; color: white; }
.tp-cell.valley { background: #3498db; border-color: #3498db; color: white; }
.time-period-summary {
font-size: 12px;
color: #495057;
padding: 8px 12px;
background: white;
border-radius: 6px;
border: 1px solid #e9ecef;
}
.time-period-summary span {
margin-right: 12px;
}
.time-period-summary .tp-count {
font-weight: 600;
}
.entity-select-wrapper {
position: relative;
}
@@ -726,9 +887,37 @@
</div>
</div>
<!-- 时段设置 -->
<div class="form-section">
<div class="form-section-title"><span class="section-num">3</span> 时段设置 <span class="form-hint">(可选,用于分时段统计电量)</span></div>
<div class="time-period-editor">
<div class="time-period-header">
<span class="time-period-hint">点击下方时段按钮,再点选小时格子来分配时段。每个小时只能属于一个时段。</span>
</div>
<div class="time-period-tools">
<button type="button" class="tp-tool-btn" id="tp-tool-select" onclick="setTimePeriodTool('select')" title="点选">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"/></svg>
点选
</button>
<button type="button" class="tp-tool-btn" id="tp-tool-clear" onclick="clearAllTimePeriods()" title="清空">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>
清空
</button>
</div>
<div class="time-period-grid-wrapper">
<div class="tp-hours-header">
<div class="tp-label-col"></div>
<div class="tp-hours-row" id="tp-hours-row"></div>
</div>
<div class="tp-period-rows" id="tp-period-rows"></div>
</div>
<div class="time-period-summary" id="tp-summary"></div>
</div>
</div>
<!-- 字段设置 -->
<div class="form-section">
<div class="form-section-title"><span class="section-num">3</span> 字段设置</div>
<div class="form-section-title"><span class="section-num">4</span> 字段设置</div>
<div class="form-group">
<label>选择报表字段 <span class="form-hint">(勾选需要在报表中显示的字段)</span></label>
<div class="checkbox-group" id="fields-list">
@@ -844,6 +1033,8 @@
document.getElementById('split-value').innerHTML = '<option value="">请先选择拆分方式</option>';
loadFields();
initTimePeriodEditor();
clearAllTimePeriods();
document.getElementById('config-modal').classList.add('active');
}
@@ -871,7 +1062,9 @@
'settle_electric_fee', 'settle_service_fee', 'settle_fee',
'coupon_amount', 'coupon_total_amount', 'elecfee_coupon_amount', 'servicefee_coupon_amount',
'invoice_fee', 'activity_electric_fee', 'activity_service_fee', 'activity_total_fee',
'total_money'
'total_money',
// 分时段电量
'sharp_electricity', 'peak_electricity', 'flat_electricity', 'valley_electricity'
];
// 渲染字段列表
@@ -944,6 +1137,144 @@
}
}
// ========== 时段编辑器 ==========
const TIME_PERIODS = [
{ key: 'sharp', label: '尖', color: '#e74c3c' },
{ key: 'peak', label: '峰', color: '#f39c12' },
{ key: 'flat', label: '平', color: '#27ae60' },
{ key: 'valley', label: '谷', color: '#3498db' }
];
// 时段配置: { sharp: [11,12], peak: [9,10], flat: [7,8], valley: [0,1,2,3] }
let timePeriodConfig = { sharp: [], peak: [], flat: [], valley: [] };
let currentTpTool = 'select'; // 'select' or period key
// 初始化时段编辑器
function initTimePeriodEditor() {
const hoursRow = document.getElementById('tp-hours-row');
const periodRows = document.getElementById('tp-period-rows');
// 渲染小时标题行
let hoursHtml = '';
for (let h = 0; h < 24; h++) {
hoursHtml += `<div class="tp-hour-label">${h}</div>`;
}
hoursRow.innerHTML = hoursHtml;
// 渲染时段行
let rowsHtml = '';
TIME_PERIODS.forEach(period => {
rowsHtml += `
<div class="tp-period-row" data-period="${period.key}">
<div class="tp-period-label ${period.key}">${period.label}时</div>
<div class="tp-period-cells" id="tp-cells-${period.key}">
${Array.from({length: 24}, (_, h) =>
`<div class="tp-cell" data-hour="${h}" data-period="${period.key}" onclick="toggleTimePeriodCell(${h}, '${period.key}')"></div>`
).join('')}
</div>
</div>
`;
});
periodRows.innerHTML = rowsHtml;
updateTimePeriodSummary();
}
// 切换时段格子
function toggleTimePeriodCell(hour, periodKey) {
// 检查该小时是否已被其他时段占用
const occupiedBy = Object.entries(timePeriodConfig).find(([k, v]) => k !== periodKey && v.includes(hour));
if (occupiedBy) {
// 已被其他时段占用,移除占用并分配给当前时段
const [otherKey] = occupiedBy;
timePeriodConfig[otherKey] = timePeriodConfig[otherKey].filter(h => h !== hour);
updateCellDisplay(hour, otherKey);
}
// 切换当前时段
if (timePeriodConfig[periodKey].includes(hour)) {
timePeriodConfig[periodKey] = timePeriodConfig[periodKey].filter(h => h !== hour);
} else {
timePeriodConfig[periodKey].push(hour);
timePeriodConfig[periodKey].sort((a, b) => a - b);
}
updateCellDisplay(hour, periodKey);
updateTimePeriodSummary();
}
// 更新格子显示
function updateCellDisplay(hour, periodKey) {
// 清除所有时段中该小时的样式
TIME_PERIODS.forEach(p => {
const cell = document.querySelector(`.tp-cell[data-hour="${hour}"][data-period="${p.key}"]`);
if (cell) {
cell.className = 'tp-cell';
cell.textContent = '';
}
});
// 设置当前时段的样式
if (timePeriodConfig[periodKey].includes(hour)) {
const cell = document.querySelector(`.tp-cell[data-hour="${hour}"][data-period="${periodKey}"]`);
if (cell) {
cell.classList.add(periodKey);
cell.textContent = '✓';
}
}
}
// 更新时段摘要
function updateTimePeriodSummary() {
const summaryEl = document.getElementById('tp-summary');
const parts = TIME_PERIODS.map(p => {
const hours = timePeriodConfig[p.key];
const count = hours.length;
const hoursStr = count > 0 ? hours.map(h => `${h}`).join(',') : '未设置';
return `<span style="color:${p.color}"><b>${p.label}</b>: ${count} (${hoursStr})</span>`;
});
summaryEl.innerHTML = parts.join('');
}
// 设置时段配置(编辑时加载)
function setTimePeriodConfig(config) {
if (!config) {
timePeriodConfig = { sharp: [], peak: [], flat: [], valley: [] };
} else {
timePeriodConfig = {
sharp: config.sharp || [],
peak: config.peak || [],
flat: config.flat || [],
valley: config.valley || []
};
}
// 更新所有格子显示
TIME_PERIODS.forEach(period => {
for (let h = 0; h < 24; h++) {
updateCellDisplay(h, period.key);
}
});
updateTimePeriodSummary();
}
// 获取时段配置(保存时调用)
function getTimePeriodConfig() {
return { ...timePeriodConfig };
}
// 清空所有时段
function clearAllTimePeriods() {
timePeriodConfig = { sharp: [], peak: [], flat: [], valley: [] };
TIME_PERIODS.forEach(period => {
for (let h = 0; h < 24; h++) {
updateCellDisplay(h, period.key);
}
});
updateTimePeriodSummary();
}
// 当前实体加载状态
let currentEntityType = '';
let entityPage = 1;
@@ -1058,6 +1389,9 @@
const selectedFields = Array.from(document.querySelectorAll('#fields-list input:checked')).map(cb => cb.value);
const sumFields = Array.from(document.querySelectorAll('#sum-fields-list input:checked')).map(cb => cb.value);
// 获取时段配置
const timePeriods = getTimePeriodConfig();
if (!configName || !splitType || !splitValue) {
alert('请填写所有必填字段');
@@ -1075,7 +1409,8 @@
split_value: splitValue,
split_name: splitName,
selected_fields: selectedFields,
sum_fields: sumFields
sum_fields: sumFields,
time_periods: timePeriods
};
try {
@@ -1131,6 +1466,10 @@
// 加载字段并回显选中状态
await loadFields(config.selected_fields, config.sum_fields);
// 初始化时段编辑器并加载配置
initTimePeriodEditor();
setTimePeriodConfig(config.time_periods);
document.getElementById('config-modal').classList.add('active');
}
}