async function loadConfigs() { try { const response = await fetch('/api/config'); const result = await response.json(); if (result.success) { renderConfigList(result.data); } else { alert('加载配置失败: ' + result.message); } } catch (error) { alert('加载配置失败: ' + error.message); } } // 渲染配置列表 function renderConfigList(configs) { const tbody = document.getElementById('config-list'); if (configs.length === 0) { tbody.innerHTML = '暂无配置,请点击"创建配置"按钮'; return; } tbody.innerHTML = configs.map(config => ` ${config.config_name} ${config.split_type === 'company_id' ? '按企业ID' : '按用户ID'} ${config.split_name || config.split_value} ${config.selected_fields.length} ${config.is_active ? '已启用' : '已禁用'}
`).join(''); } // 显示创建模态框 function showCreateModal() { editingConfigId = null; document.getElementById('modal-title').textContent = '创建配置'; document.getElementById('config-name').value = ''; document.getElementById('split-type').value = ''; document.getElementById('split-value').innerHTML = ''; // 重置字段自定义名头 fieldCustomNames = {}; // 重置合并特来电配置 const mergeTelecomCheckbox = document.getElementById('merge-telecom'); const telecomVehicleNoInput = document.getElementById('telecom-vehicle-no'); 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(''); } // 设置时段配置(编辑时加载)