diff --git a/lib/api/config_api.py b/lib/api/config_api.py index 448f99c..d90b364 100644 --- a/lib/api/config_api.py +++ b/lib/api/config_api.py @@ -60,6 +60,13 @@ def create_config(): show_custom_service_fee = 1 if data.get('show_custom_service_fee') else 0 show_total_amount = 1 if data.get('show_total_amount') else 0 total_amount_name = data.get('total_amount_name') + # 找平电量开关:默认开启(1=开启,0=关闭) + # 当数据中的'未在前端传递时,默认为1(开启),保持与历史配置的兼容性 + balance_electricity = data.get('balance_electricity') + if balance_electricity is None: + balance_electricity = 1 + else: + balance_electricity = 1 if balance_electricity else 0 if not config_name or not split_type or not split_value: log_warning('[配置API] 创建配置失败:缺少必填字段', 'api') @@ -81,9 +88,9 @@ def create_config(): (id, config_name, split_type, split_value, split_name, selected_fields, sum_fields, time_periods, merge_telecom, telecom_vehicle_no, field_custom_names, show_monthly_total, custom_service_fee_price, custom_service_fee_name, - show_custom_service_fee, show_total_amount, total_amount_name, + show_custom_service_fee, show_total_amount, total_amount_name, balance_electricity, is_active, sort_order, create_time, update_time) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 1, %s, NOW(), NOW()) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 1, %s, NOW(), NOW()) """ params = ( @@ -104,6 +111,7 @@ def create_config(): show_custom_service_fee, show_total_amount, total_amount_name if total_amount_name else None, + balance_electricity, sort_order ) @@ -148,6 +156,12 @@ def update_config(config_id): show_custom_service_fee = 1 if data.get('show_custom_service_fee') else 0 show_total_amount = 1 if data.get('show_total_amount') else 0 total_amount_name = data.get('total_amount_name') + # 找平电量开关:默认开启(1=开启,0=关闭) + balance_electricity = data.get('balance_electricity') + if balance_electricity is None: + balance_electricity = 1 + else: + balance_electricity = 1 if balance_electricity else 0 # 查询原配置,检查拆分是否变化 old_configs = execute_query('SELECT * FROM t_daily_report_config WHERE id = %s', (config_id,)) @@ -162,6 +176,7 @@ def update_config(config_id): merge_telecom = %s, telecom_vehicle_no = %s, field_custom_names = %s, show_monthly_total = %s, custom_service_fee_price = %s, custom_service_fee_name = %s, show_custom_service_fee = %s, show_total_amount = %s, total_amount_name = %s, + balance_electricity = %s, update_time = NOW() WHERE id = %s """ @@ -183,6 +198,7 @@ def update_config(config_id): show_custom_service_fee, show_total_amount, total_amount_name if total_amount_name else None, + balance_electricity, config_id ) @@ -257,8 +273,8 @@ def copy_config(config_id): # 插入新配置 execute_insert( '''INSERT INTO t_daily_report_config - (id, config_name, split_type, split_value, split_name, selected_fields, sum_fields, time_periods, merge_telecom, telecom_vehicle_no, field_custom_names, show_monthly_total, custom_service_fee_price, custom_service_fee_name, show_custom_service_fee, show_total_amount, total_amount_name, is_active, sort_order, create_time, update_time) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW())''', + (id, config_name, split_type, split_value, split_name, selected_fields, sum_fields, time_periods, merge_telecom, telecom_vehicle_no, field_custom_names, show_monthly_total, custom_service_fee_price, custom_service_fee_name, show_custom_service_fee, show_total_amount, total_amount_name, balance_electricity, is_active, sort_order, create_time, update_time) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW())''', ( new_id, new_name, @@ -277,6 +293,8 @@ def copy_config(config_id): original.get('show_custom_service_fee', 0), original.get('show_total_amount', 0), original.get('total_amount_name'), + # 找平电量:复制原配置的设置,若原配置无此字段(NULL)则默认开启(1) + original.get('balance_electricity') if original.get('balance_electricity') is not None else 1, original['is_active'], sort_order ) diff --git a/lib/db_init.py b/lib/db_init.py index a15f4f9..f93193f 100644 --- a/lib/db_init.py +++ b/lib/db_init.py @@ -182,6 +182,15 @@ def init_database_tables(): "total_amount_name VARCHAR(100) NULL COMMENT '实收金额报表表头名称'", 'total_amount_name' ) + + # 给配置表增加 balance_electricity 字段(是否找平电量) + # 当尖+峰+平+谷之和不等于充电电量时,以充电电量为准进行找平 + # 找平顺序:谷 → 平 → 峰 → 尖(加到第一个有值的时段上) + _add_column_if_not_exists( + 't_daily_report_config', + "balance_electricity TINYINT NULL DEFAULT 1 COMMENT '是否找平电量(0=否,1=是)'", + 'balance_electricity' + ) # 创建全局配置表 create_global_config_table = """ diff --git a/lib/report_generator.py b/lib/report_generator.py index 548fe92..0356cf8 100644 --- a/lib/report_generator.py +++ b/lib/report_generator.py @@ -285,6 +285,70 @@ def calculate_time_period_electricity(orders, time_periods_config): return result +def balance_time_period_electricity(orders): + """ + 找平分时段电量 + + 正常情况下:充电电量(charge_degree) = 尖电量 + 峰电量 + 平电量 + 谷电量 + 但平台导出数据时,四者之和可能不等于充电电量。 + 本函数以充电电量(charge_degree)为准,将差额加到对应时段上。 + + 找平原则(按优先级顺序): + 1. 有谷电量(>0) → 差额加到谷上 + 2. 没谷有平电量(>0) → 差额加到平上 + 3. 没平有峰电量(>0) → 差额加到峰上 + 4. 都没有 → 差额加到尖上 + + Args: + orders: 订单数据列表(会原地修改 sharp/peak/flat/valley_electricity 字段) + + Returns: + int: 实际找平的订单数 + """ + balanced_count = 0 + + for order in orders: + # 获取充电电量(作为基准) + charge_degree = order.get('charge_degree') + if charge_degree is None or charge_degree == '': + continue + + try: + charge_degree = float(charge_degree) + except (ValueError, TypeError): + continue + + # 获取各时段电量 + sharp = float(order.get('sharp_electricity', 0) or 0) + peak = float(order.get('peak_electricity', 0) or 0) + flat = float(order.get('flat_electricity', 0) or 0) + valley = float(order.get('valley_electricity', 0) or 0) + + # 计算四时段之和 + period_sum = round(sharp + peak + flat + valley, 3) + + # 计算差额 + diff = round(charge_degree - period_sum, 3) + + # 如果差额为0,不需要找平 + if abs(diff) < 0.001: + continue + + # 按优先级找平:谷 → 平 → 峰 → 尖 + if valley > 0: + order['valley_electricity'] = round(valley + diff, 3) + elif flat > 0: + order['flat_electricity'] = round(flat + diff, 3) + elif peak > 0: + order['peak_electricity'] = round(peak + diff, 3) + else: + order['sharp_electricity'] = round(sharp + diff, 3) + + balanced_count += 1 + + return balanced_count + + def get_beijing_time(): """获取北京时间""" beijing_tz = pytz.timezone('Asia/Shanghai') @@ -592,6 +656,19 @@ def generate_daily_report(config_id, start_time=None, end_time=None): log_info(f"[日报生成] 已计算 {len(time_period_electricity)} 条订单的分时段电量", 'report') + # 找平分时段电量 + # 当配置开启了找平电量功能时,以充电电量(charge_degree)为准, + # 将尖+峰+平+谷之和与充电电量的差额,按 谷→平→峰→尖 的优先级加到对应时段上 + balance_electricity_enabled = config.get('balance_electricity') + # 兼容历史配置:NULL 或未设置时默认开启 + if balance_electricity_enabled is None: + balance_electricity_enabled = 1 + + if balance_electricity_enabled == 1: + balanced_count = balance_time_period_electricity(orders) + if balanced_count > 0: + log_info(f"[日报生成] 找平电量:共处理 {balanced_count} 条订单的分时段电量差额", 'report') + # 计算虚拟字段(自定义服务费、自定义实收总金额)并添加到订单数据中 # 这样求和数据才能正确计算这些字段的总和 has_custom_service_fee = config.get('custom_service_fee_price') is not None and str(config.get('custom_service_fee_price', '')).strip() != '' diff --git a/public/static/css/style.css b/public/static/css/style.css index e300b55..6c2d970 100644 --- a/public/static/css/style.css +++ b/public/static/css/style.css @@ -1106,6 +1106,118 @@ color: #666; flex-shrink: 0; } +/* ============== Toast 消息提示 ============== */ +.toast-container { + position: fixed; + top: 20px; + right: 20px; + z-index: 10000; + display: flex; + flex-direction: column; + gap: 10px; + pointer-events: none; + max-width: 400px; +} + +.toast { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 12px 16px; + border-radius: 8px; + background: #fff; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + border-left: 4px solid #909399; + font-size: 14px; + line-height: 1.5; + color: #333; + pointer-events: auto; + animation: toast-slide-in 0.3s ease; + word-break: break-word; +} + +.toast.toast-success { + border-left-color: #67c23a; + background: #f0f9eb; +} + +.toast.toast-error { + border-left-color: #f56c6c; + background: #fef0f0; +} + +.toast.toast-warning { + border-left-color: #e6a23c; + background: #fdf6ec; +} + +.toast.toast-info { + border-left-color: #409eff; + background: #ecf5ff; +} + +.toast-icon { + flex-shrink: 0; + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + font-weight: bold; +} + +.toast.toast-success .toast-icon { color: #67c23a; } +.toast.toast-error .toast-icon { color: #f56c6c; } +.toast.toast-warning .toast-icon { color: #e6a23c; } +.toast.toast-info .toast-icon { color: #409eff; } + +.toast-content { + flex: 1; + min-width: 0; +} + +.toast-close { + flex-shrink: 0; + background: none; + border: none; + color: #c0c4cc; + cursor: pointer; + font-size: 16px; + padding: 0; + line-height: 1; +} + +.toast-close:hover { + color: #909399; +} + +.toast.toast-fade-out { + animation: toast-slide-out 0.3s ease forwards; +} + +@keyframes toast-slide-in { + from { + opacity: 0; + transform: translateX(100%); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes toast-slide-out { + from { + opacity: 1; + transform: translateX(0); + } + to { + opacity: 0; + transform: translateX(100%); + } +} + .generate-item.running .generate-item-status { color: #17a2b8; } diff --git a/public/static/js/common.js b/public/static/js/common.js index 70df0ed..6d4f6ab 100644 --- a/public/static/js/common.js +++ b/public/static/js/common.js @@ -4,6 +4,113 @@ window.onerror = function(msg, url, line, col, error) { return true; // 阻止错误传播 }; +// ============== Toast 消息提示系统 ============== +// 非阻塞消息提示,替代 alert() 弹窗 +// 用法: showToast('消息内容', 'success'); // type: success/error/warning/info +// showToast('消息内容'); // 默认 auto 自动判断类型 + +const TOAST_ICONS = { + success: '✓', + error: '✕', + warning: '!', + info: 'i' +}; + +let toastContainer = null; + +function getToastContainer() { + if (!toastContainer) { + toastContainer = document.createElement('div'); + toastContainer.className = 'toast-container'; + document.body.appendChild(toastContainer); + } + return toastContainer; +} + +/** + * 显示消息提示(非阻塞) + * @param {string} message - 消息内容 + * @param {string} type - 类型: success/error/warning/info/auto + * @param {number} duration - 显示时长(毫秒),默认 3000ms,error 默认 5000ms + */ +function showToast(message, type = 'auto', duration = null) { + // 自动判断类型 + if (type === 'auto') { + const msg = String(message); + if (/成功|完成|已保存|已删除|已更新|已启用|已禁用|已复制|已导入|已导出|已采集/.test(msg)) { + type = 'success'; + } else if (/失败|错误|异常|无法|不能|出错/.test(msg)) { + type = 'error'; + } else if (/请|警告|注意|必须|需要/.test(msg)) { + type = 'warning'; + } else { + type = 'info'; + } + } + + // 默认时长:error 5000ms,其他 3000ms + if (duration === null) { + duration = type === 'error' ? 5000 : 3000; + } + + const container = getToastContainer(); + const toast = document.createElement('div'); + toast.className = `toast toast-${type}`; + + const icon = TOAST_ICONS[type] || ''; + toast.innerHTML = ` + + + + `; + + // 安全设置消息内容(防 XSS) + toast.querySelector('.toast-content').textContent = String(message); + + // 关闭按钮 + toast.querySelector('.toast-close').addEventListener('click', function() { + removeToast(toast); + }); + + container.appendChild(toast); + + // 自动消失 + if (duration > 0) { + setTimeout(function() { + removeToast(toast); + }, duration); + } + + return toast; +} + +function removeToast(toast) { + if (!toast || !toast.parentNode) return; + toast.classList.add('toast-fade-out'); + setTimeout(function() { + if (toast.parentNode) { + toast.parentNode.removeChild(toast); + } + }, 300); +} + +// 便捷方法 +function showSuccess(message, duration) { + return showToast(message, 'success', duration); +} + +function showError(message, duration) { + return showToast(message, 'error', duration); +} + +function showWarning(message, duration) { + return showToast(message, 'warning', duration); +} + +function showInfo(message, duration) { + return showToast(message, 'info', duration); +} + // 全局变量 let editingConfigId = null; let allFields = []; @@ -93,7 +200,7 @@ async function handleLogin(event) { const password = document.getElementById('login-password').value.trim(); if (!username || !password) { - alert('请输入用户名和密码'); + showToast('请输入用户名和密码'); return; } @@ -119,10 +226,10 @@ async function handleLogin(event) { // 登录成功后加载数据 loadConfigs(); } else { - alert(result.message || '登录失败'); + showToast(result.message || '登录失败'); } } catch (error) { - alert('登录失败: ' + error.message); + showToast('登录失败: ' + error.message); } finally { btn.disabled = false; btn.textContent = '登 录'; @@ -161,17 +268,17 @@ async function saveChangePassword() { const confirmPassword = document.getElementById('confirm-password').value.trim(); if (!oldPassword || !newPassword || !confirmPassword) { - alert('请填写完整信息'); + showToast('请填写完整信息'); return; } if (newPassword.length < 6) { - alert('新密码长度不能少于6位'); + showToast('新密码长度不能少于6位'); return; } if (newPassword !== confirmPassword) { - alert('两次输入的新密码不一致'); + showToast('两次输入的新密码不一致'); return; } @@ -188,15 +295,15 @@ async function saveChangePassword() { const result = await response.json(); if (result.success) { - alert('密码修改成功,请重新登录'); + showToast('密码修改成功,请重新登录'); closeChangePasswordModal(); clearAuth(); showLoginPage(); } else { - alert(result.message || '修改失败'); + showToast(result.message || '修改失败'); } } catch (error) { - alert('修改失败: ' + error.message); + showToast('修改失败: ' + error.message); } } @@ -241,16 +348,6 @@ function formatDateTime(dateStr) { return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } -// 显示错误提示 -function showError(message) { - alert('错误:' + message); -} - -// 显示成功提示 -function showSuccess(message) { - alert('成功:' + message); -} - // 页面加载时自动加载配置列表 document.addEventListener('DOMContentLoaded', function() { loadConfigs(); diff --git a/public/static/js/config.js b/public/static/js/config.js index 74ffe68..565cb60 100644 --- a/public/static/js/config.js +++ b/public/static/js/config.js @@ -11,10 +11,10 @@ async function loadConfigs() { currentConfigList = result.data; renderConfigList(currentConfigList); } else { - alert('加载配置失败:' + result.message); + showToast('加载配置失败:' + result.message); } } catch (error) { - alert('加载配置失败:' + error.message); + showToast('加载配置失败:' + error.message); } } @@ -89,6 +89,10 @@ function showCreateModal() { 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 = ''; @@ -593,6 +597,9 @@ async function saveConfig() { // 获取月度总计开关 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; @@ -612,12 +619,12 @@ async function saveConfig() { const totalAmountName = totalAmountNameInput?.value.trim() || null; if (!configName || !splitType || !splitValue) { - alert('请填写所有必填字段'); + showToast('请填写所有必填字段'); return; } if (selectedFields.length === 0) { - alert('请至少选择一个字段'); + showToast('请至少选择一个字段'); return; } @@ -628,11 +635,11 @@ async function saveConfig() { // 验证合并特来电配置 if (mergeTelecom && splitType !== 'company_id') { - alert('合并特来电数据仅支持按企业拆分'); + showToast('合并特来电数据仅支持按企业拆分'); return; } if (mergeTelecom && !telecomVehicleNo) { - alert('请输入特来电车量自编号'); + showToast('请输入特来电车量自编号'); return; } @@ -651,6 +658,7 @@ async function saveConfig() { 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, @@ -677,14 +685,14 @@ async function saveConfig() { const result = await response.json(); if (result.success) { - alert(editingConfigId ? '配置更新成功' : '配置创建成功'); + showToast(editingConfigId ? '配置更新成功' : '配置创建成功'); closeModal(); loadConfigs(); } else { - alert('保存失败: ' + result.message); + showToast('保存失败: ' + result.message); } } catch (error) { - alert('保存失败: ' + error.message); + showToast('保存失败: ' + error.message); } } @@ -736,6 +744,16 @@ async function editConfig(configId) { 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) { @@ -770,7 +788,7 @@ async function editConfig(configId) { } } } catch (error) { - alert('加载配置失败: ' + error.message); + showToast('加载配置失败: ' + error.message); } } @@ -913,14 +931,14 @@ async function copyConfig(configId) { const result = await response.json(); if (result.success) { - alert('配置复制成功!'); + showToast('配置复制成功!'); loadConfigs(); } else { - alert('复制失败: ' + (result.message || result.error || '未知错误')); + showToast('复制失败: ' + (result.message || result.error || '未知错误')); } } catch (error) { console.error('复制配置失败:', error); - alert('复制失败,请稍后重试'); + showToast('复制失败,请稍后重试'); } } @@ -937,13 +955,13 @@ async function deleteConfig(configId) { const result = await response.json(); if (result.success) { - alert('配置删除成功'); + showToast('配置删除成功'); loadConfigs(); } else { - alert('删除失败: ' + result.message); + showToast('删除失败: ' + result.message); } } catch (error) { - alert('删除失败: ' + error.message); + showToast('删除失败: ' + error.message); } } @@ -958,10 +976,10 @@ async function toggleConfig(configId) { if (result.success) { loadConfigs(); } else { - alert('操作失败: ' + result.message); + showToast('操作失败: ' + result.message); } } catch (error) { - alert('操作失败: ' + error.message); + showToast('操作失败: ' + error.message); } } @@ -1000,7 +1018,7 @@ async function moveConfig(configId, direction) { currentConfigList[rollbackTarget] = temp; renderConfigList(currentConfigList); } - alert('移动失败: ' + result.message); + showToast('移动失败: ' + result.message); } } catch (error) { // 网络错误也回滚 @@ -1012,7 +1030,7 @@ async function moveConfig(configId, direction) { currentConfigList[rollbackTarget] = temp; renderConfigList(currentConfigList); } - alert('移动失败: ' + error.message); + showToast('移动失败: ' + error.message); } } @@ -1045,7 +1063,7 @@ function exportConfigs() { try { window.open('/api/config/export', '_blank'); } catch (error) { - alert('导出失败: ' + error.message); + showToast('导出失败: ' + error.message); } } @@ -1054,7 +1072,7 @@ function downloadTemplate() { try { window.open('/api/config/template', '_blank'); } catch (error) { - alert('模板下载失败: ' + error.message); + showToast('模板下载失败: ' + error.message); } } @@ -1065,7 +1083,7 @@ async function importConfigs(event) { if (!file) return; if (!file.name.endsWith('.xlsx') && !file.name.endsWith('.xls')) { - alert('只支持 Excel 格式(.xlsx/.xls)的配置文件'); + showToast('只支持 Excel 格式(.xlsx/.xls)的配置文件'); fileInput.value = ''; return; } @@ -1090,13 +1108,13 @@ async function importConfigs(event) { if (result.data && result.data.fail_messages && result.data.fail_messages.length > 0) { message += '\n\n失败详情:\n' + result.data.fail_messages.join('\n'); } - alert(message); + showToast(message); loadConfigs(); } else { - alert('导入失败: ' + (result.message || '未知错误')); + showToast('导入失败: ' + (result.message || '未知错误')); } } catch (error) { - alert('导入失败: ' + error.message); + showToast('导入失败: ' + error.message); } fileInput.value = ''; diff --git a/public/static/js/history.js b/public/static/js/history.js index 8b6ff04..7db894d 100644 --- a/public/static/js/history.js +++ b/public/static/js/history.js @@ -37,7 +37,7 @@ async function batchDownloadHistory() { const successCount = checkboxes.length - failedCount; if (successCount === 0) { - alert('选中的记录都是生成失败的,无法下载'); + showToast('选中的记录都是生成失败的,无法下载'); return; } @@ -59,7 +59,7 @@ async function batchDownloadHistory() { }); await handleDownloadResponse(response); } catch (error) { - alert('下载失败: ' + error.message); + showToast('下载失败: ' + error.message); } } else { // 模式2:按筛选条件下载全部匹配的成功记录 @@ -94,7 +94,7 @@ async function batchDownloadHistory() { } if (matchCount === 0) { - alert(`没有符合条件的成功记录可导出。\n筛选条件: ${filterDesc}`); + showToast(`没有符合条件的成功记录可导出。\n筛选条件: ${filterDesc}`); return; } @@ -115,7 +115,7 @@ async function batchDownloadHistory() { }); await handleDownloadResponse(response); } catch (error) { - alert('下载失败: ' + error.message); + showToast('下载失败: ' + error.message); } } } @@ -125,9 +125,9 @@ async function handleDownloadResponse(response) { if (!response.ok) { try { const result = await response.json(); - alert('下载失败: ' + (result.message || '未知错误')); + showToast('下载失败: ' + (result.message || '未知错误')); } catch (e) { - alert('下载失败: 服务器返回错误'); + showToast('下载失败: 服务器返回错误'); } return; } @@ -161,7 +161,7 @@ async function handleDownloadResponse(response) { msg += `\n跳过失败记录: ${skipped} 个`; } if (missing > 0 || skipped > 0) { - alert(msg); + showToast(msg); } } } @@ -397,10 +397,10 @@ async function loadHistoryWithDate(page) { renderHistory(result.data.list); renderPagination(result.data.page, result.data.total, result.data.page_size); } else { - alert('加载失败: ' + (result.error || result.message || '未知错误')); + showToast('加载失败: ' + (result.error || result.message || '未知错误')); } } catch (error) { - alert('加载失败: ' + error.message); + showToast('加载失败: ' + error.message); } } @@ -427,13 +427,13 @@ async function deleteHistory(id) { }); const result = await response.json(); if (result.success) { - alert('删除成功'); + showToast('删除成功'); loadHistoryWithDate(); } else { - alert('删除失败: ' + result.message); + showToast('删除失败: ' + result.message); } } catch (error) { - alert('删除失败: ' + error.message); + showToast('删除失败: ' + error.message); } } @@ -441,7 +441,7 @@ async function deleteHistory(id) { async function batchDeleteHistory() { const checkboxes = document.querySelectorAll('.history-checkbox:checked'); if (checkboxes.length === 0) { - alert('请至少选择一条记录'); + showToast('请至少选择一条记录'); return; } @@ -459,13 +459,13 @@ async function batchDeleteHistory() { }); const result = await response.json(); if (result.success) { - alert(result.message); + showToast(result.message); loadHistoryWithDate(); } else { - alert('删除失败: ' + result.message); + showToast('删除失败: ' + result.message); } } catch (error) { - alert('删除失败: ' + error.message); + showToast('删除失败: ' + error.message); } } diff --git a/public/static/js/report.js b/public/static/js/report.js index e5f9a55..92fa362 100644 --- a/public/static/js/report.js +++ b/public/static/js/report.js @@ -286,12 +286,12 @@ async function generateReport() { const endTime = document.getElementById('end-time').value; if (checkboxes.length === 0) { - alert('请至少选择一个配置'); + showToast('请至少选择一个配置'); return; } if (!startTime || !endTime) { - alert('请选择时间范围'); + showToast('请选择时间范围'); return; } diff --git a/public/static/js/sum_data.js b/public/static/js/sum_data.js index dacba11..4ebcf86 100644 --- a/public/static/js/sum_data.js +++ b/public/static/js/sum_data.js @@ -253,7 +253,7 @@ async function collectSumDataFromHistory() { const endDate = document.getElementById('sum-data-end-date').value; if (!startDate || !endDate) { - alert('请先选择日期范围'); + showToast('请先选择日期范围'); return; } @@ -273,14 +273,14 @@ async function collectSumDataFromHistory() { }); const result = await response.json(); if (result.success) { - alert(result.message); + showToast(result.message); loadSumData(1); } else { - alert('采集失败: ' + (result.message || '未知错误')); + showToast('采集失败: ' + (result.message || '未知错误')); loadSumData(1); } } catch (error) { - alert('采集失败: ' + error.message); + showToast('采集失败: ' + error.message); loadSumData(1); } } @@ -319,14 +319,14 @@ async function showEditSumDataGroup(ids) { const result = await response.json(); if (!result.success) { - alert('加载详情失败'); + showToast('加载详情失败'); return; } const groupItems = result.data || []; if (groupItems.length === 0) { - alert('未找到该记录'); + showToast('未找到该记录'); return; } @@ -349,7 +349,7 @@ async function showEditSumDataGroup(ids) { document.getElementById('sum-data-remark-input').value = chargeItem.remark || ''; document.getElementById('sum-data-modal').classList.add('active'); } catch (error) { - alert('加载详情失败: ' + error.message); + showToast('加载详情失败: ' + error.message); } } @@ -415,7 +415,7 @@ async function saveSumData() { // 编辑时修改原因为必填 if (editingGroupIds && editingGroupIds.length > 0 && !remark.trim()) { - alert('请填写修改原因'); + showToast('请填写修改原因'); return; } @@ -524,11 +524,11 @@ async function saveSumData() { } if (successCount > 0) { - alert(`更新成功(${successCount} 个字段)`); + showToast(`更新成功(${successCount} 个字段)`); closeSumDataModal(); loadSumData(sumDataCurrentPage); } else { - alert('更新失败'); + showToast('更新失败'); } return; } @@ -543,7 +543,7 @@ async function saveSumData() { const valleyTime = document.getElementById('sum-data-valley-time-input').value; if (!chargeDegree && !peakDegree && !peakTime && !normalTime && !valleyTime) { - alert('请至少输入一个求和字段值'); + showToast('请至少输入一个求和字段值'); return; } @@ -595,7 +595,7 @@ async function saveSumData() { }; if (!data.report_date) { - alert('请选择报表日期'); + showToast('请选择报表日期'); return; } @@ -607,14 +607,14 @@ async function saveSumData() { const result = await response.json(); if (result.success) { - alert('添加成功'); + showToast('添加成功'); closeSumDataModal(); loadSumData(sumDataCurrentPage); } else { - alert('保存失败: ' + (result.message || '未知错误')); + showToast('保存失败: ' + (result.message || '未知错误')); } } catch (error) { - alert('保存失败: ' + error.message); + showToast('保存失败: ' + error.message); } } @@ -654,13 +654,13 @@ async function deleteSumDataGroup(ids) { } if (success) { - alert('删除成功'); + showToast('删除成功'); loadSumData(sumDataCurrentPage); } else { - alert('部分删除失败'); + showToast('部分删除失败'); } } catch (error) { - alert('删除失败: ' + error.message); + showToast('删除失败: ' + error.message); } } @@ -673,7 +673,7 @@ function deleteSumData(id) { async function batchDeleteSumData() { const checkboxes = document.querySelectorAll('.sum-data-checkbox:checked'); if (checkboxes.length === 0) { - alert('请选择要删除的记录'); + showToast('请选择要删除的记录'); return; } @@ -697,13 +697,13 @@ async function batchDeleteSumData() { }); const result = await response.json(); if (result.success) { - alert('删除成功'); + showToast('删除成功'); loadSumData(sumDataCurrentPage); } else { - alert('删除失败: ' + (result.message || '未知错误')); + showToast('删除失败: ' + (result.message || '未知错误')); } } catch (error) { - alert('删除失败: ' + error.message); + showToast('删除失败: ' + error.message); } } @@ -725,7 +725,7 @@ async function exportSumData() { if (result.success) { if (result.total === 0) { - alert('没有可导出的数据'); + showToast('没有可导出的数据'); return; } const a = document.createElement('a'); @@ -736,10 +736,10 @@ async function exportSumData() { a.click(); document.body.removeChild(a); } else { - alert('导出失败: ' + (result.message || '未知错误')); + showToast('导出失败: ' + (result.message || '未知错误')); } } catch (error) { - alert('导出失败: ' + error.message); + showToast('导出失败: ' + error.message); } } @@ -837,14 +837,14 @@ async function saveSumDataConfig() { const result = await response.json(); if (result.success) { - alert('配置保存成功'); + showToast('配置保存成功'); sumDataConfigFields = selectedFields; closeSumDataConfigModal(); loadSumData(sumDataCurrentPage); } else { - alert('保存失败: ' + (result.message || '未知错误')); + showToast('保存失败: ' + (result.message || '未知错误')); } } catch (error) { - alert('保存失败: ' + error.message); + showToast('保存失败: ' + error.message); } } diff --git a/templates/index.html b/templates/index.html index a868176..b6142d7 100644 --- a/templates/index.html +++ b/templates/index.html @@ -337,6 +337,13 @@ 报表底部显示当月充电量累计总计(数据来源于求和采集表,对账后数据) + +