refactor: 重构项目结构,按功能模块拆分文件
- 后端:将 app.py 拆分为 lib/api/ 目录下的多个蓝图模块 - config_api.py: 配置管理 API - entity_api.py: 实体管理 API - field_api.py: 字段列表 API - report_api.py: 日报生成和历史记录 API - init_api.py: 数据库初始化 API - download_api.py: 文件下载 API - 前端:将 index.html 拆分为多个文件 - static/css/style.css: CSS 样式 - static/js/common.js: 通用工具函数 - static/js/config.js: 配置管理 - static/js/entity.js: 实体管理 - static/js/report.js: 日报生成 - static/js/history.js: 历史记录 - 更新 app.py 使用蓝图注册 API - 创建 AGENTS.md 项目文档 Coze-Commit-Type: user Coze-User-ID: 3722323274763196 Coze-Conversation-ID: 9894087
This commit is contained in:
35
lib/api/__init__.py
Normal file
35
lib/api/__init__.py
Normal file
@@ -0,0 +1,35 @@
|
||||
"""
|
||||
API 蓝图模块
|
||||
"""
|
||||
from flask import Blueprint
|
||||
|
||||
# 配置管理蓝图
|
||||
config_bp = Blueprint('config', __name__, url_prefix='/api')
|
||||
|
||||
# 实体管理蓝图
|
||||
entity_bp = Blueprint('entity', __name__, url_prefix='/api')
|
||||
|
||||
# 字段列表蓝图
|
||||
field_bp = Blueprint('field', __name__, url_prefix='/api')
|
||||
|
||||
# 日报生成和历史记录蓝图
|
||||
report_bp = Blueprint('report', __name__, url_prefix='/api')
|
||||
|
||||
# 数据库初始化蓝图
|
||||
init_bp = Blueprint('init', __name__, url_prefix='/api')
|
||||
|
||||
# 文件下载蓝图
|
||||
download_bp = Blueprint('download', __name__, url_prefix='/api')
|
||||
|
||||
# 导入各模块的路由
|
||||
from . import config_api, entity_api, field_api, report_api, init_api, download_api
|
||||
|
||||
|
||||
def register_blueprints(app):
|
||||
"""注册所有蓝图"""
|
||||
app.register_blueprint(config_bp)
|
||||
app.register_blueprint(entity_bp)
|
||||
app.register_blueprint(field_bp)
|
||||
app.register_blueprint(report_bp)
|
||||
app.register_blueprint(init_bp)
|
||||
app.register_blueprint(download_bp)
|
||||
257
lib/api/config_api.py
Normal file
257
lib/api/config_api.py
Normal file
@@ -0,0 +1,257 @@
|
||||
"""
|
||||
配置管理 API
|
||||
"""
|
||||
import json
|
||||
from datetime import datetime
|
||||
from flask import request, jsonify
|
||||
from lib.db import execute_query, execute_update, execute_insert
|
||||
from . import config_bp
|
||||
|
||||
|
||||
@config_bp.route('/config', methods=['GET'])
|
||||
def get_configs():
|
||||
"""获取所有配置"""
|
||||
try:
|
||||
configs = execute_query(
|
||||
'SELECT * FROM t_daily_report_config ORDER BY sort_order ASC, id DESC'
|
||||
)
|
||||
|
||||
# 解析 JSON 字段
|
||||
for config in configs:
|
||||
config['selected_fields'] = json.loads(config['selected_fields'])
|
||||
config['sum_fields'] = json.loads(config['sum_fields']) if config['sum_fields'] else []
|
||||
config['time_periods'] = json.loads(config['time_periods']) if config.get('time_periods') else {}
|
||||
|
||||
return jsonify({'success': True, 'data': configs})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@config_bp.route('/config', methods=['POST'])
|
||||
def create_config():
|
||||
"""创建配置"""
|
||||
try:
|
||||
data = request.json
|
||||
config_name = data.get('config_name')
|
||||
split_type = data.get('split_type')
|
||||
split_value = data.get('split_value')
|
||||
split_name = data.get('split_name', '')
|
||||
selected_fields = data.get('selected_fields', [])
|
||||
sum_fields = data.get('sum_fields', [])
|
||||
time_periods = data.get('time_periods', {})
|
||||
merge_telecom = 1 if data.get('merge_telecom') else 0
|
||||
telecom_vehicle_no = data.get('telecom_vehicle_no', '')
|
||||
field_custom_names = data.get('field_custom_names', {})
|
||||
|
||||
if not config_name or not split_type or not split_value:
|
||||
return jsonify({'success': False, 'message': '缺少必填字段'}), 400
|
||||
|
||||
if not selected_fields:
|
||||
return jsonify({'success': False, 'message': '请至少选择一个字段'}), 400
|
||||
|
||||
# 生成 ID
|
||||
config_id = int(datetime.now().timestamp() * 1000)
|
||||
|
||||
# 获取最大排序值
|
||||
max_sort = execute_query('SELECT MAX(sort_order) as max_sort FROM t_daily_report_config')
|
||||
sort_order = (max_sort[0]['max_sort'] or 0) + 1
|
||||
|
||||
sql = """
|
||||
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, is_active, sort_order, create_time, update_time)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 1, %s, NOW(), NOW())
|
||||
"""
|
||||
|
||||
params = (
|
||||
config_id,
|
||||
config_name,
|
||||
split_type,
|
||||
split_value,
|
||||
split_name,
|
||||
json.dumps(selected_fields, ensure_ascii=False),
|
||||
json.dumps(sum_fields, ensure_ascii=False),
|
||||
json.dumps(time_periods, ensure_ascii=False),
|
||||
merge_telecom,
|
||||
telecom_vehicle_no,
|
||||
json.dumps(field_custom_names, ensure_ascii=False) if field_custom_names else None,
|
||||
sort_order
|
||||
)
|
||||
|
||||
execute_insert(sql, params)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '配置创建成功',
|
||||
'data': {'id': config_id}
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@config_bp.route('/config/<int:config_id>', methods=['PUT'])
|
||||
def update_config(config_id):
|
||||
"""更新配置"""
|
||||
try:
|
||||
data = request.json
|
||||
config_name = data.get('config_name')
|
||||
split_type = data.get('split_type')
|
||||
split_value = data.get('split_value')
|
||||
split_name = data.get('split_name', '')
|
||||
selected_fields = data.get('selected_fields', [])
|
||||
sum_fields = data.get('sum_fields', [])
|
||||
time_periods = data.get('time_periods', {})
|
||||
merge_telecom = 1 if data.get('merge_telecom') else 0
|
||||
telecom_vehicle_no = data.get('telecom_vehicle_no', '')
|
||||
field_custom_names = data.get('field_custom_names', {})
|
||||
|
||||
sql = """
|
||||
UPDATE t_daily_report_config
|
||||
SET config_name = %s, split_type = %s, split_value = %s, split_name = %s,
|
||||
selected_fields = %s, sum_fields = %s, time_periods = %s,
|
||||
merge_telecom = %s, telecom_vehicle_no = %s, field_custom_names = %s, update_time = NOW()
|
||||
WHERE id = %s
|
||||
"""
|
||||
|
||||
params = (
|
||||
config_name,
|
||||
split_type,
|
||||
split_value,
|
||||
split_name,
|
||||
json.dumps(selected_fields, ensure_ascii=False),
|
||||
json.dumps(sum_fields, ensure_ascii=False),
|
||||
json.dumps(time_periods, ensure_ascii=False),
|
||||
merge_telecom,
|
||||
telecom_vehicle_no,
|
||||
json.dumps(field_custom_names, ensure_ascii=False) if field_custom_names else None,
|
||||
config_id
|
||||
)
|
||||
|
||||
execute_update(sql, params)
|
||||
|
||||
return jsonify({'success': True, 'message': '配置更新成功'})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@config_bp.route('/config/<int:config_id>', methods=['DELETE'])
|
||||
def delete_config(config_id):
|
||||
"""删除配置"""
|
||||
try:
|
||||
execute_update('DELETE FROM t_daily_report_config WHERE id = %s', (config_id,))
|
||||
return jsonify({'success': True, 'message': '配置删除成功'})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@config_bp.route('/config/<int:config_id>/copy', methods=['POST'])
|
||||
def copy_config(config_id):
|
||||
"""复制配置"""
|
||||
try:
|
||||
# 获取原配置
|
||||
configs = execute_query('SELECT * FROM t_daily_report_config WHERE id = %s', (config_id,))
|
||||
if not configs:
|
||||
return jsonify({'success': False, 'message': '原配置不存在'}), 404
|
||||
|
||||
original = configs[0]
|
||||
|
||||
# 生成新 ID 和新名称
|
||||
new_id = int(datetime.now().timestamp() * 1000)
|
||||
new_name = f"{original['config_name']}_副本"
|
||||
|
||||
# 获取最大排序值
|
||||
max_sort = execute_query('SELECT MAX(sort_order) as max_sort FROM t_daily_report_config')
|
||||
sort_order = (max_sort[0]['max_sort'] or 0) + 1
|
||||
|
||||
# 插入新配置
|
||||
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, is_active, sort_order, create_time, update_time)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), NOW())''',
|
||||
(
|
||||
new_id,
|
||||
new_name,
|
||||
original['split_type'],
|
||||
original['split_value'],
|
||||
original['split_name'],
|
||||
original['selected_fields'],
|
||||
original.get('sum_fields', ''),
|
||||
original.get('time_periods', ''),
|
||||
original.get('merge_telecom', 0),
|
||||
original.get('telecom_vehicle_no', ''),
|
||||
original.get('field_custom_names', None),
|
||||
original['is_active'],
|
||||
sort_order
|
||||
)
|
||||
)
|
||||
|
||||
return jsonify({'success': True, 'message': '配置复制成功', 'id': new_id})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@config_bp.route('/config/<int:config_id>/toggle', methods=['POST'])
|
||||
def toggle_config(config_id):
|
||||
"""启用/禁用配置"""
|
||||
try:
|
||||
execute_update(
|
||||
'UPDATE t_daily_report_config SET is_active = NOT is_active, update_time = NOW() WHERE id = %s',
|
||||
(config_id,)
|
||||
)
|
||||
return jsonify({'success': True, 'message': '操作成功'})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@config_bp.route('/config/<int:config_id>/move', methods=['POST'])
|
||||
def move_config(config_id):
|
||||
"""移动配置排序"""
|
||||
try:
|
||||
direction = request.json.get('direction') # up 或 down
|
||||
|
||||
# 获取当前配置
|
||||
current = execute_query(
|
||||
'SELECT sort_order FROM t_daily_report_config WHERE id = %s',
|
||||
(config_id,)
|
||||
)
|
||||
if not current:
|
||||
return jsonify({'success': False, 'message': '配置不存在'}), 404
|
||||
|
||||
current_order = current[0]['sort_order']
|
||||
|
||||
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'])
|
||||
)
|
||||
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:
|
||||
# 交换排序
|
||||
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'])
|
||||
)
|
||||
|
||||
return jsonify({'success': True, 'message': '移动成功'})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
40
lib/api/download_api.py
Normal file
40
lib/api/download_api.py
Normal file
@@ -0,0 +1,40 @@
|
||||
"""
|
||||
文件下载 API
|
||||
"""
|
||||
import os
|
||||
from flask import request, jsonify, send_from_directory
|
||||
from . import download_bp
|
||||
|
||||
|
||||
@download_bp.route('/download', methods=['GET'])
|
||||
def download_file():
|
||||
"""下载文件"""
|
||||
try:
|
||||
file_path = request.args.get('path')
|
||||
print(f"[下载] 请求路径:{file_path}")
|
||||
|
||||
if not file_path:
|
||||
return jsonify({'success': False, 'message': '文件路径不能为空'}), 400
|
||||
|
||||
# 安全检查:确保路径在 public 目录下
|
||||
if not file_path.startswith('/reports/'):
|
||||
return jsonify({'success': False, 'message': '无效的文件路径'}), 400
|
||||
|
||||
filename = file_path.replace('/reports/', '')
|
||||
# 使用绝对路径
|
||||
reports_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'public', 'reports')
|
||||
|
||||
print(f"[下载] 文件目录:{reports_dir}")
|
||||
print(f"[下载] 文件名:{filename}")
|
||||
print(f"[下载] 文件是否存在:{os.path.exists(os.path.join(reports_dir, filename))}")
|
||||
|
||||
# 列出目录中的所有文件
|
||||
if os.path.exists(reports_dir):
|
||||
print(f"[下载] 目录中的文件:{os.listdir(reports_dir)}")
|
||||
|
||||
return send_from_directory(reports_dir, filename, as_attachment=True)
|
||||
except Exception as e:
|
||||
print(f"[下载] 错误:{str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
121
lib/api/entity_api.py
Normal file
121
lib/api/entity_api.py
Normal file
@@ -0,0 +1,121 @@
|
||||
"""
|
||||
实体管理 API(企业、用户、场站)
|
||||
"""
|
||||
from flask import request, jsonify
|
||||
from lib.db import execute_query
|
||||
from . import entity_bp
|
||||
|
||||
|
||||
@entity_bp.route('/entities', methods=['GET'])
|
||||
def get_entities():
|
||||
"""获取企业、用户或场站列表(支持分页和搜索)"""
|
||||
try:
|
||||
entity_type = request.args.get('type', 'company')
|
||||
page = int(request.args.get('page', 1))
|
||||
page_size = int(request.args.get('page_size', 100))
|
||||
search = request.args.get('search', '')
|
||||
|
||||
offset = (page - 1) * page_size
|
||||
|
||||
if entity_type == 'company':
|
||||
# 获取企业列表 (company_state=0 表示启用)
|
||||
if search:
|
||||
companies = execute_query(
|
||||
'SELECT id AS company_id, company_name FROM t_company WHERE company_state = 0 AND company_name LIKE %s ORDER BY company_name LIMIT %s OFFSET %s',
|
||||
(f'%{search}%', page_size, offset)
|
||||
)
|
||||
total_result = execute_query(
|
||||
'SELECT COUNT(*) as total FROM t_company WHERE company_state = 0 AND company_name LIKE %s',
|
||||
(f'%{search}%',)
|
||||
)
|
||||
else:
|
||||
companies = execute_query(
|
||||
'SELECT id AS company_id, company_name FROM t_company WHERE company_state = 0 ORDER BY company_name LIMIT %s OFFSET %s',
|
||||
(page_size, offset)
|
||||
)
|
||||
total_result = execute_query(
|
||||
'SELECT COUNT(*) as total FROM t_company WHERE company_state = 0'
|
||||
)
|
||||
total = total_result[0]['total'] if total_result else 0
|
||||
return jsonify({'success': True, 'data': companies, 'total': total, 'page': page, 'page_size': page_size})
|
||||
|
||||
elif entity_type == 'station':
|
||||
# 获取场站列表
|
||||
if search:
|
||||
stations = execute_query(
|
||||
'SELECT id AS station_id, station_name FROM t_station WHERE station_name LIKE %s ORDER BY station_name LIMIT %s OFFSET %s',
|
||||
(f'%{search}%', page_size, offset)
|
||||
)
|
||||
total_result = execute_query(
|
||||
'SELECT COUNT(*) as total FROM t_station WHERE station_name LIKE %s',
|
||||
(f'%{search}%',)
|
||||
)
|
||||
else:
|
||||
stations = execute_query(
|
||||
'SELECT id AS station_id, station_name FROM t_station ORDER BY station_name LIMIT %s OFFSET %s',
|
||||
(page_size, offset)
|
||||
)
|
||||
total_result = execute_query(
|
||||
'SELECT COUNT(*) as total FROM t_station'
|
||||
)
|
||||
total = total_result[0]['total'] if total_result else 0
|
||||
return jsonify({'success': True, 'data': stations, 'total': total, 'page': page, 'page_size': page_size})
|
||||
|
||||
else:
|
||||
# 获取用户列表(支持分页和搜索,搜索支持用户名和手机号)
|
||||
if search:
|
||||
users = execute_query(
|
||||
'SELECT id AS user_id, user_name, phone FROM t_user WHERE user_name LIKE %s OR phone LIKE %s ORDER BY user_name LIMIT %s OFFSET %s',
|
||||
(f'%{search}%', f'%{search}%', page_size, offset)
|
||||
)
|
||||
total_result = execute_query(
|
||||
'SELECT COUNT(*) as total FROM t_user WHERE user_name LIKE %s OR phone LIKE %s',
|
||||
(f'%{search}%', f'%{search}%')
|
||||
)
|
||||
else:
|
||||
users = execute_query(
|
||||
'SELECT id AS user_id, user_name, phone FROM t_user ORDER BY user_name LIMIT %s OFFSET %s',
|
||||
(page_size, offset)
|
||||
)
|
||||
total_result = execute_query(
|
||||
'SELECT COUNT(*) as total FROM t_user'
|
||||
)
|
||||
total = total_result[0]['total'] if total_result else 0
|
||||
return jsonify({'success': True, 'data': users, 'total': total, 'page': page, 'page_size': page_size})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@entity_bp.route('/entities/by_ids', methods=['POST'])
|
||||
def get_entities_by_ids():
|
||||
"""根据 ID 列表查询实体信息(用于编辑回显)"""
|
||||
try:
|
||||
data = request.json
|
||||
entity_type = data.get('type', 'company')
|
||||
ids = data.get('ids', [])
|
||||
|
||||
if not ids:
|
||||
return jsonify({'success': True, 'data': []})
|
||||
|
||||
placeholders = ', '.join(['%s'] * len(ids))
|
||||
|
||||
if entity_type == 'company':
|
||||
entities = execute_query(
|
||||
f'SELECT id AS company_id, company_name FROM t_company WHERE id IN ({placeholders})',
|
||||
tuple(ids)
|
||||
)
|
||||
elif entity_type == 'station':
|
||||
entities = execute_query(
|
||||
f'SELECT id AS station_id, station_name FROM t_station WHERE id IN ({placeholders})',
|
||||
tuple(ids)
|
||||
)
|
||||
else:
|
||||
entities = execute_query(
|
||||
f'SELECT id AS user_id, user_name FROM t_user WHERE id IN ({placeholders})',
|
||||
tuple(ids)
|
||||
)
|
||||
|
||||
return jsonify({'success': True, 'data': entities})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
16
lib/api/field_api.py
Normal file
16
lib/api/field_api.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
字段列表 API
|
||||
"""
|
||||
from flask import jsonify
|
||||
from lib.field_mapping import get_all_fields_with_display
|
||||
from . import field_bp
|
||||
|
||||
|
||||
@field_bp.route('/fields', methods=['GET'])
|
||||
def get_fields():
|
||||
"""获取所有可用字段"""
|
||||
try:
|
||||
fields = get_all_fields_with_display()
|
||||
return jsonify({'success': True, 'data': fields})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
70
lib/api/init_api.py
Normal file
70
lib/api/init_api.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
数据库初始化 API
|
||||
"""
|
||||
from flask import jsonify
|
||||
from lib.db import execute_update
|
||||
from . import init_bp
|
||||
|
||||
|
||||
@init_bp.route('/init-tables', methods=['POST'])
|
||||
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}")
|
||||
|
||||
return jsonify({'success': True, 'message': '数据库表初始化成功'})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
199
lib/api/report_api.py
Normal file
199
lib/api/report_api.py
Normal file
@@ -0,0 +1,199 @@
|
||||
"""
|
||||
日报生成和历史记录 API
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
from datetime import datetime
|
||||
from flask import request, jsonify, send_from_directory
|
||||
from lib.db import execute_query, execute_update
|
||||
from lib.report_generator import generate_daily_report
|
||||
from . import report_bp
|
||||
|
||||
|
||||
@report_bp.route('/report/generate', methods=['POST'])
|
||||
def generate_report():
|
||||
"""生成日报"""
|
||||
try:
|
||||
data = request.json
|
||||
config_id = data.get('config_id')
|
||||
|
||||
if not config_id:
|
||||
return jsonify({'success': False, 'message': '请选择配置'}), 400
|
||||
|
||||
# 解析时间参数
|
||||
start_time = None
|
||||
end_time = None
|
||||
|
||||
if data.get('start_time'):
|
||||
start_time = datetime.strptime(data['start_time'], '%Y-%m-%d %H:%M:%S')
|
||||
if data.get('end_time'):
|
||||
end_time = datetime.strptime(data['end_time'], '%Y-%m-%d %H:%M:%S')
|
||||
|
||||
result = generate_daily_report(config_id, start_time, end_time)
|
||||
|
||||
if result['success']:
|
||||
return jsonify(result)
|
||||
else:
|
||||
return jsonify(result), 400
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@report_bp.route('/report/history', methods=['GET'])
|
||||
def get_report_history():
|
||||
"""获取日报生成历史"""
|
||||
try:
|
||||
page = int(request.args.get('page', 1))
|
||||
page_size = int(request.args.get('page_size', 20))
|
||||
start_date = request.args.get('start_date')
|
||||
end_date = request.args.get('end_date')
|
||||
offset = (page - 1) * page_size
|
||||
|
||||
# 构建查询条件
|
||||
where_clauses = []
|
||||
params = []
|
||||
|
||||
if start_date:
|
||||
where_clauses.append('DATE(start_time) >= %s')
|
||||
params.append(start_date)
|
||||
|
||||
if end_date:
|
||||
where_clauses.append('DATE(end_time) <= %s')
|
||||
params.append(end_date)
|
||||
|
||||
where_sql = ' AND '.join(where_clauses) if where_clauses else '1=1'
|
||||
|
||||
# 查询总数
|
||||
total_result = execute_query(
|
||||
f'SELECT COUNT(*) as total FROM t_daily_report_history WHERE {where_sql}',
|
||||
tuple(params)
|
||||
)
|
||||
total = total_result[0]['total']
|
||||
|
||||
# 查询数据
|
||||
history = execute_query(
|
||||
f'SELECT * FROM t_daily_report_history WHERE {where_sql} ORDER BY create_time DESC LIMIT %s OFFSET %s',
|
||||
tuple(params) + (page_size, offset)
|
||||
)
|
||||
|
||||
# 解析 JSON 字段并确保 file_path 正确
|
||||
for item in history:
|
||||
item['sum_results'] = json.loads(item['sum_results']) if item['sum_results'] else {}
|
||||
# 确保 file_path 是字符串
|
||||
if item['file_path'] is None:
|
||||
item['file_path'] = ''
|
||||
|
||||
# 调试日志
|
||||
print(f"[历史记录] ID: {item['id']}, status: {item['status']}, file_path: '{item['file_path']}'")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {
|
||||
'list': history,
|
||||
'total': total,
|
||||
'page': page,
|
||||
'page_size': page_size
|
||||
}
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"[历史记录 API] 错误:{e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@report_bp.route('/report/history/<int:history_id>', methods=['DELETE'])
|
||||
def delete_report_history(history_id):
|
||||
"""删除单个历史记录"""
|
||||
try:
|
||||
# 先查询文件路径
|
||||
result = execute_query(
|
||||
'SELECT file_path FROM t_daily_report_history WHERE id = %s',
|
||||
(history_id,)
|
||||
)
|
||||
|
||||
if result:
|
||||
file_path = result[0].get('file_path', '')
|
||||
# 删除文件
|
||||
if file_path and os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'public', file_path.lstrip('/'))):
|
||||
os.remove(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'public', file_path.lstrip('/')))
|
||||
|
||||
# 删除数据库记录
|
||||
execute_update('DELETE FROM t_daily_report_history WHERE id = %s', (history_id,))
|
||||
|
||||
return jsonify({'success': True, 'message': '删除成功'})
|
||||
except Exception as e:
|
||||
print(f"[删除历史记录] 错误:{e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@report_bp.route('/report/history/batch-delete', methods=['POST'])
|
||||
def batch_delete_report_history():
|
||||
"""批量删除历史记录"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
ids = data.get('ids', [])
|
||||
|
||||
if not ids:
|
||||
return jsonify({'success': False, 'message': '请选择要删除的记录'}), 400
|
||||
|
||||
# 查询文件路径
|
||||
results = execute_query(
|
||||
f'SELECT file_path FROM t_daily_report_history WHERE id IN ({",".join(["%s"] * len(ids))})',
|
||||
tuple(ids)
|
||||
)
|
||||
|
||||
# 删除文件
|
||||
for result in results:
|
||||
file_path = result.get('file_path', '')
|
||||
if file_path and os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'public', file_path.lstrip('/'))):
|
||||
os.remove(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'public', file_path.lstrip('/')))
|
||||
|
||||
# 删除数据库记录
|
||||
execute_update(
|
||||
f'DELETE FROM t_daily_report_history WHERE id IN ({",".join(["%s"] * len(ids))})',
|
||||
tuple(ids)
|
||||
)
|
||||
|
||||
return jsonify({'success': True, 'message': f'成功删除 {len(ids)} 条记录'})
|
||||
except Exception as e:
|
||||
print(f"[批量删除历史记录] 错误:{e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@report_bp.route('/report/history/<int:history_id>/download', methods=['GET'])
|
||||
def download_report_by_id(history_id):
|
||||
"""根据 ID 下载历史记录"""
|
||||
try:
|
||||
# 查询历史记录
|
||||
result = execute_query(
|
||||
'SELECT file_path, split_name, report_date FROM t_daily_report_history WHERE id = %s',
|
||||
(history_id,)
|
||||
)
|
||||
|
||||
if not result:
|
||||
return jsonify({'success': False, 'message': '记录不存在'}), 404
|
||||
|
||||
file_path = result[0]['file_path']
|
||||
if not file_path:
|
||||
return jsonify({'success': False, 'message': '文件不存在'}), 404
|
||||
|
||||
# 构建完整路径
|
||||
full_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'public', file_path.lstrip('/'))
|
||||
|
||||
if not os.path.exists(full_path):
|
||||
return jsonify({'success': False, 'message': '文件不存在'}), 404
|
||||
|
||||
# 返回文件
|
||||
directory = os.path.dirname(full_path)
|
||||
filename = os.path.basename(full_path)
|
||||
return send_from_directory(directory, filename, as_attachment=True)
|
||||
except Exception as e:
|
||||
print(f"[下载历史记录] 错误:{e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
Reference in New Issue
Block a user