// 当前配置列表缓存(用于乐观更新)
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 `
`;
}).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 = '';
}