增加求和采集和细节修改
This commit is contained in:
8
app.py
8
app.py
@@ -12,6 +12,7 @@ import pytz
|
||||
from lib.db import execute_query
|
||||
from lib.report_generator import generate_daily_report
|
||||
from lib.api import register_blueprints
|
||||
from lib.db_init import init_database_tables
|
||||
|
||||
# 创建 Flask 应用
|
||||
app = Flask(__name__,
|
||||
@@ -25,6 +26,13 @@ os.makedirs(os.path.join('public', 'reports'), exist_ok=True)
|
||||
# 注册 API 蓝图
|
||||
register_blueprints(app)
|
||||
|
||||
# 应用启动时自动初始化数据库表
|
||||
print('正在初始化数据库表...')
|
||||
if init_database_tables():
|
||||
print('数据库表初始化成功')
|
||||
else:
|
||||
print('数据库表初始化失败,请检查数据库连接')
|
||||
|
||||
|
||||
# ==================== 页面路由 ====================
|
||||
|
||||
|
||||
@@ -21,8 +21,11 @@ init_bp = Blueprint('init', __name__, url_prefix='/api')
|
||||
# 文件下载蓝图
|
||||
download_bp = Blueprint('download', __name__, url_prefix='/api')
|
||||
|
||||
# 求和数据管理蓝图
|
||||
sum_data_bp = Blueprint('sum_data', __name__, url_prefix='/api')
|
||||
|
||||
# 导入各模块的路由
|
||||
from . import config_api, entity_api, field_api, report_api, init_api, download_api
|
||||
from . import config_api, entity_api, field_api, report_api, init_api, download_api, sum_data_api
|
||||
|
||||
|
||||
def register_blueprints(app):
|
||||
@@ -33,3 +36,4 @@ def register_blueprints(app):
|
||||
app.register_blueprint(report_bp)
|
||||
app.register_blueprint(init_bp)
|
||||
app.register_blueprint(download_bp)
|
||||
app.register_blueprint(sum_data_bp)
|
||||
|
||||
@@ -208,50 +208,60 @@ def move_config(config_id):
|
||||
"""移动配置排序"""
|
||||
try:
|
||||
direction = request.json.get('direction') # up 或 down
|
||||
print(f"[move_config] 开始移动 config_id={config_id}, direction={direction}")
|
||||
|
||||
# 获取当前配置
|
||||
current = execute_query(
|
||||
'SELECT sort_order FROM t_daily_report_config WHERE id = %s',
|
||||
(config_id,)
|
||||
# 查出所有配置,按 sort_order, id 排序
|
||||
all_configs = execute_query(
|
||||
'SELECT id, sort_order FROM t_daily_report_config ORDER BY sort_order ASC, id ASC'
|
||||
)
|
||||
if not current:
|
||||
print(f"[move_config] 所有配置数量: {len(all_configs)}")
|
||||
for idx, c in enumerate(all_configs):
|
||||
print(f" [{idx}] id={c['id']}, sort_order={c['sort_order']}")
|
||||
|
||||
# 找到当前配置的索引
|
||||
current_index = None
|
||||
for i, c in enumerate(all_configs):
|
||||
if c['id'] == config_id:
|
||||
current_index = i
|
||||
break
|
||||
|
||||
if current_index is None:
|
||||
print(f"[move_config] 未找到配置 id={config_id}")
|
||||
return jsonify({'success': False, 'message': '配置不存在'}), 404
|
||||
|
||||
current_order = current[0]['sort_order']
|
||||
print(f"[move_config] 当前配置索引: {current_index}")
|
||||
|
||||
# 计算目标索引
|
||||
if direction == 'up':
|
||||
# 找到上一个配置
|
||||
prev = execute_query(
|
||||
'SELECT id, sort_order FROM t_daily_report_config WHERE sort_order < %s ORDER BY sort_order DESC LIMIT 1',
|
||||
(current_order,)
|
||||
)
|
||||
if prev:
|
||||
# 交换排序
|
||||
execute_update(
|
||||
'UPDATE t_daily_report_config SET sort_order = %s WHERE id = %s',
|
||||
(prev[0]['sort_order'], config_id)
|
||||
)
|
||||
execute_update(
|
||||
'UPDATE t_daily_report_config SET sort_order = %s WHERE id = %s',
|
||||
(current_order, prev[0]['id'])
|
||||
)
|
||||
target_index = current_index - 1
|
||||
elif direction == 'down':
|
||||
# 找到下一个配置
|
||||
next_config = execute_query(
|
||||
'SELECT id, sort_order FROM t_daily_report_config WHERE sort_order > %s ORDER BY sort_order ASC LIMIT 1',
|
||||
(current_order,)
|
||||
)
|
||||
if next_config:
|
||||
# 交换排序
|
||||
target_index = current_index + 1
|
||||
else:
|
||||
return jsonify({'success': False, 'message': '无效的移动方向'}), 400
|
||||
|
||||
# 边界检查
|
||||
if target_index < 0 or target_index >= len(all_configs):
|
||||
print(f"[move_config] 已到边界,无法移动 target_index={target_index}")
|
||||
return jsonify({'success': True, 'message': '已到边界'})
|
||||
|
||||
# 在内存数组中交换位置
|
||||
all_configs[current_index], all_configs[target_index] = all_configs[target_index], all_configs[current_index]
|
||||
|
||||
print(f"[move_config] 交换后顺序: {[c['id'] for c in all_configs]}")
|
||||
|
||||
# 全量重排:按新顺序给每个配置重新赋值 sort_order
|
||||
for idx, cfg in enumerate(all_configs):
|
||||
new_sort = idx + 1
|
||||
print(f" 更新 id={cfg['id']}: sort_order {cfg['sort_order']} -> {new_sort}")
|
||||
execute_update(
|
||||
'UPDATE t_daily_report_config SET sort_order = %s WHERE id = %s',
|
||||
(next_config[0]['sort_order'], config_id)
|
||||
)
|
||||
execute_update(
|
||||
'UPDATE t_daily_report_config SET sort_order = %s WHERE id = %s',
|
||||
(current_order, next_config[0]['id'])
|
||||
(new_sort, cfg['id'])
|
||||
)
|
||||
|
||||
print("[move_config] 重排完成")
|
||||
return jsonify({'success': True, 'message': '移动成功'})
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
print(f"[move_config] 异常: {e}")
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
数据库初始化 API
|
||||
"""
|
||||
from flask import jsonify
|
||||
from lib.db import execute_update
|
||||
from lib.db_init import init_database_tables
|
||||
from . import init_bp
|
||||
|
||||
|
||||
@@ -10,61 +10,10 @@ from . import init_bp
|
||||
def init_tables():
|
||||
"""初始化数据库表"""
|
||||
try:
|
||||
# 创建配置表
|
||||
create_config_table = """
|
||||
CREATE TABLE IF NOT EXISTS t_daily_report_config (
|
||||
id BIGINT NOT NULL COMMENT '配置 ID',
|
||||
config_name VARCHAR(100) NOT NULL COMMENT '配置名称',
|
||||
split_type VARCHAR(20) NOT NULL COMMENT '拆分方式',
|
||||
split_value VARCHAR(100) NOT NULL COMMENT '拆分值',
|
||||
split_name VARCHAR(200) NULL COMMENT '拆分显示名称',
|
||||
selected_fields VARCHAR(3000) NOT NULL COMMENT '选择的字段列表',
|
||||
sum_fields VARCHAR(3000) NULL COMMENT '求和字段列表',
|
||||
is_active TINYINT NULL COMMENT '是否启用',
|
||||
sort_order INT NULL COMMENT '排序顺序',
|
||||
create_time DATETIME NULL COMMENT '创建时间',
|
||||
update_time DATETIME NULL COMMENT '更新时间'
|
||||
)
|
||||
UNIQUE KEY(id)
|
||||
DISTRIBUTED BY HASH(id) BUCKETS 1
|
||||
PROPERTIES("replication_num" = "1")
|
||||
"""
|
||||
execute_update(create_config_table)
|
||||
|
||||
# 创建历史表
|
||||
create_history_table = """
|
||||
CREATE TABLE IF NOT EXISTS t_daily_report_history (
|
||||
id BIGINT NOT NULL COMMENT '历史 ID',
|
||||
report_date VARCHAR(20) NULL COMMENT '报表日期',
|
||||
start_time VARCHAR(50) NULL COMMENT '开始时间',
|
||||
end_time VARCHAR(50) NULL COMMENT '结束时间',
|
||||
split_type VARCHAR(20) NULL COMMENT '拆分方式',
|
||||
split_value VARCHAR(100) NULL COMMENT '拆分值',
|
||||
split_name VARCHAR(200) NULL COMMENT '拆分显示名称',
|
||||
config_id BIGINT NULL COMMENT '配置 ID',
|
||||
config_name VARCHAR(200) NULL COMMENT '配置名称',
|
||||
total_orders INT NULL COMMENT '订单总数',
|
||||
total_amount DECIMAL(10,2) NULL COMMENT '总金额',
|
||||
sum_results VARCHAR(2000) NULL COMMENT '求和结果',
|
||||
file_path VARCHAR(500) NULL COMMENT '文件路径',
|
||||
status TINYINT NULL COMMENT '状态',
|
||||
create_time DATETIME NULL COMMENT '创建时间'
|
||||
)
|
||||
UNIQUE KEY(id)
|
||||
DISTRIBUTED BY HASH(id) BUCKETS 1
|
||||
PROPERTIES("replication_num" = "1")
|
||||
"""
|
||||
execute_update(create_history_table)
|
||||
|
||||
# 为已存在的表添加 config_name 字段(如果不存在)
|
||||
try:
|
||||
execute_update("""
|
||||
ALTER TABLE t_daily_report_history
|
||||
ADD COLUMN IF NOT EXISTS config_name VARCHAR(200) NULL COMMENT '配置名称'
|
||||
""")
|
||||
except Exception as e:
|
||||
print(f"[数据库] 添加 config_name 字段时出错(可能已存在): {e}")
|
||||
|
||||
success = init_database_tables()
|
||||
if success:
|
||||
return jsonify({'success': True, 'message': '数据库表初始化成功'})
|
||||
else:
|
||||
return jsonify({'success': False, 'message': '数据库表初始化失败'}), 500
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
130
lib/api/sum_data_api.py
Normal file
130
lib/api/sum_data_api.py
Normal file
@@ -0,0 +1,130 @@
|
||||
"""
|
||||
求和数据管理 API
|
||||
"""
|
||||
from flask import request, jsonify
|
||||
from lib.sum_data_collector import (
|
||||
collect_sum_data_from_history,
|
||||
get_sum_data_list,
|
||||
add_sum_data,
|
||||
update_sum_data,
|
||||
delete_sum_data,
|
||||
batch_delete_sum_data
|
||||
)
|
||||
from . import sum_data_bp
|
||||
|
||||
|
||||
@sum_data_bp.route('/sum-data/collect', methods=['POST'])
|
||||
def collect_sum_data():
|
||||
"""从历史记录采集求和数据"""
|
||||
try:
|
||||
data = request.json or {}
|
||||
history_id = data.get('history_id')
|
||||
|
||||
result = collect_sum_data_from_history(history_id)
|
||||
if result['success']:
|
||||
return jsonify(result)
|
||||
else:
|
||||
return jsonify(result), 400
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@sum_data_bp.route('/sum-data', methods=['GET'])
|
||||
def list_sum_data():
|
||||
"""查询求和数据列表"""
|
||||
try:
|
||||
page = int(request.args.get('page', 1))
|
||||
page_size = int(request.args.get('page_size', 20))
|
||||
report_date = request.args.get('report_date')
|
||||
config_id = request.args.get('config_id')
|
||||
config_name = request.args.get('config_name')
|
||||
sum_field_key = request.args.get('sum_field_key')
|
||||
data_source = request.args.get('data_source')
|
||||
|
||||
result = get_sum_data_list(
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
report_date=report_date,
|
||||
config_id=config_id,
|
||||
config_name=config_name,
|
||||
sum_field_key=sum_field_key,
|
||||
data_source=data_source
|
||||
)
|
||||
|
||||
if result['success']:
|
||||
return jsonify(result)
|
||||
else:
|
||||
return jsonify(result), 400
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@sum_data_bp.route('/sum-data', methods=['POST'])
|
||||
def create_sum_data():
|
||||
"""手动添加求和数据"""
|
||||
try:
|
||||
data = request.json
|
||||
result = add_sum_data(data)
|
||||
if result['success']:
|
||||
return jsonify(result)
|
||||
else:
|
||||
return jsonify(result), 400
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@sum_data_bp.route('/sum-data/<int:sum_data_id>', methods=['PUT'])
|
||||
def update_sum_data_api(sum_data_id):
|
||||
"""更新求和数据"""
|
||||
try:
|
||||
data = request.json
|
||||
result = update_sum_data(sum_data_id, data)
|
||||
if result['success']:
|
||||
return jsonify(result)
|
||||
else:
|
||||
return jsonify(result), 400
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@sum_data_bp.route('/sum-data/<int:sum_data_id>', methods=['DELETE'])
|
||||
def delete_sum_data_api(sum_data_id):
|
||||
"""删除求和数据"""
|
||||
try:
|
||||
result = delete_sum_data(sum_data_id)
|
||||
if result['success']:
|
||||
return jsonify(result)
|
||||
else:
|
||||
return jsonify(result), 400
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@sum_data_bp.route('/sum-data/batch-delete', methods=['POST'])
|
||||
def batch_delete_sum_data_api():
|
||||
"""批量删除求和数据"""
|
||||
try:
|
||||
data = request.json
|
||||
ids = data.get('ids', [])
|
||||
if not ids:
|
||||
return jsonify({'success': False, 'message': '请选择要删除的记录'}), 400
|
||||
|
||||
result = batch_delete_sum_data(ids)
|
||||
if result['success']:
|
||||
return jsonify(result)
|
||||
else:
|
||||
return jsonify(result), 400
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
119
lib/db_init.py
Normal file
119
lib/db_init.py
Normal file
@@ -0,0 +1,119 @@
|
||||
"""
|
||||
数据库初始化
|
||||
"""
|
||||
from lib.db import execute_update, execute_query
|
||||
|
||||
|
||||
def init_database_tables():
|
||||
"""初始化所有数据库表(应用启动时自动调用)"""
|
||||
try:
|
||||
# 创建配置表
|
||||
create_config_table = """
|
||||
CREATE TABLE IF NOT EXISTS t_daily_report_config (
|
||||
id BIGINT NOT NULL COMMENT '配置 ID',
|
||||
config_name VARCHAR(100) NOT NULL COMMENT '配置名称',
|
||||
split_type VARCHAR(20) NOT NULL COMMENT '拆分方式',
|
||||
split_value VARCHAR(100) NOT NULL COMMENT '拆分值',
|
||||
split_name VARCHAR(200) NULL COMMENT '拆分显示名称',
|
||||
selected_fields VARCHAR(3000) NOT NULL COMMENT '选择的字段列表',
|
||||
sum_fields VARCHAR(3000) NULL COMMENT '求和字段列表',
|
||||
time_periods VARCHAR(1000) NULL COMMENT '分时段电量配置',
|
||||
merge_telecom TINYINT NULL COMMENT '是否合并特来电数据',
|
||||
telecom_vehicle_no VARCHAR(500) NULL COMMENT '特来电车量自编号',
|
||||
field_custom_names VARCHAR(2000) NULL COMMENT '字段自定义报表名头',
|
||||
is_active TINYINT NULL COMMENT '是否启用',
|
||||
sort_order INT NULL COMMENT '排序顺序',
|
||||
create_time DATETIME NULL COMMENT '创建时间',
|
||||
update_time DATETIME NULL COMMENT '更新时间'
|
||||
)
|
||||
UNIQUE KEY(id)
|
||||
DISTRIBUTED BY HASH(id) BUCKETS 1
|
||||
PROPERTIES("replication_num" = "1")
|
||||
"""
|
||||
execute_update(create_config_table)
|
||||
print("[数据库] t_daily_report_config 表已就绪")
|
||||
|
||||
# 创建历史表
|
||||
create_history_table = """
|
||||
CREATE TABLE IF NOT EXISTS t_daily_report_history (
|
||||
id BIGINT NOT NULL COMMENT '历史 ID',
|
||||
report_date VARCHAR(20) NULL COMMENT '报表日期',
|
||||
start_time VARCHAR(50) NULL COMMENT '开始时间',
|
||||
end_time VARCHAR(50) NULL COMMENT '结束时间',
|
||||
split_type VARCHAR(20) NULL COMMENT '拆分方式',
|
||||
split_value VARCHAR(100) NULL COMMENT '拆分值',
|
||||
split_name VARCHAR(200) NULL COMMENT '拆分显示名称',
|
||||
config_id BIGINT NULL COMMENT '配置 ID',
|
||||
config_name VARCHAR(200) NULL COMMENT '配置名称',
|
||||
total_orders INT NULL COMMENT '订单总数',
|
||||
total_amount DECIMAL(10,2) NULL COMMENT '总金额',
|
||||
sum_results VARCHAR(2000) NULL COMMENT '求和结果',
|
||||
file_path VARCHAR(500) NULL COMMENT '文件路径',
|
||||
status TINYINT NULL COMMENT '状态',
|
||||
create_time DATETIME NULL COMMENT '创建时间'
|
||||
)
|
||||
UNIQUE KEY(id)
|
||||
DISTRIBUTED BY HASH(id) BUCKETS 1
|
||||
PROPERTIES("replication_num" = "1")
|
||||
"""
|
||||
execute_update(create_history_table)
|
||||
print("[数据库] t_daily_report_history 表已就绪")
|
||||
|
||||
# 为已存在的表添加 config_name 字段(如果不存在)
|
||||
try:
|
||||
execute_update("""
|
||||
ALTER TABLE t_daily_report_history
|
||||
ADD COLUMN IF NOT EXISTS config_name VARCHAR(200) NULL COMMENT '配置名称'
|
||||
""")
|
||||
except Exception as e:
|
||||
print(f"[数据库] 添加 config_name 字段时出错(可能已存在): {e}")
|
||||
|
||||
# 创建求和数据表
|
||||
create_sum_data_table = """
|
||||
CREATE TABLE IF NOT EXISTS t_daily_report_sum_data (
|
||||
id BIGINT NOT NULL COMMENT '主键 ID',
|
||||
report_date VARCHAR(20) NULL COMMENT '报表日期',
|
||||
config_id BIGINT NULL COMMENT '配置 ID',
|
||||
config_name VARCHAR(200) NULL COMMENT '配置名称',
|
||||
split_type VARCHAR(20) NULL COMMENT '拆分方式',
|
||||
split_value VARCHAR(100) NULL COMMENT '拆分值',
|
||||
split_name VARCHAR(200) NULL COMMENT '拆分显示名称',
|
||||
sum_field_key VARCHAR(100) NULL COMMENT '求和字段键名',
|
||||
sum_field_name VARCHAR(100) NULL COMMENT '求和字段中文名称',
|
||||
sum_value DECIMAL(18,3) NULL COMMENT '求和值',
|
||||
total_orders INT NULL COMMENT '订单数',
|
||||
data_source VARCHAR(20) NULL COMMENT '数据来源 auto=自动采集 manual=手动编辑',
|
||||
remark VARCHAR(500) NULL COMMENT '备注',
|
||||
create_time DATETIME NULL COMMENT '创建时间',
|
||||
update_time DATETIME NULL COMMENT '更新时间'
|
||||
)
|
||||
UNIQUE KEY(id)
|
||||
DISTRIBUTED BY HASH(id) BUCKETS 1
|
||||
PROPERTIES("replication_num" = "1")
|
||||
"""
|
||||
execute_update(create_sum_data_table)
|
||||
print("[数据库] t_daily_report_sum_data 表已就绪")
|
||||
|
||||
# 数据迁移:给 sort_order 为 NULL 的配置记录按 id 顺序赋值
|
||||
try:
|
||||
null_count = execute_query(
|
||||
"SELECT COUNT(*) as cnt FROM t_daily_report_config WHERE sort_order IS NULL"
|
||||
)
|
||||
if null_count and null_count[0]['cnt'] > 0:
|
||||
print(f"[数据库] 检测到 {null_count[0]['cnt']} 条配置 sort_order 为空,开始初始化排序...")
|
||||
all_configs = execute_query(
|
||||
"SELECT id FROM t_daily_report_config ORDER BY id ASC"
|
||||
)
|
||||
for idx, cfg in enumerate(all_configs):
|
||||
execute_update(
|
||||
"UPDATE t_daily_report_config SET sort_order = %s WHERE id = %s",
|
||||
(idx + 1, cfg['id'])
|
||||
)
|
||||
print("[数据库] 配置 sort_order 初始化完成")
|
||||
except Exception as e:
|
||||
print(f"[数据库] 初始化 sort_order 时出错: {e}")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"[数据库] 初始化失败: {e}")
|
||||
return False
|
||||
@@ -450,6 +450,12 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
|
||||
total = sum(float(order.get(field, 0) or 0) for order in orders)
|
||||
sum_results[field] = round(total, 3)
|
||||
|
||||
# 强制采集充电电量(charge_degree),即使不在求和字段配置中也要采集
|
||||
if 'charge_degree' not in sum_results and orders and 'charge_degree' in orders[0]:
|
||||
total_charge_degree = sum(float(order.get('charge_degree', 0) or 0) for order in orders)
|
||||
sum_results['charge_degree'] = round(total_charge_degree, 3)
|
||||
print(f"[日报生成] 强制采集充电电量: {sum_results['charge_degree']} kWh")
|
||||
|
||||
# 构建ID到名称的映射
|
||||
id_name_maps = build_id_to_name_map(orders, selected_fields)
|
||||
|
||||
@@ -492,6 +498,18 @@ def generate_daily_report(config_id, start_time=None, end_time=None):
|
||||
)
|
||||
print(f"[日报生成] 历史记录已保存,ID={history_id}")
|
||||
|
||||
# 自动采集求和数据到求和数据表
|
||||
try:
|
||||
if sum_results:
|
||||
from lib.sum_data_collector import collect_sum_data_from_history
|
||||
collect_result = collect_sum_data_from_history(history_id)
|
||||
if collect_result['success']:
|
||||
print(f"[日报生成] 求和数据采集成功")
|
||||
else:
|
||||
print(f"[日报生成] 求和数据采集失败: {collect_result['message']}")
|
||||
except Exception as collect_error:
|
||||
print(f"[日报生成] 求和数据采集异常: {str(collect_error)}")
|
||||
|
||||
print(f"[日报生成] ✓ 生成成功")
|
||||
print(f" 订单数量: {len(orders)}")
|
||||
print(f" 总金额: {total_amount}")
|
||||
|
||||
409
lib/sum_data_collector.py
Normal file
409
lib/sum_data_collector.py
Normal file
@@ -0,0 +1,409 @@
|
||||
"""
|
||||
日报求和数据采集与管理
|
||||
"""
|
||||
import json
|
||||
from datetime import datetime
|
||||
from lib.db import execute_query, execute_update, execute_insert
|
||||
from lib.field_mapping import get_field_display_name
|
||||
|
||||
|
||||
def collect_sum_data_from_history(history_id=None):
|
||||
"""
|
||||
从历史记录中采集求和数据并写入求和数据表
|
||||
充电电量(charge_degree)是必须采集的,其他求和字段有就采集,没有就不采集
|
||||
|
||||
Args:
|
||||
history_id: 历史记录ID(可选,不填则采集所有成功的历史记录)
|
||||
|
||||
Returns:
|
||||
dict: 采集结果
|
||||
"""
|
||||
try:
|
||||
# 查询历史记录
|
||||
if history_id:
|
||||
history_list = execute_query(
|
||||
'SELECT * FROM t_daily_report_history WHERE id = %s AND status = 1',
|
||||
(history_id,)
|
||||
)
|
||||
else:
|
||||
history_list = execute_query(
|
||||
'SELECT * FROM t_daily_report_history WHERE status = 1 ORDER BY create_time DESC'
|
||||
)
|
||||
|
||||
if not history_list:
|
||||
return {'success': False, 'message': '没有找到符合条件的历史记录'}
|
||||
|
||||
collected_count = 0
|
||||
skipped_count = 0
|
||||
|
||||
for history in history_list:
|
||||
sum_results = history.get('sum_results')
|
||||
|
||||
# 解析 sum_results JSON
|
||||
if isinstance(sum_results, str):
|
||||
sum_results = json.loads(sum_results) if sum_results else {}
|
||||
elif sum_results is None:
|
||||
sum_results = {}
|
||||
|
||||
# 强制确保有充电电量(charge_degree)
|
||||
if 'charge_degree' not in sum_results or sum_results['charge_degree'] is None:
|
||||
# 从订单表重新计算充电电量
|
||||
try:
|
||||
charge_degree = _calculate_charge_degree(history)
|
||||
if charge_degree is not None:
|
||||
sum_results['charge_degree'] = charge_degree
|
||||
print(f"[采集] 历史记录 {history['id']} 补充计算充电电量: {charge_degree} kWh")
|
||||
except Exception as calc_error:
|
||||
print(f"[采集] 历史记录 {history['id']} 计算充电电量失败: {calc_error}")
|
||||
|
||||
# 如果还是没有任何求和数据,跳过
|
||||
if not sum_results:
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
# 先删除该历史记录对应的旧数据(避免重复)
|
||||
execute_update(
|
||||
'''DELETE FROM t_daily_report_sum_data
|
||||
WHERE report_date = %s AND config_id = %s AND split_value = %s AND data_source = 'auto'
|
||||
''',
|
||||
(history['report_date'], history['config_id'], history['split_value'])
|
||||
)
|
||||
|
||||
# 逐条插入求和字段数据
|
||||
for field_key, field_value in sum_results.items():
|
||||
sum_data_id = int(datetime.now().timestamp() * 1000000) + collected_count
|
||||
sql = '''
|
||||
INSERT INTO t_daily_report_sum_data
|
||||
(id, report_date, config_id, config_name, split_type, split_value, split_name,
|
||||
sum_field_key, sum_field_name, sum_value, total_orders, data_source, remark,
|
||||
create_time, update_time)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW())
|
||||
'''
|
||||
params = (
|
||||
sum_data_id,
|
||||
history['report_date'],
|
||||
history['config_id'],
|
||||
history.get('config_name', ''),
|
||||
history.get('split_type', ''),
|
||||
history.get('split_value', ''),
|
||||
history.get('split_name', ''),
|
||||
field_key,
|
||||
get_field_display_name(field_key),
|
||||
float(field_value) if field_value is not None else 0,
|
||||
history.get('total_orders', 0),
|
||||
'auto',
|
||||
f'从历史记录自动采集,历史ID: {history["id"]}'
|
||||
)
|
||||
execute_insert(sql, params)
|
||||
collected_count += 1
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'message': f'采集完成,共采集 {collected_count} 条求和数据,跳过 {skipped_count} 条无求和数据的记录'
|
||||
}
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return {'success': False, 'message': f'采集失败: {str(e)}'}
|
||||
|
||||
|
||||
def _calculate_charge_degree(history):
|
||||
"""
|
||||
从订单表中计算指定历史记录对应的充电电量总和
|
||||
|
||||
Args:
|
||||
history: 历史记录字典
|
||||
|
||||
Returns:
|
||||
float: 充电电量总和(保留三位小数),失败返回 None
|
||||
"""
|
||||
try:
|
||||
split_type = history.get('split_type', '')
|
||||
split_value = history.get('split_value', '')
|
||||
start_time = history.get('start_time', '')
|
||||
end_time = history.get('end_time', '')
|
||||
|
||||
if not split_type or not split_value or not start_time or not end_time:
|
||||
return None
|
||||
|
||||
# 检查是否为多选值(逗号分隔)
|
||||
split_values = [v.strip() for v in str(split_value).split(',') if v.strip()]
|
||||
|
||||
if len(split_values) > 1:
|
||||
placeholders = ', '.join(['%s'] * len(split_values))
|
||||
sql = f"""
|
||||
SELECT SUM(charge_degree) as total_degree
|
||||
FROM t_equipment_charge_order
|
||||
WHERE state = 3
|
||||
AND report_time >= %s
|
||||
AND report_time < %s
|
||||
AND {split_type} IN ({placeholders})
|
||||
"""
|
||||
params = [start_time, end_time] + split_values
|
||||
else:
|
||||
sql = f"""
|
||||
SELECT SUM(charge_degree) as total_degree
|
||||
FROM t_equipment_charge_order
|
||||
WHERE state = 3
|
||||
AND report_time >= %s
|
||||
AND report_time < %s
|
||||
AND {split_type} = %s
|
||||
"""
|
||||
params = [start_time, end_time, split_value]
|
||||
|
||||
result = execute_query(sql, tuple(params))
|
||||
if result and result[0].get('total_degree') is not None:
|
||||
return round(float(result[0]['total_degree']), 3)
|
||||
|
||||
return 0.0
|
||||
except Exception as e:
|
||||
print(f"[计算充电电量] 失败: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def get_sum_data_list(page=1, page_size=20, report_date=None, config_id=None,
|
||||
config_name=None, sum_field_key=None, data_source=None):
|
||||
"""
|
||||
查询求和数据列表
|
||||
|
||||
Args:
|
||||
page: 页码
|
||||
page_size: 每页大小
|
||||
report_date: 报表日期(可选)
|
||||
config_id: 配置ID(可选)
|
||||
config_name: 配置名称(可选,模糊搜索)
|
||||
sum_field_key: 求和字段键名(可选)
|
||||
data_source: 数据来源(可选)
|
||||
|
||||
Returns:
|
||||
dict: 查询结果
|
||||
"""
|
||||
try:
|
||||
offset = (page - 1) * page_size
|
||||
|
||||
where_clauses = ['1=1']
|
||||
params = []
|
||||
|
||||
if report_date:
|
||||
where_clauses.append('report_date = %s')
|
||||
params.append(report_date)
|
||||
|
||||
if config_id:
|
||||
where_clauses.append('config_id = %s')
|
||||
params.append(config_id)
|
||||
|
||||
if config_name:
|
||||
where_clauses.append('config_name LIKE %s')
|
||||
params.append(f'%{config_name}%')
|
||||
|
||||
if sum_field_key:
|
||||
where_clauses.append('sum_field_key = %s')
|
||||
params.append(sum_field_key)
|
||||
|
||||
if data_source:
|
||||
where_clauses.append('data_source = %s')
|
||||
params.append(data_source)
|
||||
|
||||
where_sql = ' AND '.join(where_clauses)
|
||||
|
||||
# 查询总数
|
||||
total_result = execute_query(
|
||||
f'SELECT COUNT(*) as total FROM t_daily_report_sum_data WHERE {where_sql}',
|
||||
tuple(params)
|
||||
)
|
||||
total = total_result[0]['total']
|
||||
|
||||
# 查询数据
|
||||
list_result = execute_query(
|
||||
f'''SELECT * FROM t_daily_report_sum_data
|
||||
WHERE {where_sql}
|
||||
ORDER BY report_date DESC, config_id, id DESC
|
||||
LIMIT %s OFFSET %s''',
|
||||
tuple(params) + (page_size, offset)
|
||||
)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'data': {
|
||||
'list': list_result,
|
||||
'total': total,
|
||||
'page': page,
|
||||
'page_size': page_size
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return {'success': False, 'message': str(e)}
|
||||
|
||||
|
||||
def add_sum_data(data):
|
||||
"""
|
||||
手动添加求和数据
|
||||
|
||||
Args:
|
||||
data: 求和数据字典
|
||||
|
||||
Returns:
|
||||
dict: 操作结果
|
||||
"""
|
||||
try:
|
||||
sum_data_id = int(datetime.now().timestamp() * 1000)
|
||||
sql = '''
|
||||
INSERT INTO t_daily_report_sum_data
|
||||
(id, report_date, config_id, config_name, split_type, split_value, split_name,
|
||||
sum_field_key, sum_field_name, sum_value, total_orders, data_source, remark,
|
||||
create_time, update_time)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW())
|
||||
'''
|
||||
params = (
|
||||
sum_data_id,
|
||||
data.get('report_date', ''),
|
||||
data.get('config_id'),
|
||||
data.get('config_name', ''),
|
||||
data.get('split_type', ''),
|
||||
data.get('split_value', ''),
|
||||
data.get('split_name', ''),
|
||||
data.get('sum_field_key', ''),
|
||||
data.get('sum_field_name', ''),
|
||||
float(data.get('sum_value', 0)),
|
||||
data.get('total_orders', 0),
|
||||
'manual',
|
||||
data.get('remark', '')
|
||||
)
|
||||
execute_insert(sql, params)
|
||||
return {'success': True, 'message': '添加成功', 'id': sum_data_id}
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return {'success': False, 'message': str(e)}
|
||||
|
||||
|
||||
def update_sum_data(sum_data_id, data):
|
||||
"""
|
||||
更新求和数据
|
||||
|
||||
Args:
|
||||
sum_data_id: 求和数据ID
|
||||
data: 更新的数据字典
|
||||
|
||||
Returns:
|
||||
dict: 操作结果
|
||||
"""
|
||||
try:
|
||||
update_fields = []
|
||||
params = []
|
||||
|
||||
if 'report_date' in data:
|
||||
update_fields.append('report_date = %s')
|
||||
params.append(data['report_date'])
|
||||
|
||||
if 'config_id' in data:
|
||||
update_fields.append('config_id = %s')
|
||||
params.append(data['config_id'])
|
||||
|
||||
if 'config_name' in data:
|
||||
update_fields.append('config_name = %s')
|
||||
params.append(data['config_name'])
|
||||
|
||||
if 'split_type' in data:
|
||||
update_fields.append('split_type = %s')
|
||||
params.append(data['split_type'])
|
||||
|
||||
if 'split_value' in data:
|
||||
update_fields.append('split_value = %s')
|
||||
params.append(data['split_value'])
|
||||
|
||||
if 'split_name' in data:
|
||||
update_fields.append('split_name = %s')
|
||||
params.append(data['split_name'])
|
||||
|
||||
if 'sum_field_key' in data:
|
||||
update_fields.append('sum_field_key = %s')
|
||||
params.append(data['sum_field_key'])
|
||||
|
||||
if 'sum_field_name' in data:
|
||||
update_fields.append('sum_field_name = %s')
|
||||
params.append(data['sum_field_name'])
|
||||
|
||||
if 'sum_value' in data:
|
||||
update_fields.append('sum_value = %s')
|
||||
params.append(float(data['sum_value']))
|
||||
|
||||
if 'total_orders' in data:
|
||||
update_fields.append('total_orders = %s')
|
||||
params.append(data['total_orders'])
|
||||
|
||||
if 'remark' in data:
|
||||
update_fields.append('remark = %s')
|
||||
params.append(data['remark'])
|
||||
|
||||
if not update_fields:
|
||||
return {'success': False, 'message': '没有需要更新的字段'}
|
||||
|
||||
update_fields.append('update_time = NOW()')
|
||||
params.append(sum_data_id)
|
||||
|
||||
sql = f'''
|
||||
UPDATE t_daily_report_sum_data
|
||||
SET {', '.join(update_fields)}
|
||||
WHERE id = %s
|
||||
'''
|
||||
rowcount = execute_update(sql, tuple(params))
|
||||
|
||||
if rowcount > 0:
|
||||
return {'success': True, 'message': '更新成功'}
|
||||
else:
|
||||
return {'success': False, 'message': '记录不存在'}
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return {'success': False, 'message': str(e)}
|
||||
|
||||
|
||||
def delete_sum_data(sum_data_id):
|
||||
"""
|
||||
删除求和数据
|
||||
|
||||
Args:
|
||||
sum_data_id: 求和数据ID
|
||||
|
||||
Returns:
|
||||
dict: 操作结果
|
||||
"""
|
||||
try:
|
||||
rowcount = execute_update(
|
||||
'DELETE FROM t_daily_report_sum_data WHERE id = %s',
|
||||
(sum_data_id,)
|
||||
)
|
||||
if rowcount > 0:
|
||||
return {'success': True, 'message': '删除成功'}
|
||||
else:
|
||||
return {'success': False, 'message': '记录不存在'}
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return {'success': False, 'message': str(e)}
|
||||
|
||||
|
||||
def batch_delete_sum_data(ids):
|
||||
"""
|
||||
批量删除求和数据
|
||||
|
||||
Args:
|
||||
ids: ID列表
|
||||
|
||||
Returns:
|
||||
dict: 操作结果
|
||||
"""
|
||||
try:
|
||||
placeholders = ', '.join(['%s'] * len(ids))
|
||||
rowcount = execute_update(
|
||||
f'DELETE FROM t_daily_report_sum_data WHERE id IN ({placeholders})',
|
||||
tuple(ids)
|
||||
)
|
||||
return {'success': True, 'message': f'成功删除 {rowcount} 条记录'}
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return {'success': False, 'message': str(e)}
|
||||
@@ -30,6 +30,8 @@ function switchTab(tabName) {
|
||||
loadGenerateConfigs();
|
||||
} else if (tabName === 'history') {
|
||||
loadHistory();
|
||||
} else if (tabName === 'sum-data') {
|
||||
loadSumData();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// 当前配置列表缓存(用于乐观更新)
|
||||
let currentConfigList = [];
|
||||
|
||||
// 加载配置列表
|
||||
async function loadConfigs() {
|
||||
try {
|
||||
@@ -5,7 +8,8 @@ async function loadConfigs() {
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
renderConfigList(result.data);
|
||||
currentConfigList = result.data;
|
||||
renderConfigList(currentConfigList);
|
||||
} else {
|
||||
alert('加载配置失败:' + result.message);
|
||||
}
|
||||
@@ -23,7 +27,10 @@ function renderConfigList(configs) {
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = configs.map(config => `
|
||||
tbody.innerHTML = configs.map((config, index) => {
|
||||
const isFirst = index === 0;
|
||||
const isLast = index === configs.length - 1;
|
||||
return `
|
||||
<tr>
|
||||
<td>${config.config_name}</td>
|
||||
<td>${config.split_type === 'company_id' ? '按企业 ID' : config.split_type === 'user_id' ? '按用户 ID' : '按场站 ID'}</td>
|
||||
@@ -41,11 +48,11 @@ function renderConfigList(configs) {
|
||||
<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>
|
||||
<button class="btn btn-sm" onclick="moveConfig(${config.id}, 'up')" ${isFirst ? 'disabled style="opacity:0.4;cursor:not-allowed;"' : ''}>↑</button>
|
||||
<button class="btn btn-sm" onclick="moveConfig(${config.id}, 'down')" ${isLast ? 'disabled style="opacity:0.4;cursor:not-allowed;"' : ''}>↓</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
`}).join('');
|
||||
}
|
||||
|
||||
function showCreateModal() {
|
||||
@@ -776,6 +783,21 @@ async function toggleConfig(configId) {
|
||||
|
||||
// 移动配置
|
||||
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',
|
||||
@@ -784,12 +806,28 @@ async function moveConfig(configId, direction) {
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
loadConfigs();
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
alert('移动失败: ' + 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);
|
||||
}
|
||||
alert('移动失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
527
public/static/js/sum_data.js
Normal file
527
public/static/js/sum_data.js
Normal file
@@ -0,0 +1,527 @@
|
||||
// 求和数据管理
|
||||
let sumDataCurrentPage = 1;
|
||||
let sumDataPageSize = 10;
|
||||
let editingSumDataId = null;
|
||||
let sumDataGroupedList = [];
|
||||
let sumDataDynamicFields = [];
|
||||
|
||||
// 分组并渲染求和数据列表
|
||||
function renderSumDataList(rawList) {
|
||||
const tbody = document.getElementById('sum-data-list');
|
||||
const totalColCount = 7 + (rawList ? 0 : 0) + 3; // 占位,后面重新计算
|
||||
|
||||
if (!rawList || rawList.length === 0) {
|
||||
// 清空动态表头
|
||||
const chargeTh = document.getElementById('th-charge-degree');
|
||||
const totalOrdersTh = document.getElementById('th-total-orders');
|
||||
let nextSibling = chargeTh.nextElementSibling;
|
||||
while (nextSibling && nextSibling !== totalOrdersTh) {
|
||||
const toRemove = nextSibling;
|
||||
nextSibling = nextSibling.nextElementSibling;
|
||||
toRemove.remove();
|
||||
}
|
||||
tbody.innerHTML = '<tr><td colspan="10" class="empty">暂无数据</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
// 收集所有动态求和字段(除了充电电量 charge_degree)
|
||||
const fieldMap = {};
|
||||
rawList.forEach(item => {
|
||||
const key = item.sum_field_key;
|
||||
if (key && key !== 'charge_degree' && !fieldMap[key]) {
|
||||
fieldMap[key] = item.sum_field_name || key;
|
||||
}
|
||||
});
|
||||
|
||||
// 排序:峰平谷按顺序
|
||||
const fieldOrder = ['sharp_electricity', 'peak_electricity', 'flat_electricity', 'valley_electricity'];
|
||||
sumDataDynamicFields = Object.keys(fieldMap).sort((a, b) => {
|
||||
const idxA = fieldOrder.indexOf(a);
|
||||
const idxB = fieldOrder.indexOf(b);
|
||||
if (idxA >= 0 && idxB >= 0) return idxA - idxB;
|
||||
if (idxA >= 0) return -1;
|
||||
if (idxB >= 0) return 1;
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
|
||||
// 渲染动态列表头(在充电电量列之后插入)
|
||||
const chargeTh = document.getElementById('th-charge-degree');
|
||||
const totalOrdersTh = document.getElementById('th-total-orders');
|
||||
|
||||
// 先移除旧的动态 th(充电电量和订单数之间的)
|
||||
let nextSibling = chargeTh.nextElementSibling;
|
||||
while (nextSibling && nextSibling !== totalOrdersTh) {
|
||||
const toRemove = nextSibling;
|
||||
nextSibling = nextSibling.nextElementSibling;
|
||||
toRemove.remove();
|
||||
}
|
||||
|
||||
// 插入新的动态 th
|
||||
sumDataDynamicFields.forEach(key => {
|
||||
const name = fieldMap[key];
|
||||
const th = document.createElement('th');
|
||||
th.style.minWidth = '110px';
|
||||
th.textContent = name;
|
||||
totalOrdersTh.before(th);
|
||||
});
|
||||
|
||||
// 按 报表日期 + 配置ID + 拆分值 分组
|
||||
const groupMap = {};
|
||||
rawList.forEach(item => {
|
||||
const groupKey = `${item.report_date}_${item.config_id}_${item.split_value}`;
|
||||
if (!groupMap[groupKey]) {
|
||||
groupMap[groupKey] = {
|
||||
report_date: item.report_date,
|
||||
config_id: item.config_id,
|
||||
config_name: item.config_name,
|
||||
split_type: item.split_type,
|
||||
split_value: item.split_value,
|
||||
split_name: item.split_name,
|
||||
total_orders: item.total_orders || 0,
|
||||
data_source: item.data_source,
|
||||
remark: item.remark || '',
|
||||
fields: {},
|
||||
ids: []
|
||||
};
|
||||
}
|
||||
groupMap[groupKey].fields[item.sum_field_key] = {
|
||||
value: item.sum_value,
|
||||
id: item.id,
|
||||
name: item.sum_field_name
|
||||
};
|
||||
groupMap[groupKey].ids.push(item.id);
|
||||
// 取最新的备注和数据来源
|
||||
if (item.remark) groupMap[groupKey].remark = item.remark;
|
||||
if (item.data_source === 'manual') groupMap[groupKey].data_source = 'manual';
|
||||
});
|
||||
|
||||
sumDataGroupedList = Object.values(groupMap).sort((a, b) => {
|
||||
if (a.report_date !== b.report_date) return b.report_date.localeCompare(a.report_date);
|
||||
return (a.config_name || '').localeCompare(b.config_name || '');
|
||||
});
|
||||
|
||||
// 分页处理
|
||||
const total = sumDataGroupedList.length;
|
||||
const start = (sumDataCurrentPage - 1) * sumDataPageSize;
|
||||
const pageData = sumDataGroupedList.slice(start, start + sumDataPageSize);
|
||||
|
||||
const totalColSpan = 9 + sumDataDynamicFields.length; // 复选+日期+配置+拆分值+充电电量+动态列+订单数+来源+备注+操作
|
||||
|
||||
if (pageData.length === 0) {
|
||||
tbody.innerHTML = `<tr><td colspan="${totalColSpan}" class="empty">暂无数据</td></tr>`;
|
||||
renderSumDataPagination(total);
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = pageData.map(group => {
|
||||
const chargeDegree = group.fields['charge_degree'];
|
||||
const chargeValue = chargeDegree && chargeDegree.value !== null && chargeDegree.value !== undefined
|
||||
? Number(chargeDegree.value).toFixed(3) : '-';
|
||||
|
||||
const dataSource = group.data_source === 'auto' ? '自动采集' : '手动编辑';
|
||||
const dataSourceClass = group.data_source === 'auto' ? 'status-active' : 'status-inactive';
|
||||
const remark = group.remark || '-';
|
||||
|
||||
const dynamicCells = sumDataDynamicFields.map(key => {
|
||||
const field = group.fields[key];
|
||||
const val = field && field.value !== null && field.value !== undefined
|
||||
? Number(field.value).toFixed(3) : '-';
|
||||
return `<td>${val}</td>`;
|
||||
}).join('');
|
||||
|
||||
const firstId = chargeDegree ? chargeDegree.id : (group.ids[0] || 0);
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td><input type="checkbox" class="sum-data-checkbox" value="${group.ids.join(',')}"></td>
|
||||
<td>${group.report_date || '-'}</td>
|
||||
<td>${group.config_name || '-'}</td>
|
||||
<td title="${group.split_name || ''}">${group.split_name || group.split_value || '-'}</td>
|
||||
<td style="background: #f8fafc; font-weight: 600; color: #1d4ed8;">${chargeValue}</td>
|
||||
${dynamicCells}
|
||||
<td>${group.total_orders || 0}</td>
|
||||
<td><span class="status-badge ${dataSourceClass}">${dataSource}</span></td>
|
||||
<td title="${remark}" style="max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${remark}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" onclick='showEditSumDataGroup(${JSON.stringify(group.ids)})'>编辑</button>
|
||||
<button class="btn btn-sm btn-danger" onclick='deleteSumDataGroup(${JSON.stringify(group.ids)})'>删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
renderSumDataPagination(total);
|
||||
}
|
||||
|
||||
// 渲染分页
|
||||
function renderSumDataPagination(total) {
|
||||
const pagination = document.getElementById('sum-data-pagination');
|
||||
const totalPages = Math.ceil(total / sumDataPageSize);
|
||||
if (totalPages <= 1) {
|
||||
pagination.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
if (sumDataCurrentPage > 1) {
|
||||
html += `<button class="btn btn-sm" onclick="loadSumData(${sumDataCurrentPage - 1})">上一页</button>`;
|
||||
}
|
||||
html += `<span class="page-info">第 ${sumDataCurrentPage} / ${totalPages} 页,共 ${total} 条</span>`;
|
||||
if (sumDataCurrentPage < totalPages) {
|
||||
html += `<button class="btn btn-sm" onclick="loadSumData(${sumDataCurrentPage + 1})">下一页</button>`;
|
||||
}
|
||||
pagination.innerHTML = html;
|
||||
}
|
||||
|
||||
// 加载求和数据
|
||||
async function loadSumData(page = 1) {
|
||||
sumDataCurrentPage = page;
|
||||
const reportDate = document.getElementById('sum-data-report-date').value;
|
||||
const configName = document.getElementById('sum-data-config-name').value;
|
||||
const dataSource = document.getElementById('sum-data-source').value;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/sum-data?page=1&page_size=5000&report_date=${encodeURIComponent(reportDate)}&config_name=${encodeURIComponent(configName)}&data_source=${encodeURIComponent(dataSource)}`);
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
renderSumDataList(result.data.list || []);
|
||||
} else {
|
||||
document.getElementById('sum-data-list').innerHTML = '<tr><td colspan="12" class="empty">加载失败: ' + (result.message || '未知错误') + '</td></tr>';
|
||||
}
|
||||
} catch (error) {
|
||||
document.getElementById('sum-data-list').innerHTML = '<tr><td colspan="12" class="empty">加载失败: ' + error.message + '</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
// 清除查询
|
||||
function clearSumDataQuery() {
|
||||
document.getElementById('sum-data-report-date').value = '';
|
||||
document.getElementById('sum-data-config-name').value = '';
|
||||
document.getElementById('sum-data-source').value = '';
|
||||
loadSumData(1);
|
||||
}
|
||||
|
||||
// 全选/取消全选
|
||||
function toggleSelectAllSumData() {
|
||||
const checked = document.getElementById('select-all-sum-data').checked;
|
||||
document.querySelectorAll('.sum-data-checkbox').forEach(cb => {
|
||||
cb.checked = checked;
|
||||
});
|
||||
}
|
||||
|
||||
// 从历史记录采集
|
||||
async function collectSumDataFromHistory() {
|
||||
if (!confirm('确定要从所有成功的历史记录中重新采集求和数据吗?\n(会先清除旧的自动采集数据)')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/sum-data/collect', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
alert(result.message);
|
||||
loadSumData(1);
|
||||
} else {
|
||||
alert('采集失败: ' + (result.message || '未知错误'));
|
||||
}
|
||||
} catch (error) {
|
||||
alert('采集失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 显示添加模态框
|
||||
function showAddSumDataModal() {
|
||||
editingSumDataId = null;
|
||||
document.getElementById('sum-data-modal-title').textContent = '手动添加求和数据';
|
||||
document.getElementById('sum-data-id').value = '';
|
||||
document.getElementById('sum-data-report-date-input').value = '';
|
||||
document.getElementById('sum-data-config-name-input').value = '';
|
||||
document.getElementById('sum-data-split-type-input').value = 'company_id';
|
||||
document.getElementById('sum-data-split-value-input').value = '';
|
||||
document.getElementById('sum-data-split-name-input').value = '';
|
||||
document.getElementById('sum-data-field-key-input').value = '';
|
||||
document.getElementById('sum-data-field-name-input').value = '';
|
||||
document.getElementById('sum-data-value-input').value = '';
|
||||
document.getElementById('sum-data-total-orders-input').value = 0;
|
||||
document.getElementById('sum-data-remark-input').value = '';
|
||||
document.getElementById('sum-data-modal').classList.add('active');
|
||||
}
|
||||
|
||||
// 显示编辑分组模态框
|
||||
let editingGroupIds = [];
|
||||
let editingGroupData = null;
|
||||
|
||||
async function showEditSumDataGroup(ids) {
|
||||
try {
|
||||
// 获取所有选中的记录详情
|
||||
const response = await fetch(`/api/sum-data?page=1&page_size=5000`);
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success) {
|
||||
alert('加载详情失败');
|
||||
return;
|
||||
}
|
||||
|
||||
const allList = result.data.list || [];
|
||||
const groupItems = allList.filter(item => ids.includes(item.id));
|
||||
|
||||
if (groupItems.length === 0) {
|
||||
alert('未找到该记录');
|
||||
return;
|
||||
}
|
||||
|
||||
editingGroupIds = ids;
|
||||
editingGroupData = groupItems;
|
||||
|
||||
// 找到充电电量那条作为主记录
|
||||
const chargeItem = groupItems.find(i => i.sum_field_key === 'charge_degree') || groupItems[0];
|
||||
|
||||
document.getElementById('sum-data-modal-title').textContent = '编辑求和数据';
|
||||
document.getElementById('sum-data-id').value = chargeItem.id;
|
||||
document.getElementById('sum-data-report-date-input').value = chargeItem.report_date || '';
|
||||
document.getElementById('sum-data-config-name-input').value = chargeItem.config_name || '';
|
||||
document.getElementById('sum-data-split-type-input').value = chargeItem.split_type || 'company_id';
|
||||
document.getElementById('sum-data-split-value-input').value = chargeItem.split_value || '';
|
||||
document.getElementById('sum-data-split-name-input').value = chargeItem.split_name || '';
|
||||
|
||||
// 动态生成字段输入区域
|
||||
renderEditGroupFields(groupItems);
|
||||
|
||||
document.getElementById('sum-data-total-orders-input').value = chargeItem.total_orders || 0;
|
||||
document.getElementById('sum-data-remark-input').value = '';
|
||||
document.getElementById('sum-data-modal').classList.add('active');
|
||||
} catch (error) {
|
||||
alert('加载详情失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染编辑分组时的动态字段输入
|
||||
function renderEditGroupFields(items) {
|
||||
const fieldKeyInput = document.getElementById('sum-data-field-key-input');
|
||||
const fieldNameInput = document.getElementById('sum-data-field-name-input');
|
||||
const valueInput = document.getElementById('sum-data-value-input');
|
||||
|
||||
// 先构造动态字段区域
|
||||
let fieldsHtml = '<div style="margin-bottom: 15px;"><label style="display: block; margin-bottom: 8px; font-weight: 600;">求和字段值:</label>';
|
||||
fieldsHtml += '<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">';
|
||||
|
||||
// 先放充电电量
|
||||
const chargeItem = items.find(i => i.sum_field_key === 'charge_degree');
|
||||
if (chargeItem) {
|
||||
fieldsHtml += `
|
||||
<div class="form-group" style="margin-bottom: 0;">
|
||||
<label>${chargeItem.sum_field_name || chargeItem.sum_field_key} (必选):</label>
|
||||
<input type="number" step="0.001" class="form-control"
|
||||
data-field-key="${chargeItem.sum_field_key}"
|
||||
data-field-name="${chargeItem.sum_field_name || chargeItem.sum_field_key}"
|
||||
data-field-id="${chargeItem.id}"
|
||||
value="${chargeItem.sum_value || 0}">
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 再放其他字段
|
||||
items.filter(i => i.sum_field_key !== 'charge_degree').forEach(item => {
|
||||
fieldsHtml += `
|
||||
<div class="form-group" style="margin-bottom: 0;">
|
||||
<label>${item.sum_field_name || item.sum_field_key}:</label>
|
||||
<input type="number" step="0.001" class="form-control"
|
||||
data-field-key="${item.sum_field_key}"
|
||||
data-field-name="${item.sum_field_name || item.sum_field_key}"
|
||||
data-field-id="${item.id}"
|
||||
value="${item.sum_value || 0}">
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
fieldsHtml += '</div></div>';
|
||||
|
||||
// 替换掉原来的单字段输入
|
||||
fieldKeyInput.parentElement.style.display = 'none';
|
||||
fieldNameInput.parentElement.style.display = 'none';
|
||||
valueInput.parentElement.style.display = 'none';
|
||||
|
||||
// 插入到订单数前面
|
||||
const totalOrdersGroup = document.getElementById('sum-data-total-orders-input').parentElement;
|
||||
|
||||
// 检查是否已经有动态区域了
|
||||
let dynamicArea = document.getElementById('sum-data-dynamic-fields');
|
||||
if (!dynamicArea) {
|
||||
dynamicArea = document.createElement('div');
|
||||
dynamicArea.id = 'sum-data-dynamic-fields';
|
||||
totalOrdersGroup.parentElement.insertBefore(dynamicArea, totalOrdersGroup);
|
||||
}
|
||||
dynamicArea.innerHTML = fieldsHtml;
|
||||
}
|
||||
|
||||
// 保存求和数据(支持分组编辑)
|
||||
async function saveSumData() {
|
||||
const remark = document.getElementById('sum-data-remark-input').value;
|
||||
|
||||
// 编辑时修改原因为必填
|
||||
if (editingGroupIds && editingGroupIds.length > 0 && !remark.trim()) {
|
||||
alert('请填写修改原因');
|
||||
return;
|
||||
}
|
||||
|
||||
const totalOrders = document.getElementById('sum-data-total-orders-input').value;
|
||||
|
||||
try {
|
||||
// 分组编辑模式
|
||||
if (editingGroupIds && editingGroupIds.length > 0) {
|
||||
// 读取动态字段值
|
||||
const fieldInputs = document.querySelectorAll('#sum-data-dynamic-fields input[data-field-key]');
|
||||
let successCount = 0;
|
||||
|
||||
for (const input of fieldInputs) {
|
||||
const fieldId = input.getAttribute('data-field-id');
|
||||
const fieldValue = input.value;
|
||||
|
||||
const response = await fetch(`/api/sum-data/${fieldId}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
sum_value: fieldValue,
|
||||
total_orders: totalOrders,
|
||||
remark: remark
|
||||
})
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) successCount++;
|
||||
}
|
||||
|
||||
if (successCount > 0) {
|
||||
alert(`更新成功(${successCount} 个字段)`);
|
||||
closeSumDataModal();
|
||||
loadSumData(sumDataCurrentPage);
|
||||
} else {
|
||||
alert('更新失败');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 单条新增模式
|
||||
const data = {
|
||||
report_date: document.getElementById('sum-data-report-date-input').value,
|
||||
config_name: document.getElementById('sum-data-config-name-input').value,
|
||||
split_type: document.getElementById('sum-data-split-type-input').value,
|
||||
split_value: document.getElementById('sum-data-split-value-input').value,
|
||||
split_name: document.getElementById('sum-data-split-name-input').value,
|
||||
sum_field_key: document.getElementById('sum-data-field-key-input').value,
|
||||
sum_field_name: document.getElementById('sum-data-field-name-input').value,
|
||||
sum_value: document.getElementById('sum-data-value-input').value,
|
||||
total_orders: totalOrders,
|
||||
remark: remark
|
||||
};
|
||||
|
||||
if (!data.report_date) {
|
||||
alert('请选择报表日期');
|
||||
return;
|
||||
}
|
||||
if (!data.sum_field_key) {
|
||||
alert('请输入求和字段键名');
|
||||
return;
|
||||
}
|
||||
if (data.sum_value === '' || data.sum_value === null) {
|
||||
alert('请输入求和值');
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch('/api/sum-data', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
alert('添加成功');
|
||||
closeSumDataModal();
|
||||
loadSumData(sumDataCurrentPage);
|
||||
} else {
|
||||
alert('保存失败: ' + (result.message || '未知错误'));
|
||||
}
|
||||
} catch (error) {
|
||||
alert('保存失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭模态框
|
||||
function closeSumDataModal() {
|
||||
document.getElementById('sum-data-modal').classList.remove('active');
|
||||
editingGroupIds = [];
|
||||
editingGroupData = null;
|
||||
// 恢复单字段输入的显示
|
||||
document.querySelector('#sum-data-field-key-input').parentElement.style.display = '';
|
||||
document.querySelector('#sum-data-field-name-input').parentElement.style.display = '';
|
||||
document.querySelector('#sum-data-value-input').parentElement.style.display = '';
|
||||
const dynamicArea = document.getElementById('sum-data-dynamic-fields');
|
||||
if (dynamicArea) dynamicArea.innerHTML = '';
|
||||
}
|
||||
|
||||
// 删除单条(分组)
|
||||
async function deleteSumDataGroup(ids) {
|
||||
if (!confirm(`确定要删除这组求和数据吗?(共 ${ids.length} 条字段记录)`)) return;
|
||||
|
||||
try {
|
||||
let success = true;
|
||||
for (const id of ids) {
|
||||
const response = await fetch(`/api/sum-data/${id}`, { method: 'DELETE' });
|
||||
const result = await response.json();
|
||||
if (!result.success) success = false;
|
||||
}
|
||||
|
||||
if (success) {
|
||||
alert('删除成功');
|
||||
loadSumData(sumDataCurrentPage);
|
||||
} else {
|
||||
alert('部分删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('删除失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容旧函数名
|
||||
function deleteSumData(id) {
|
||||
deleteSumDataGroup([id]);
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
async function batchDeleteSumData() {
|
||||
const checkboxes = document.querySelectorAll('.sum-data-checkbox:checked');
|
||||
if (checkboxes.length === 0) {
|
||||
alert('请选择要删除的记录');
|
||||
return;
|
||||
}
|
||||
|
||||
const ids = [];
|
||||
checkboxes.forEach(cb => {
|
||||
const val = cb.value;
|
||||
if (val.indexOf(',') >= 0) {
|
||||
val.split(',').forEach(id => ids.push(parseInt(id)));
|
||||
} else {
|
||||
ids.push(parseInt(val));
|
||||
}
|
||||
});
|
||||
|
||||
if (!confirm(`确定要删除选中的 ${ids.length} 条求和数据吗?`)) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/sum-data/batch-delete', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ids: ids })
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
alert('删除成功');
|
||||
loadSumData(sumDataCurrentPage);
|
||||
} else {
|
||||
alert('删除失败: ' + (result.message || '未知错误'));
|
||||
}
|
||||
} catch (error) {
|
||||
alert('删除失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>订单日报系统</title>
|
||||
<link rel="stylesheet" href="/public/static/css/style.css">
|
||||
<link rel="stylesheet" href="/public/static/css/style.css?v=20260714">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
@@ -14,6 +14,7 @@
|
||||
<button class="tab active" onclick="switchTab('config')">配置管理</button>
|
||||
<button class="tab" onclick="switchTab('generate')">日报生成</button>
|
||||
<button class="tab" onclick="switchTab('history')">历史记录</button>
|
||||
<button class="tab" onclick="switchTab('sum-data')">求和数据</button>
|
||||
</div>
|
||||
|
||||
<!-- 配置管理 -->
|
||||
@@ -116,6 +117,49 @@
|
||||
<div id="history-pagination" style="margin-top: 20px; display: flex; justify-content: center; gap: 5px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 求和数据 -->
|
||||
<div id="sum-data-tab" class="tab-content">
|
||||
<div class="card">
|
||||
<div style="display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; align-items: center;">
|
||||
<input type="date" id="sum-data-report-date" class="form-control" style="width: auto;">
|
||||
<input type="text" id="sum-data-config-name" class="form-control" style="width: 180px;" placeholder="配置名称搜索">
|
||||
<select id="sum-data-source" class="form-control" style="width: auto;" onchange="loadSumData()">
|
||||
<option value="">全部来源</option>
|
||||
<option value="auto">自动采集</option>
|
||||
<option value="manual">手动编辑</option>
|
||||
</select>
|
||||
<button class="btn btn-primary" onclick="loadSumData()">查询</button>
|
||||
<button class="btn" onclick="clearSumDataQuery()">清除</button>
|
||||
<button class="btn btn-success" onclick="showAddSumDataModal()">手动添加</button>
|
||||
<button class="btn btn-warning" onclick="collectSumDataFromHistory()">从历史采集</button>
|
||||
<button class="btn btn-danger" onclick="batchDeleteSumData()" style="margin-left: auto;">批量删除</button>
|
||||
</div>
|
||||
|
||||
<div style="overflow-x: auto;">
|
||||
<table id="sum-data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="min-width: 40px;"><input type="checkbox" id="select-all-sum-data" onchange="toggleSelectAllSumData()"></th>
|
||||
<th style="min-width: 110px;">报表日期</th>
|
||||
<th style="min-width: 100px;">配置名称</th>
|
||||
<th style="min-width: 120px;">拆分值</th>
|
||||
<th id="th-charge-degree" style="min-width: 120px; background: #eff6ff; color: #1d4ed8;">充电电量(kWh) <span style="font-weight: normal; font-size: 11px;">(必选)</span></th>
|
||||
<th id="th-total-orders" style="min-width: 80px;">订单数</th>
|
||||
<th style="min-width: 90px;">数据来源</th>
|
||||
<th style="min-width: 150px;">修改原因/备注</th>
|
||||
<th style="min-width: 140px;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="sum-data-list">
|
||||
<tr><td colspan="12" class="loading">加载中...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="sum-data-pagination" style="margin-top: 20px; display: flex; justify-content: center; gap: 5px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 创建/编辑配置模态框 -->
|
||||
@@ -209,11 +253,83 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加/编辑求和数据模态框 -->
|
||||
<div id="sum-data-modal" class="modal">
|
||||
<div class="modal-content" style="max-width: 600px;">
|
||||
<div class="modal-header">
|
||||
<h2 id="sum-data-modal-title">添加求和数据</h2>
|
||||
<button class="close-btn" onclick="closeSumDataModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="sum-data-id">
|
||||
|
||||
<div class="form-group">
|
||||
<label>报表日期:</label>
|
||||
<input type="date" id="sum-data-report-date-input" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>配置名称:</label>
|
||||
<input type="text" id="sum-data-config-name-input" class="form-control" placeholder="请输入配置名称">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>拆分方式:</label>
|
||||
<select id="sum-data-split-type-input" class="form-control">
|
||||
<option value="company_id">按企业</option>
|
||||
<option value="user_id">按用户</option>
|
||||
<option value="station_id">按场站</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>拆分值:</label>
|
||||
<input type="text" id="sum-data-split-value-input" class="form-control" placeholder="请输入拆分值">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>拆分显示名称:</label>
|
||||
<input type="text" id="sum-data-split-name-input" class="form-control" placeholder="请输入拆分显示名称">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>求和字段键名:</label>
|
||||
<input type="text" id="sum-data-field-key-input" class="form-control" placeholder="例如:charge_degree">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>求和字段名称:</label>
|
||||
<input type="text" id="sum-data-field-name-input" class="form-control" placeholder="例如:充电电量(kWh)">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>求和值:</label>
|
||||
<input type="number" step="0.001" id="sum-data-value-input" class="form-control" placeholder="请输入求和值">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>订单数:</label>
|
||||
<input type="number" id="sum-data-total-orders-input" class="form-control" placeholder="请输入订单数" value="0">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>修改原因 <span style="color: red;">*</span>:</label>
|
||||
<textarea id="sum-data-remark-input" class="form-control" rows="3" placeholder="请输入修改原因(必填)"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn" onclick="closeSumDataModal()">取消</button>
|
||||
<button class="btn btn-primary" onclick="saveSumData()">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- JavaScript 文件 -->
|
||||
<script src="/public/static/js/common.js"></script>
|
||||
<script src="/public/static/js/config.js"></script>
|
||||
<script src="/public/static/js/entity.js"></script>
|
||||
<script src="/public/static/js/report.js"></script>
|
||||
<script src="/public/static/js/history.js"></script>
|
||||
<script src="/public/static/js/common.js?v=20260714"></script>
|
||||
<script src="/public/static/js/config.js?v=20260714"></script>
|
||||
<script src="/public/static/js/entity.js?v=20260714"></script>
|
||||
<script src="/public/static/js/report.js?v=20260714"></script>
|
||||
<script src="/public/static/js/history.js?v=20260714"></script>
|
||||
<script src="/public/static/js/sum_data.js?v=20260714"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user