diff --git a/assets/image_20260713160840329.png b/assets/image_20260713160840329.png new file mode 100644 index 0000000..d19b963 Binary files /dev/null and b/assets/image_20260713160840329.png differ diff --git a/public/static/js/common.js b/public/static/js/common.js index 9f59d4b..34f49ee 100644 --- a/public/static/js/common.js +++ b/public/static/js/common.js @@ -66,3 +66,347 @@ function showCreateModal() { const mergeTelecomGroup = document.getElementById('merge-telecom-group'); if (mergeTelecomCheckbox) mergeTelecomCheckbox.checked = false; if (telecomVehicleNoInput) telecomVehicleNoInput.value = ''; + if (mergeTelecomGroup) mergeTelecomGroup.style.display = 'none'; + toggleTelecomVehicleNo(); + + loadFields(); + initTimePeriodEditor(); + clearAllTimePeriods(); + document.getElementById('config-modal').classList.add('active'); +} + +// 加载字段列表 +async function loadFields(selectedFields = [], sumFields = []) { + try { + const response = await fetch('/api/fields'); + const result = await response.json(); + + if (result.success) { + allFields = result.data; + renderFields(selectedFields, sumFields); + } + } catch (error) { + console.error('加载字段失败:', error); + } +} + +// 可求和的数值类型字段 +const SUMMABLE_FIELD_KEYS = [ + 'charge_degree', 'charge_ah', 'charge_begin_soc', 'charge_end_soc', + 'receivable_electric_fee', 'receivable_service_fee', 'receivable_total_fee', + 'charge_elecfee_amount', 'charge_elecfee_cost_amount', 'charge_service_amount', + 'charging_amt', 'actual_pay_amount', 'pay_amount', + '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', + // 分时段电量 + 'sharp_electricity', 'peak_electricity', 'flat_electricity', 'valley_electricity' +]; + +// 渲染字段列表 +function renderFields(selectedFields = [], sumFields = []) { + const fieldsList = document.getElementById('fields-list'); + const sumFieldsList = document.getElementById('sum-fields-list'); + + fieldsList.innerHTML = allFields.map(field => { + const isSelected = selectedFields.includes(field.key); + const isRequired = field.key === 'charge_degree'; // 充电量为必选字段 + return ` +
+ + +
+ `; + }).join(''); + + // 初始化求和字段列表(只显示已选字段中的可求和字段) + refreshSumFieldsList(selectedFields, sumFields); +} + +// 点击 checkbox-item 容器时切换选中状态 +function toggleCheckboxItem(item) { + const cb = item.querySelector('input[type="checkbox"]'); + if (event.target === cb) return; // 如果点击的就是 checkbox 本身,不重复触发 + cb.checked = !cb.checked; + item.classList.toggle('checked', cb.checked); + onFieldCheckboxChange(cb); +} + +// 字段 checkbox 变化时更新求和字段列表和已选字段顺序 +function onFieldCheckboxChange(cb) { + // 防止取消选择必选字段(充电量) + if (cb.value === 'charge_degree' && !cb.checked) { + cb.checked = true; + return; + } + + const item = cb.closest('.checkbox-item'); + if (item) item.classList.toggle('checked', cb.checked); + + const fieldsList = document.getElementById('fields-list'); + const checkedFields = Array.from(fieldsList.querySelectorAll('input:checked')).map(c => c.value); + + // 保留当前求和字段的选择状态 + const sumFieldsList = document.getElementById('sum-fields-list'); + const currentSumFields = sumFieldsList ? Array.from(sumFieldsList.querySelectorAll('input:checked')).map(c => c.value) : []; + + refreshSumFieldsList(checkedFields, currentSumFields); + refreshSelectedFieldsOrder(checkedFields); +} + +// 字段自定义名头存储 +let fieldCustomNames = {}; + +// 刷新已选字段顺序列表 +function refreshSelectedFieldsOrder(selectedFields, customNames = null) { + const orderGroup = document.getElementById('selected-fields-order-group'); + const orderList = document.getElementById('selected-fields-order'); + if (!orderList) return; + + // 如果传入了自定义名头,更新全局变量 + if (customNames !== null) { + fieldCustomNames = {...customNames}; + } + + // 根据是否有已选字段,显示/隐藏字段顺序区域 + if (orderGroup) { + orderGroup.style.display = selectedFields.length > 0 ? 'block' : 'none'; + } + + if (selectedFields.length === 0) { + orderList.innerHTML = '请先在上方勾选字段'; + } else { + orderList.innerHTML = selectedFields.map((fieldKey, index) => { + const field = allFields.find(f => f.key === fieldKey); + const displayName = field ? field.display : fieldKey; + const customName = fieldCustomNames[fieldKey] || ''; + const isFirst = index === 0; + const isLast = index === selectedFields.length - 1; + return ` +
+ #${index + 1} + ${displayName} + +
+ + +
+
+ `; + }).join(''); + } +} + +// 更新字段自定义名头 +function updateFieldCustomName(fieldKey, customName) { + if (customName && customName.trim()) { + fieldCustomNames[fieldKey] = customName.trim(); + } else { + delete fieldCustomNames[fieldKey]; + } +} + +// 获取字段自定义名头 +function getFieldCustomNames() { + return {...fieldCustomNames}; +} + +// 字段上移 +function moveFieldUp(index) { + if (index <= 0) return; + const orderList = document.getElementById('selected-fields-order'); + const items = Array.from(orderList.querySelectorAll('.selected-field-item')); + const item = items[index]; + const prevItem = items[index - 1]; + orderList.insertBefore(item, prevItem); + updateFieldIndexes(); +} + +// 字段下移 +function moveFieldDown(index) { + const orderList = document.getElementById('selected-fields-order'); + const items = Array.from(orderList.querySelectorAll('.selected-field-item')); + if (index >= items.length - 1) return; + const item = items[index]; + const nextItem = items[index + 1]; + orderList.insertBefore(nextItem, item); + updateFieldIndexes(); +} + +// 更新字段序号 +function updateFieldIndexes() { + const orderList = document.getElementById('selected-fields-order'); + const items = orderList.querySelectorAll('.selected-field-item'); + items.forEach((item, index) => { + const indexSpan = item.querySelector('.field-index'); + if (indexSpan) indexSpan.textContent = `#${index + 1}`; + + // 更新按钮状态 + const upBtn = item.querySelector('.btn-move[title="上移"]'); + const downBtn = item.querySelector('.btn-move[title="下移"]'); + if (upBtn) upBtn.disabled = index === 0; + if (downBtn) downBtn.disabled = index === items.length - 1; + }); +} + +// 获取当前字段顺序 +function getSelectedFieldsOrder() { + const orderList = document.getElementById('selected-fields-order'); + if (!orderList) return []; + const items = orderList.querySelectorAll('.selected-field-item'); + return Array.from(items).map(item => item.dataset.field); +} + +// 刷新求和字段列表(仅显示已选字段中的数值类型字段) +function refreshSumFieldsList(selectedFields, currentSumFields) { + const sumFieldsList = document.getElementById('sum-fields-list'); + const availableSumFields = allFields.filter(f => + selectedFields.includes(f.key) && SUMMABLE_FIELD_KEYS.includes(f.key) + ); + + if (availableSumFields.length === 0) { + sumFieldsList.innerHTML = '请先在上方勾选数值类型的字段'; + } else { + sumFieldsList.innerHTML = availableSumFields.map(field => ` +
+ + +
+ `).join(''); + } +} + +// 切换特来电车量自编号输入框显示 +function toggleTelecomVehicleNo() { + const mergeTelecom = document.getElementById('merge-telecom').checked; + const vehicleNoGroup = document.getElementById('telecom-vehicle-no-group'); + if (vehicleNoGroup) { + vehicleNoGroup.style.display = mergeTelecom ? 'block' : 'none'; + } +} + +// 更新已选实体计数 +function updateSelectedCount() { + const select = document.getElementById('split-value'); + const countEl = document.getElementById('selected-entity-count'); + const count = select.selectedOptions.length; + if (count > 0) { + countEl.textContent = '已选 ' + count + ' 项'; + countEl.style.display = 'inline'; + } else { + countEl.style.display = 'none'; + } +} + +// ========== 时段编辑器 ========== +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 += `
${h}
`; + } + hoursRow.innerHTML = hoursHtml; + + // 渲染时段行 + let rowsHtml = ''; + TIME_PERIODS.forEach(period => { + rowsHtml += ` +
+
${period.label}时
+
+ ${Array.from({length: 24}, (_, h) => + `
` + ).join('')} +
+
+ `; + }); + periodRows.innerHTML = rowsHtml; + + updateTimePeriodSummary(); +} + +// 切换时段格子 +function toggleTimePeriodCell(hour, periodKey) { + console.log('toggleTimePeriodCell called:', 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 = ''; + } + }); + + // 检查该小时属于哪个时段,并设置对应的样式 + for (const p of TIME_PERIODS) { + if (timePeriodConfig[p.key].includes(hour)) { + const cell = document.querySelector(`.tp-cell[data-hour="${hour}"][data-period="${p.key}"]`); + if (cell) { + cell.classList.add(p.key); + cell.textContent = '✓'; + } + break; // 每个小时只能属于一个时段 + } + } +} + +// 更新时段摘要 +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 `${p.label}时: ${count}个 (${hoursStr})`; + }); + summaryEl.innerHTML = parts.join(''); +} + +// 设置时段配置(编辑时加载) diff --git a/public/static/js/history.js b/public/static/js/history.js index df867a7..899316f 100644 --- a/public/static/js/history.js +++ b/public/static/js/history.js @@ -222,4 +222,3 @@ async function loadHistoryWithDate(page, startDate, endDate) { // 初始化 loadConfigs(); loadFields(); -