// 当前配置列表缓存(用于乐观更新) let currentConfigList = []; // 加载配置列表 async function loadConfigs() { try { const response = await fetch('/api/config'); const result = await response.json(); if (result.success) { currentConfigList = result.data; renderConfigList(currentConfigList); } else { showToast('加载配置失败:' + result.message); } } catch (error) { showToast('加载配置失败:' + error.message); } } // 渲染配置列表 function renderConfigList(configs) { const tbody = document.getElementById('config-list'); if (configs.length === 0) { tbody.innerHTML = '暂无配置,请点击"创建配置"按钮'; return; } tbody.innerHTML = configs.map((config, index) => { const isFirst = index === 0; const isLast = index === configs.length - 1; const sortOrder = index + 1; return ` ${sortOrder} ${config.config_name} ${config.split_type === 'company_id' ? '按企业 ID' : config.split_type === 'user_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 = ''; // 清空已选实体 selectedEntityIds.clear(); // 清空实体列表 document.getElementById('entity-list').innerHTML = '
请先选择拆分方式
'; document.getElementById('entity-search').value = ''; document.getElementById('selected-count').textContent = '已选:0 个'; // 重置字段自定义名头 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(); // 重置月度总计开关 const showMonthlyTotalCheckbox = document.getElementById('show-monthly-total'); if (showMonthlyTotalCheckbox) showMonthlyTotalCheckbox.checked = false; // 重置找平电量开关(默认开启) const balanceElectricityCheckbox = document.getElementById('balance-electricity'); if (balanceElectricityCheckbox) balanceElectricityCheckbox.checked = true; // 重置自定义服务费单价 const customServiceFeePriceInput = document.getElementById('custom-service-fee-price'); if (customServiceFeePriceInput) customServiceFeePriceInput.value = ''; // 重置自定义服务费表头名称 const customServiceFeeNameInput = document.getElementById('custom-service-fee-name'); if (customServiceFeeNameInput) customServiceFeeNameInput.value = ''; // 重置显示自定义服务费字段开关 const showCustomServiceFeeInput = document.getElementById('show-custom-service-fee'); if (showCustomServiceFeeInput) showCustomServiceFeeInput.checked = false; // 重置显示实收总金额字段开关 const showTotalAmountInput = document.getElementById('show-total-amount'); if (showTotalAmountInput) showTotalAmountInput.checked = false; // 重置实收总金额表头名称 const totalAmountNameInput = document.getElementById('total-amount-name'); if (totalAmountNameInput) totalAmountNameInput.value = ''; 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', // 虚拟字段(自定义计算字段) 'custom_service_fee', 'custom_total_amount' ]; // 渲染字段列表 function renderFields(selectedFields = [], sumFields = []) { const fieldsList = document.getElementById('field-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 currentOrder = getSelectedFieldsOrder(); // 获取所有勾选的字段 const fieldsList = document.getElementById('field-list'); const allCheckedFields = Array.from(fieldsList.querySelectorAll('input:checked')).map(c => c.value); // 构建新的字段顺序:保留当前顺序,添加新勾选的字段,移除取消勾选的字段 let newOrder = []; for (const fieldKey of currentOrder) { if (allCheckedFields.includes(fieldKey)) { newOrder.push(fieldKey); } } // 添加新勾选的字段(不在当前顺序中的) for (const fieldKey of allCheckedFields) { if (!newOrder.includes(fieldKey)) { newOrder.push(fieldKey); } } // 保留当前求和字段的选择状态 const sumFieldsList = document.getElementById('sum-fields-list'); const currentSumFields = sumFieldsList ? Array.from(sumFieldsList.querySelectorAll('input:checked')).map(c => c.value) : []; refreshSumFieldsList(newOrder, currentSumFields); refreshSelectedFieldsOrder(newOrder); } // 刷新已选字段顺序列表 function refreshSelectedFieldsOrder(selectedFields, customNames = null) { const orderGroup = document.getElementById('field-order-section'); 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} (${fieldKey})
`; }).join(''); } } // 更新字段自定义名头 function updateFieldCustomName(fieldKey, customName) { if (customName && customName.trim()) { fieldCustomNames[fieldKey] = customName.trim(); } else { delete fieldCustomNames[fieldKey]; } } // 获取字段自定义名头 function getFieldCustomNames() { return {...fieldCustomNames}; } // 字段上移 function moveFieldUp(fieldKey) { const orderList = document.getElementById('selected-fields-order'); const items = Array.from(orderList.querySelectorAll('.selected-field-item')); const index = items.findIndex(item => item.dataset.field === fieldKey); if (index <= 0) return; const item = items[index]; const prevItem = items[index - 1]; orderList.insertBefore(item, prevItem); updateFieldIndexes(); } // 字段下移 function moveFieldDown(fieldKey) { const orderList = document.getElementById('selected-fields-order'); const items = Array.from(orderList.querySelectorAll('.selected-field-item')); const index = items.findIndex(item => item.dataset.field === fieldKey); if (index < 0 || 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 onVirtualFieldToggle(cb) { const fieldKey = cb.id === 'show-custom-service-fee' ? 'custom_service_fee' : 'custom_total_amount'; const isChecked = cb.checked; // 获取当前字段顺序 const currentOrder = getSelectedFieldsOrder(); let newOrder = []; if (isChecked) { // 勾选:添加字段到列表末尾 if (!currentOrder.includes(fieldKey)) { newOrder = [...currentOrder, fieldKey]; } else { newOrder = currentOrder; } } else { // 取消勾选:从列表中移除字段 newOrder = currentOrder.filter(f => f !== fieldKey); } // 获取当前求和字段选择状态 const sumFieldsList = document.getElementById('sum-fields-list'); const currentSumFields = sumFieldsList ? Array.from(sumFieldsList.querySelectorAll('input:checked')).map(c => c.value) : []; // 更新字段顺序列表和求和字段列表 refreshSelectedFieldsOrder(newOrder); refreshSumFieldsList(newOrder, currentSumFields); // 如果勾选了字段,更新字段列表中的checkbox状态(保持一致) const fieldList = document.getElementById('field-list'); const fieldCb = fieldList?.querySelector(`input[value="${fieldKey}"]`); if (fieldCb) { fieldCb.checked = isChecked; fieldCb.closest('.checkbox-item')?.classList.toggle('checked', isChecked); } } // 获取当前字段顺序 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-section'); if (vehicleNoGroup) { vehicleNoGroup.style.display = mergeTelecom ? 'block' : '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('time-period-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(''); } // 设置时段配置(编辑时加载) 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() { console.log('getTimePeriodConfig called, current config:', timePeriodConfig); 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(); } // 当前实体加载状态 // 加载企业/用户列表(支持分页和搜索) async function saveConfig() { const configName = document.getElementById('config-name').value; const splitType = document.getElementById('split-type').value; // 处理多选:从 selectedEntityIds 获取选中的实体 const splitValue = Array.from(selectedEntityIds).join(','); // 通过 API 查询所有已选实体的名称(确保翻页后也能正确获取) let splitName = ''; if (selectedEntityIds.size > 0) { const entityType = splitType === 'company_id' ? 'company' : splitType === 'user_id' ? 'user' : 'station'; try { const response = await fetch('/api/entities/by_ids', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: entityType, ids: Array.from(selectedEntityIds) }) }); const result = await response.json(); if (result.success && result.data) { const idSet = new Set(Array.from(selectedEntityIds)); const sortedEntities = result.data.filter(e => { const value = splitType === 'company_id' ? e.company_id : splitType === 'user_id' ? e.user_id : e.station_id; return idSet.has(String(value)); }).sort((a, b) => { const aVal = splitType === 'company_id' ? a.company_id : splitType === 'user_id' ? a.user_id : a.station_id; const bVal = splitType === 'company_id' ? b.company_id : splitType === 'user_id' ? b.user_id : b.station_id; const aIdx = Array.from(selectedEntityIds).indexOf(String(aVal)); const bIdx = Array.from(selectedEntityIds).indexOf(String(bVal)); return aIdx - bIdx; }); splitName = sortedEntities.map(e => { if (splitType === 'company_id') return e.company_name; if (splitType === 'user_id') return e.user_name; return e.station_name; }).join(','); } } catch (err) { console.error('获取实体名称失败:', err); } } // 获取字段顺序(从已选字段顺序列表获取) const selectedFields = getSelectedFieldsOrder(); const sumFields = Array.from(document.querySelectorAll('#sum-fields-list input:checked')).map(cb => cb.value); // 获取时段配置 const timePeriods = getTimePeriodConfig(); // 获取合并特来电配置 const mergeTelecom = document.getElementById('merge-telecom')?.checked || false; const telecomVehicleNo = document.getElementById('telecom-vehicle-no')?.value.trim() || ''; // 获取月度总计开关 const showMonthlyTotal = document.getElementById('show-monthly-total')?.checked || false; // 获取找平电量开关(默认开启) const balanceElectricity = document.getElementById('balance-electricity')?.checked || false; // 获取自定义服务费单价 const customServiceFeePriceInput = document.getElementById('custom-service-fee-price'); const customServiceFeePrice = customServiceFeePriceInput?.value !== '' ? parseFloat(customServiceFeePriceInput.value) : null; // 获取自定义服务费表头名称 const customServiceFeeNameInput = document.getElementById('custom-service-fee-name'); const customServiceFeeName = customServiceFeeNameInput?.value.trim() || null; // 获取显示自定义服务费字段开关 const showCustomServiceFee = document.getElementById('show-custom-service-fee')?.checked || false; // 获取显示实收总金额字段开关 const showTotalAmount = document.getElementById('show-total-amount')?.checked || false; // 获取实收总金额表头名称 const totalAmountNameInput = document.getElementById('total-amount-name'); const totalAmountName = totalAmountNameInput?.value.trim() || null; if (!configName || !splitType || !splitValue) { showToast('请填写所有必填字段'); return; } if (selectedFields.length === 0) { showToast('请至少选择一个字段'); return; } // 确保充电量为必选字段 if (!selectedFields.includes('charge_degree')) { selectedFields.push('charge_degree'); } // 验证合并特来电配置 if (mergeTelecom && splitType !== 'company_id') { showToast('合并特来电数据仅支持按企业拆分'); return; } if (mergeTelecom && !telecomVehicleNo) { showToast('请输入特来电车量自编号'); return; } // 获取字段自定义名头 const fieldCustomNamesData = getFieldCustomNames(); const data = { config_name: configName, split_type: splitType, split_value: splitValue, split_name: splitName, selected_fields: selectedFields, sum_fields: sumFields, time_periods: timePeriods, merge_telecom: mergeTelecom, telecom_vehicle_no: telecomVehicleNo, field_custom_names: fieldCustomNamesData, show_monthly_total: showMonthlyTotal, balance_electricity: balanceElectricity, custom_service_fee_price: customServiceFeePrice, custom_service_fee_name: customServiceFeeName, show_custom_service_fee: showCustomServiceFee, show_total_amount: showTotalAmount, total_amount_name: totalAmountName }; try { let response; if (editingConfigId) { response = await fetch(`/api/config/${editingConfigId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); } else { response = await fetch('/api/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); } const result = await response.json(); if (result.success) { showToast(editingConfigId ? '配置更新成功' : '配置创建成功'); closeModal(); loadConfigs(); } else { showToast('保存失败: ' + result.message); } } catch (error) { showToast('保存失败: ' + error.message); } } // 编辑配置 async function editConfig(configId) { try { const response = await fetch('/api/config'); const result = await response.json(); if (result.success) { const config = result.data.find(c => c.id === configId); if (config) { editingConfigId = configId; document.getElementById('modal-title').textContent = '编辑配置'; document.getElementById('config-name').value = config.config_name; document.getElementById('split-type').value = config.split_type; // 获取已选择的值 const splitValues = config.split_value.split(','); // 加载实体并回显 await loadEntitiesForEdit(config.split_type, splitValues); // 加载字段并回显选中状态 await loadFields(config.selected_fields, config.sum_fields); // 加载字段顺序和自定义名头 const customNames = config.field_custom_names || {}; refreshSelectedFieldsOrder(config.selected_fields, customNames); // 初始化时段编辑器并加载配置 initTimePeriodEditor(); setTimePeriodConfig(config.time_periods); // 加载合并特来电配置 const mergeTelecomCheckbox = document.getElementById('merge-telecom'); const telecomVehicleNoInput = document.getElementById('telecom-vehicle-no'); if (mergeTelecomCheckbox) { mergeTelecomCheckbox.checked = config.merge_telecom === 1 || config.merge_telecom === true; toggleTelecomVehicleNo(); } if (telecomVehicleNoInput) { telecomVehicleNoInput.value = config.telecom_vehicle_no || ''; } // 加载月度总计开关 const showMonthlyTotalCheckbox = document.getElementById('show-monthly-total'); if (showMonthlyTotalCheckbox) { showMonthlyTotalCheckbox.checked = config.show_monthly_total === 1 || config.show_monthly_total === true; } // 加载找平电量开关(兼容历史配置:NULL/未设置时默认开启) const balanceElectricityCheckbox = document.getElementById('balance-electricity'); if (balanceElectricityCheckbox) { // balance_electricity 为 null/undefined 时默认开启,为 0 时关闭 balanceElectricityCheckbox.checked = config.balance_electricity === null || config.balance_electricity === undefined || config.balance_electricity === 1 || config.balance_electricity === true; } // 加载自定义服务费单价 const customServiceFeePriceInput = document.getElementById('custom-service-fee-price'); if (customServiceFeePriceInput) { customServiceFeePriceInput.value = config.custom_service_fee_price !== null && config.custom_service_fee_price !== undefined ? config.custom_service_fee_price : ''; } // 加载自定义服务费表头名称 const customServiceFeeNameInput = document.getElementById('custom-service-fee-name'); if (customServiceFeeNameInput) { customServiceFeeNameInput.value = config.custom_service_fee_name || ''; } // 加载显示自定义服务费字段开关 const showCustomServiceFeeInput = document.getElementById('show-custom-service-fee'); if (showCustomServiceFeeInput) { showCustomServiceFeeInput.checked = config.show_custom_service_fee === 1; } // 加载显示实收总金额字段开关 const showTotalAmountInput = document.getElementById('show-total-amount'); if (showTotalAmountInput) { showTotalAmountInput.checked = config.show_total_amount === 1; } // 加载实收总金额表头名称 const totalAmountNameInput = document.getElementById('total-amount-name'); if (totalAmountNameInput) { totalAmountNameInput.value = config.total_amount_name || ''; } document.getElementById('config-modal').classList.add('active'); } } } catch (error) { showToast('加载配置失败: ' + error.message); } } // 加载实体用于编辑回显 async function loadEntitiesForEdit(splitType, selectedValues) { const container = document.getElementById('entity-list'); if (!splitType) { container.innerHTML = '
请先选择拆分方式
'; return; } const entityType = splitType === 'company_id' ? 'company' : splitType === 'user_id' ? 'user' : splitType === 'station_id' ? 'station' : ''; container.innerHTML = '
加载中...
'; try { // 先查询已选择的实体信息 let selectedEntities = []; if (selectedValues.length > 0 && selectedValues[0] !== '') { const response = await fetch('/api/entities/by_ids', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: entityType, ids: selectedValues }) }); const result = await response.json(); if (result.success) { selectedEntities = result.data; } } // 加载第一页数据 const response = await fetch(`/api/entities?type=${entityType}&page=1&page_size=${entityPageSize}`); const result = await response.json(); if (result.success) { const entities = result.data; const total = result.total || 0; const totalPages = Math.ceil(total / entityPageSize); // 构建已选择实体的ID集合 const selectedIdSet = new Set(selectedValues); // 清空并重建 selectedEntityIds selectedEntityIds.clear(); selectedValues.forEach(v => selectedEntityIds.add(String(v))); // 合并已选择的实体和第一页数据(去重) const mergedEntities = []; const addedIds = new Set(); // 先添加已选择的实体(确保它们显示在顶部) for (const e of selectedEntities) { let value = ''; let name = ''; let typeLabel = ''; if (splitType === 'company_id') { value = e.company_id; name = e.company_name; } else if (splitType === 'user_id') { value = e.user_id; name = e.user_name; typeLabel = e.order_type === 3 ? '(企业用户)' : '(普通用户)'; if (e.phone) { name = `${e.user_name} (${e.phone})`; } } else if (splitType === 'station_id') { value = e.station_id; name = e.station_name; } if (!addedIds.has(String(value))) { mergedEntities.push({ value, name, typeLabel, isSelected: true }); addedIds.add(String(value)); } } // 再添加第一页数据(跳过已添加的) for (const e of entities) { let value = ''; let name = ''; let typeLabel = ''; if (splitType === 'company_id') { value = e.company_id; name = e.company_name; } else if (splitType === 'user_id') { value = e.user_id; name = e.user_name; typeLabel = e.order_type === 3 ? '(企业用户)' : '(普通用户)'; if (e.phone) { name = `${e.user_name} (${e.phone})`; } } else if (splitType === 'station_id') { value = e.station_id; name = e.station_name; } if (!addedIds.has(String(value))) { const isSelected = selectedIdSet.has(String(value)); mergedEntities.push({ value, name, typeLabel, isSelected }); addedIds.add(String(value)); } } // 渲染 checkbox 列表 container.innerHTML = mergedEntities.map(item => { const isChecked = item.isSelected ? 'checked' : ''; const checkedClass = item.isSelected ? 'checked' : ''; const topClass = item.isSelected ? 'entity-selected-top' : ''; return `
${item.name} ${item.typeLabel}
`; }).join(''); // 更新分页UI currentEntityType = entityType; entityPage = 1; updateEntityPagination(totalPages, total); updateSelectedCount(); } } catch (error) { console.error('加载列表失败:', error); container.innerHTML = '
加载失败
'; } } // 复制配置 async function copyConfig(configId) { try { const response = await fetch(`/api/config/${configId}/copy`, { method: 'POST' }); const result = await response.json(); if (result.success) { showToast('配置复制成功!'); loadConfigs(); } else { showToast('复制失败: ' + (result.message || result.error || '未知错误')); } } catch (error) { console.error('复制配置失败:', error); showToast('复制失败,请稍后重试'); } } // 删除配置 async function deleteConfig(configId) { if (!confirm('确定要删除这个配置吗?')) { return; } try { const response = await fetch(`/api/config/${configId}`, { method: 'DELETE' }); const result = await response.json(); if (result.success) { showToast('配置删除成功'); loadConfigs(); } else { showToast('删除失败: ' + result.message); } } catch (error) { showToast('删除失败: ' + error.message); } } // 启用/禁用配置 async function toggleConfig(configId) { try { const response = await fetch(`/api/config/${configId}/toggle`, { method: 'POST' }); const result = await response.json(); if (result.success) { loadConfigs(); } else { showToast('操作失败: ' + result.message); } } catch (error) { showToast('操作失败: ' + error.message); } } // 移动配置 async function moveConfig(configId, direction) { // 找到当前配置索引 const currentIndex = currentConfigList.findIndex(c => c.id === configId); if (currentIndex === -1) return; // 边界检查 const targetIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1; if (targetIndex < 0 || targetIndex >= currentConfigList.length) return; // 乐观更新:先在前端交换,立即刷新UI const temp = currentConfigList[currentIndex]; currentConfigList[currentIndex] = currentConfigList[targetIndex]; currentConfigList[targetIndex] = temp; renderConfigList(currentConfigList); // 异步同步到后端 try { const response = await fetch(`/api/config/${configId}/move`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ direction }) }); const result = await response.json(); if (!result.success) { // 失败了就回滚 const rollbackIndex = currentConfigList.findIndex(c => c.id === configId); const rollbackTarget = direction === 'up' ? rollbackIndex + 1 : rollbackIndex - 1; if (rollbackTarget >= 0 && rollbackTarget < currentConfigList.length) { const temp = currentConfigList[rollbackIndex]; currentConfigList[rollbackIndex] = currentConfigList[rollbackTarget]; currentConfigList[rollbackTarget] = temp; renderConfigList(currentConfigList); } showToast('移动失败: ' + result.message); } } catch (error) { // 网络错误也回滚 const rollbackIndex = currentConfigList.findIndex(c => c.id === configId); const rollbackTarget = direction === 'up' ? rollbackIndex + 1 : rollbackIndex - 1; if (rollbackTarget >= 0 && rollbackTarget < currentConfigList.length) { const temp = currentConfigList[rollbackIndex]; currentConfigList[rollbackIndex] = currentConfigList[rollbackTarget]; currentConfigList[rollbackTarget] = temp; renderConfigList(currentConfigList); } showToast('移动失败: ' + error.message); } } // 关闭模态框 function closeModal() { document.getElementById('config-modal').classList.remove('active'); selectedEntityIds.clear(); } // 清空时段配置 function clearTimePeriods() { timePeriodConfig = { sharp: [], peak: [], flat: [], valley: [] }; updateTimePeriodSummary(); } // 设置默认时段配置 function setDefaultTimePeriods() { timePeriodConfig = { sharp: [10, 11, 14, 15], peak: [8, 9, 12, 13, 16, 17, 18, 19], flat: [7, 20, 21, 22], valley: [0, 1, 2, 3, 4, 5, 6, 23] }; updateTimePeriodSummary(); } // 导出配置 function exportConfigs() { try { window.open('/api/config/export', '_blank'); } catch (error) { showToast('导出失败: ' + error.message); } } // 下载导入模板 function downloadTemplate() { try { window.open('/api/config/template', '_blank'); } catch (error) { showToast('模板下载失败: ' + error.message); } } // 导入配置 async function importConfigs(event) { const fileInput = event.target; const file = fileInput.files[0]; if (!file) return; if (!file.name.endsWith('.xlsx') && !file.name.endsWith('.xls')) { showToast('只支持 Excel 格式(.xlsx/.xls)的配置文件'); fileInput.value = ''; return; } if (!confirm('确定要导入配置吗?\n导入的配置会自动命名为"原名称_导入",默认禁用,排序号追加到末尾。')) { fileInput.value = ''; return; } const formData = new FormData(); formData.append('file', file); try { const response = await fetch('/api/config/import', { method: 'POST', body: formData }); const result = await response.json(); if (result.success) { let message = result.message; if (result.data && result.data.fail_messages && result.data.fail_messages.length > 0) { message += '\n\n失败详情:\n' + result.data.fail_messages.join('\n'); } showToast(message); loadConfigs(); } else { showToast('导入失败: ' + (result.message || '未知错误')); } } catch (error) { showToast('导入失败: ' + error.message); } fileInput.value = ''; }