845 lines
31 KiB
JavaScript
845 lines
31 KiB
JavaScript
// 加载配置列表
|
|
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 = '<tr><td colspan="6" class="empty">暂无配置,请点击"创建配置"按钮</td></tr>';
|
|
return;
|
|
}
|
|
|
|
tbody.innerHTML = configs.map(config => `
|
|
<tr>
|
|
<td>${config.config_name}</td>
|
|
<td>${config.split_type === 'company_id' ? '按企业 ID' : config.split_type === 'user_id' ? '按用户 ID' : '按场站 ID'}</td>
|
|
<td>${config.split_name || config.split_value}</td>
|
|
<td>${config.selected_fields.length}</td>
|
|
<td>
|
|
<span class="status-badge ${config.is_active ? 'status-active' : 'status-inactive'}">
|
|
${config.is_active ? '已启用' : '已禁用'}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-warning" onclick="toggleConfig(${config.id}, ${config.is_active})">
|
|
${config.is_active ? '禁用' : '启用'}
|
|
</button>
|
|
<button class="btn btn-sm btn-primary" onclick="editConfig(${config.id})">编辑</button>
|
|
<button class="btn btn-sm btn-success" onclick="copyConfig(${config.id})">复制</button>
|
|
<button class="btn btn-sm btn-danger" onclick="deleteConfig(${config.id})">删除</button>
|
|
<button class="btn btn-sm" onclick="moveConfig(${config.id}, 'up')">↑</button>
|
|
<button class="btn btn-sm" onclick="moveConfig(${config.id}, 'down')">↓</button>
|
|
</td>
|
|
</tr>
|
|
`).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 = '<option value="">请先选择拆分方式</option>';
|
|
|
|
// 重置字段自定义名头
|
|
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('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 `
|
|
<div class="checkbox-item ${isSelected ? 'checked' : ''} ${isRequired ? 'required-field' : ''}" onclick="${isRequired ? '' : 'toggleCheckboxItem(this)'}">
|
|
<input type="checkbox" value="${field.key}"
|
|
${isSelected ? 'checked' : ''}
|
|
${isRequired ? 'disabled checked' : ''}
|
|
onchange="onFieldCheckboxChange(this)">
|
|
<label>${field.display}${isRequired ? ' <span class="required-badge">必选</span>' : ''}</label>
|
|
</div>
|
|
`;
|
|
}).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('field-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('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 = '<small style="color:#999;text-align:center;padding:8px;">请先在上方勾选字段</small>';
|
|
} 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 `
|
|
<div class="selected-field-item" data-field="${fieldKey}">
|
|
<span class="field-index">#${index + 1}</span>
|
|
<span class="field-name" title="${displayName}">${displayName}</span>
|
|
<input type="text" class="field-custom-name" placeholder="自定义报表名头" value="${customName}"
|
|
onchange="updateFieldCustomName('${fieldKey}', this.value)"
|
|
style="flex: 1; min-width: 100px; padding: 4px 8px; font-size: 12px;">
|
|
<div class="field-actions">
|
|
<button type="button" class="btn-move" onclick="moveFieldUp(${index})" ${isFirst ? 'disabled' : ''} title="上移">↑</button>
|
|
<button type="button" class="btn-move" onclick="moveFieldDown(${index})" ${isLast ? 'disabled' : ''} title="下移">↓</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).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 = '<small style="color:#999;padding:8px;">请先在上方勾选数值类型的字段</small>';
|
|
} else {
|
|
sumFieldsList.innerHTML = availableSumFields.map(field => `
|
|
<div class="checkbox-item ${currentSumFields.includes(field.key) ? 'checked' : ''}" onclick="toggleCheckboxItem(this)">
|
|
<input type="checkbox" value="${field.key}"
|
|
${currentSumFields.includes(field.key) ? 'checked' : ''}>
|
|
<label>${field.display}</label>
|
|
</div>
|
|
`).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 += `<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) {
|
|
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 `<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() {
|
|
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();
|
|
}
|
|
|
|
// 当前实体加载状态
|
|
let currentEntityType = '';
|
|
let entityPage = 1;
|
|
let currentSearch = '';
|
|
const entityPageSize = 100;
|
|
|
|
// 加载企业/用户列表(支持分页和搜索)
|
|
async function saveConfig() {
|
|
const configName = document.getElementById('config-name').value;
|
|
const splitType = document.getElementById('split-type').value;
|
|
// 处理多选:从 selectedEntityIds 获取选中的实体
|
|
const splitValue = Array.from(selectedEntityIds).join(',');
|
|
|
|
// 获取选中实体的名称(需要从 DOM 中获取)
|
|
const container = document.getElementById('entity-list');
|
|
const checkboxes = container.querySelectorAll('input[type="checkbox"]:checked');
|
|
const splitName = Array.from(checkboxes).map(cb => {
|
|
const item = cb.closest('.checkbox-item');
|
|
const span = item.querySelector('span');
|
|
return span ? span.textContent.trim() : '';
|
|
}).join(',');
|
|
|
|
// 获取字段顺序(从已选字段顺序列表获取)
|
|
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() || '';
|
|
|
|
if (!configName || !splitType || !splitValue) {
|
|
alert('请填写所有必填字段');
|
|
return;
|
|
}
|
|
|
|
if (selectedFields.length === 0) {
|
|
alert('请至少选择一个字段');
|
|
return;
|
|
}
|
|
|
|
// 确保充电量为必选字段
|
|
if (!selectedFields.includes('charge_degree')) {
|
|
selectedFields.push('charge_degree');
|
|
}
|
|
|
|
// 验证合并特来电配置
|
|
if (mergeTelecom && splitType !== 'company_id') {
|
|
alert('合并特来电数据仅支持按企业拆分');
|
|
return;
|
|
}
|
|
if (mergeTelecom && !telecomVehicleNo) {
|
|
alert('请输入特来电车量自编号');
|
|
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
|
|
};
|
|
|
|
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) {
|
|
alert(editingConfigId ? '配置更新成功' : '配置创建成功');
|
|
closeModal();
|
|
loadConfigs();
|
|
} else {
|
|
alert('保存失败: ' + result.message);
|
|
}
|
|
} catch (error) {
|
|
alert('保存失败: ' + 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 || '';
|
|
}
|
|
|
|
document.getElementById('config-modal').classList.add('active');
|
|
}
|
|
}
|
|
} catch (error) {
|
|
alert('加载配置失败: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// 加载实体用于编辑回显
|
|
async function loadEntitiesForEdit(splitType, selectedValues) {
|
|
const container = document.getElementById('entity-list');
|
|
|
|
if (!splitType) {
|
|
container.innerHTML = '<div class="empty">请先选择拆分方式</div>';
|
|
return;
|
|
}
|
|
|
|
const entityType = splitType === 'company_id' ? 'company' :
|
|
splitType === 'user_id' ? 'user' :
|
|
splitType === 'station_id' ? 'station' : '';
|
|
|
|
container.innerHTML = '<div class="loading">加载中...</div>';
|
|
|
|
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' : '';
|
|
return `
|
|
<div class="checkbox-item ${checkedClass}" onclick="toggleEntityCheckbox(this, '${item.value}')">
|
|
<input type="checkbox" value="${item.value}" ${isChecked} onclick="event.stopPropagation()">
|
|
<span>${item.name} ${item.typeLabel}</span>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
// 更新分页UI
|
|
currentEntityType = entityType;
|
|
entityPage = 1;
|
|
updateEntityPagination(totalPages, total);
|
|
updateSelectedCount();
|
|
}
|
|
} catch (error) {
|
|
console.error('加载列表失败:', error);
|
|
container.innerHTML = '<div class="empty">加载失败</div>';
|
|
}
|
|
}
|
|
|
|
// 复制配置
|
|
async function copyConfig(configId) {
|
|
try {
|
|
const response = await fetch(`/api/config/${configId}/copy`, {
|
|
method: 'POST'
|
|
});
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
alert('配置复制成功!');
|
|
loadConfigs();
|
|
} else {
|
|
alert('复制失败: ' + (result.message || result.error || '未知错误'));
|
|
}
|
|
} catch (error) {
|
|
console.error('复制配置失败:', error);
|
|
alert('复制失败,请稍后重试');
|
|
}
|
|
}
|
|
|
|
// 删除配置
|
|
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) {
|
|
alert('配置删除成功');
|
|
loadConfigs();
|
|
} else {
|
|
alert('删除失败: ' + result.message);
|
|
}
|
|
} catch (error) {
|
|
alert('删除失败: ' + 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 {
|
|
alert('操作失败: ' + result.message);
|
|
}
|
|
} catch (error) {
|
|
alert('操作失败: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// 移动配置
|
|
async function moveConfig(configId, direction) {
|
|
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) {
|
|
loadConfigs();
|
|
} else {
|
|
alert('移动失败: ' + result.message);
|
|
}
|
|
} catch (error) {
|
|
alert('移动失败: ' + error.message);
|
|
}
|
|
}
|
|
|
|
// 关闭模态框
|
|
function closeModal() {
|
|
document.getElementById('config-modal').classList.remove('active');
|
|
}
|
|
|
|
|
|
// 清空时段配置
|
|
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 onSplitTypeChange() {
|
|
const splitType = document.getElementById('split-type').value;
|
|
if (splitType) {
|
|
loadEntities(1, '');
|
|
} else {
|
|
document.getElementById('entity-list').innerHTML = '<div class="empty">请先选择拆分方式</div>';
|
|
}
|
|
}
|