Commit 35b42130 by yiling.shen

添加批量导出功能:修复Docker容器中的数据库连接问题,完善Excel导出逻辑

parent 1837325e
......@@ -646,6 +646,48 @@ def admin_dashboard():
color: #666;
margin-top: 5px;
}
.export-btn {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(40, 167, 69, 0.3);
}
.export-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(40, 167, 69, 0.4);
}
.export-btn:disabled {
background: #6c757d;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.export-status {
padding: 10px;
border-radius: 5px;
margin-top: 10px;
}
.export-status.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.export-status.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.export-status.info {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
</style>
</head>
<body>
......@@ -674,6 +716,13 @@ def admin_dashboard():
<div class="stat-label">数据完整性</div>
</div>
</div>
<div style="margin-top: 20px; text-align: center;">
<button id="exportBtn" class="export-btn" onclick="exportData()">
📊 导出所有诊所数据到Excel
</button>
<div id="exportStatus" style="margin-top: 10px; display: none;"></div>
</div>
</div>
<h2>门诊列表</h2>
......@@ -744,6 +793,62 @@ def admin_dashboard():
</div>
</div>
</div>
<script>
function exportData() {
const exportBtn = document.getElementById('exportBtn');
const exportStatus = document.getElementById('exportStatus');
// 禁用按钮,显示加载状态
exportBtn.disabled = true;
exportBtn.textContent = '🔄 正在导出...';
exportStatus.style.display = 'block';
exportStatus.className = 'export-status info';
exportStatus.textContent = '正在生成Excel文件,请稍候...';
// 调用导出API
fetch('/api/export-data')
.then(response => response.json())
.then(data => {
if (data.success) {
exportStatus.className = 'export-status success';
exportStatus.textContent = `✅ ${data.message} - 文件名: ${data.filename}`;
// 自动下载文件
const downloadLink = document.createElement('a');
downloadLink.href = data.download_url;
downloadLink.download = data.filename;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
// 恢复按钮状态
exportBtn.disabled = false;
exportBtn.textContent = '📊 导出所有诊所数据到Excel';
// 3秒后隐藏状态信息
setTimeout(() => {
exportStatus.style.display = 'none';
}, 3000);
} else {
throw new Error(data.message);
}
})
.catch(error => {
exportStatus.className = 'export-status error';
exportStatus.textContent = `❌ 导出失败: ${error.message}`;
// 恢复按钮状态
exportBtn.disabled = false;
exportBtn.textContent = '📊 导出所有诊所数据到Excel';
// 5秒后隐藏错误信息
setTimeout(() => {
exportStatus.style.display = 'none';
}, 5000);
});
}
</script>
</body>
</html>
'''
......@@ -998,6 +1103,115 @@ def api_logout():
return jsonify({'success': True, 'message': '登出成功'})
@app.route('/api/export-data', methods=['GET'])
def export_data():
"""导出所有诊所的回访记录数据到Excel"""
try:
# 检查用户权限
session_id = session.get('session_id')
if not session_id:
return jsonify({'success': False, 'message': '未登录'}), 401
session_data = auth_system.validate_session(session_id)
if not session_data:
session.pop('session_id', None)
return jsonify({'success': False, 'message': 'Session已过期'}), 401
# 只有管理员可以导出所有数据
if session_data['role'] != 'admin':
return jsonify({'success': False, 'message': '权限不足,只有管理员可以导出数据'}), 403
# 导入导出模块
try:
from export_data import DataExporter
except ImportError:
return jsonify({'success': False, 'message': '导出模块未找到'}), 500
# 生成Excel文件
exporter = DataExporter()
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_filename = f"回访记录导出_{timestamp}.xlsx"
# 在Docker容器中,使用/app目录
if os.path.exists('/app'):
output_path = os.path.join('/app', output_filename)
else:
output_path = os.path.join(os.getcwd(), output_filename)
print(f"导出路径: {output_path}")
print(f"当前工作目录: {os.getcwd()}")
# 导出数据
try:
exporter.export_to_excel(output_path)
print(f"导出完成,文件路径: {output_path}")
except Exception as export_error:
print(f"导出过程中出错: {export_error}")
import traceback
traceback.print_exc()
return jsonify({'success': False, 'message': f'导出过程出错: {str(export_error)}'}), 500
# 检查文件是否生成成功
if not os.path.exists(output_path):
print(f"文件不存在: {output_path}")
# 列出目录内容
try:
dir_content = os.listdir(os.path.dirname(output_path))
print(f"目录内容: {dir_content}")
except Exception as list_error:
print(f"无法列出目录内容: {list_error}")
return jsonify({'success': False, 'message': '文件生成失败'}), 500
# 检查文件大小
file_size = os.path.getsize(output_path)
print(f"生成的文件大小: {file_size} 字节")
# 返回文件下载链接
return jsonify({
'success': True,
'message': '数据导出成功',
'filename': output_filename,
'download_url': f'/download/{output_filename}'
})
except Exception as e:
print(f"导出数据失败: {e}")
return jsonify({'success': False, 'message': f'导出失败: {str(e)}'}), 500
@app.route('/download/<filename>')
def download_file(filename):
"""下载导出的文件"""
try:
# 检查用户权限
session_id = session.get('session_id')
if not session_id:
return jsonify({'success': False, 'message': '未登录'}), 401
session_data = auth_system.validate_session(session_id)
if not session_data:
session.pop('session_id', None)
return jsonify({'success': False, 'message': 'Session已过期'}), 401
# 只有管理员可以下载文件
if session_data['role'] != 'admin':
return jsonify({'success': False, 'message': '权限不足'}), 403
# 检查文件路径安全性
if '..' in filename or filename.startswith('/'):
return "非法文件路径", 400
# 构建文件路径
file_path = os.path.join(os.getcwd(), filename)
if not os.path.exists(file_path):
return "文件不存在", 404
# 发送文件
return send_file(file_path, as_attachment=True, download_name=filename)
except Exception as e:
return f"文件下载错误: {str(e)}", 500
@app.route('/patient_profiles/<path:filename>')
def serve_patient_profiles(filename):
"""提供患者画像文件服务"""
......
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
检查数据库中的回访记录详情
"""
import pymysql
from datetime import datetime
def check_callback_records():
"""检查回访记录详情"""
try:
# 连接数据库
connection = pymysql.connect(
host='localhost',
port=3307,
user='callback_user',
password='dev_password_123',
database='callback_system',
charset='utf8mb4',
use_unicode=True,
init_command='SET NAMES utf8mb4'
)
cursor = connection.cursor()
# 查看表结构
print("=== 回访记录表结构 ===")
cursor.execute("DESCRIBE callback_records")
columns = cursor.fetchall()
for col in columns:
print(f"{col[0]}: {col[1]} {col[2]} {col[3]}")
print("\n=== 最新5条回访记录 ===")
cursor.execute("""
SELECT record_id, case_number, callback_methods, callback_result,
callback_record, operator, create_time
FROM callback_records
ORDER BY create_time DESC
LIMIT 5
""")
records = cursor.fetchall()
for record in records:
print(f"\n记录ID: {record[0]}")
print(f"病例号: {record[1]}")
print(f"回访方式: {record[2]}")
print(f"回访结果: {record[3]}")
print(f"回访记录: {record[4]}")
print(f"操作员: {record[5]}")
print(f"创建时间: {record[6]}")
print("-" * 50)
# 检查是否有失败原因等字段
print("\n=== 检查失败原因字段 ===")
cursor.execute("SHOW COLUMNS FROM callback_records LIKE '%failure%'")
failure_cols = cursor.fetchall()
if failure_cols:
print("失败原因相关字段:")
for col in failure_cols:
print(f" {col[0]}: {col[1]}")
else:
print("没有找到失败原因相关字段")
cursor.close()
connection.close()
except Exception as e:
print(f"数据库连接失败: {e}")
if __name__ == "__main__":
check_callback_records()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
数据导出模块 - 导出所有诊所的回访记录到Excel
"""
import os
import json
from datetime import datetime
from typing import Dict, List, Any
import pymysql
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
class DataExporter:
"""数据导出器"""
def __init__(self):
# 检查是否在Docker容器中运行
if os.path.exists('/app'):
# Docker容器内,连接MySQL容器
self.db_config = {
'host': 'mysql',
'port': 3306,
'user': 'callback_user',
'password': 'dev_password_123',
'database': 'callback_system',
'charset': 'utf8mb4',
'use_unicode': True,
'init_command': 'SET NAMES utf8mb4'
}
else:
# 本地运行,连接本地MySQL
self.db_config = {
'host': 'localhost',
'port': 3307,
'user': 'callback_user',
'password': 'dev_password_123',
'database': 'callback_system',
'charset': 'utf8mb4',
'use_unicode': True,
'init_command': 'SET NAMES utf8mb4'
}
def get_clinic_data(self) -> Dict[str, Any]:
"""获取所有诊所的数据"""
try:
connection = pymysql.connect(**self.db_config)
cursor = connection.cursor()
# 获取所有回访记录
cursor.execute("""
SELECT
cr.case_number,
cr.callback_methods,
cr.callback_result,
cr.callback_record,
cr.operator,
cr.create_time,
cr.update_time
FROM callback_records cr
ORDER BY cr.create_time DESC
""")
records = cursor.fetchall()
# 获取诊所配置
clinic_config = self._get_clinic_config()
# 按诊所分组数据
clinic_data = {}
print(f"处理 {len(records)} 条记录...")
print(f"诊所配置: {clinic_config}")
for record in records:
case_number = record[0]
clinic_id = self._get_clinic_id_from_case(case_number, clinic_config)
print(f"病例号 {case_number} -> 诊所ID {clinic_id}")
if clinic_id not in clinic_data:
# 获取诊所名称
clinic_name = "未知诊所"
if clinic_id in clinic_config:
clinic_name = clinic_config[clinic_id].get('clinic_name', '未知诊所')
else:
# 如果配置中没有,使用硬编码的映射
clinic_name_map = {
'clinic_xuexian': '学前街门诊',
'clinic_dafeng': '大丰门诊',
'clinic_dongting': '东亭门诊',
'clinic_helai': '河埒门诊',
'clinic_hongdou': '红豆门诊',
'clinic_huishan': '惠山门诊',
'clinic_mashan': '马山门诊',
'clinic_hospital': '通善口腔医院',
'clinic_xinwu': '新吴门诊'
}
clinic_name = clinic_name_map.get(clinic_id, '未知诊所')
clinic_data[clinic_id] = {
'clinic_name': clinic_name,
'records': []
}
clinic_data[clinic_id]['records'].append({
'case_number': case_number,
'callback_methods': json.loads(record[1]) if record[1] else [],
'callback_result': record[2],
'callback_record': record[3],
'operator': record[4],
'create_time': record[5],
'update_time': record[6]
})
print(f"分组后的诊所数据: {len(clinic_data)} 个诊所")
for clinic_id, data in clinic_data.items():
print(f" {clinic_id}: {data['clinic_name']} - {len(data['records'])} 条记录")
cursor.close()
connection.close()
return clinic_data
except Exception as e:
print(f"获取数据失败: {e}")
return {}
def _get_clinic_config(self) -> Dict[str, Any]:
"""获取诊所配置"""
try:
# 导入诊所配置
import clinic_config
users = clinic_config.DEFAULT_USERS
# 创建诊所映射
clinic_mapping = {}
for user in users:
if user.get('clinic_id') and user.get('clinic_id') != 'admin':
clinic_mapping[user['clinic_id']] = {
'clinic_name': user.get('clinic_name', '未知诊所'),
'username': user.get('username', '')
}
return clinic_mapping
except ImportError:
# 如果无法导入,使用硬编码的诊所映射
return {
'clinic_xuexian': {'clinic_name': '学前街门诊', 'username': 'jinqin'},
'clinic_dafeng': {'clinic_name': '大丰门诊', 'username': 'chenlin'},
'clinic_dongting': {'clinic_name': '东亭门诊', 'username': 'dongting'},
'clinic_helai': {'clinic_name': '河埒门诊', 'username': 'helai'},
'clinic_hongdou': {'clinic_name': '红豆门诊', 'username': 'hongdou'},
'clinic_huishan': {'clinic_name': '惠山门诊', 'username': 'huishan'},
'clinic_mashan': {'clinic_name': '马山门诊', 'username': 'mashan'},
'clinic_hospital': {'clinic_name': '通善口腔医院', 'username': 'hospital'},
'clinic_xinwu': {'clinic_name': '新吴门诊', 'username': 'xinwu'}
}
def _get_clinic_id_from_case(self, case_number: str, clinic_config: Dict) -> str:
"""根据病例号判断诊所ID"""
# 根据病例号前缀判断诊所
if case_number.startswith('TS0G'):
return 'clinic_dafeng'
elif case_number.startswith('TS0C'):
return 'clinic_dongting'
elif case_number.startswith('JY0A'):
return 'clinic_helai'
elif case_number.startswith('TS0B'):
return 'clinic_hongdou'
elif case_number.startswith('TS0E'):
return 'clinic_huishan'
elif case_number.startswith('TS0I'):
return 'clinic_mashan'
elif case_number.startswith('TS0F'):
return 'clinic_xinwu'
elif case_number.startswith('TS0M'):
return 'clinic_xuexian'
else:
return 'clinic_hospital'
def export_to_excel(self, output_path: str = None) -> str:
"""导出数据到Excel文件"""
if not output_path:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_path = f"回访记录导出_{timestamp}.xlsx"
# 获取数据
clinic_data = self.get_clinic_data()
# 创建工作簿
wb = Workbook()
# 创建总览表
self._create_summary_sheet(wb, clinic_data)
# 创建各诊所详细表
print(f"开始创建诊所详细表...")
for clinic_id, data in clinic_data.items():
if data['records']:
print(f"创建诊所表: {clinic_id} - {data['clinic_name']} ({len(data['records'])} 条记录)")
self._create_clinic_sheet(wb, clinic_id, data)
else:
print(f"跳过空诊所: {clinic_id} - {data['clinic_name']}")
print(f"Excel工作表列表: {wb.sheetnames}")
# 删除默认的Sheet
if 'Sheet' in wb.sheetnames:
wb.remove(wb['Sheet'])
# 保存文件
wb.save(output_path)
return output_path
def _create_summary_sheet(self, wb: Workbook, clinic_data: Dict[str, Any]):
"""创建总览表"""
ws = wb.create_sheet("总览")
# 设置标题
title = "患者回访记录系统 - 数据总览"
ws['A1'] = title
ws.merge_cells('A1:F1')
# 设置标题样式
title_font = Font(size=16, bold=True)
title_fill = PatternFill(start_color="366092", end_color="366092", fill_type="solid")
title_alignment = Alignment(horizontal="center", vertical="center")
ws['A1'].font = title_font
ws['A1'].fill = title_fill
ws['A1'].alignment = title_alignment
# 设置列标题
headers = ["诊所名称", "总记录数", "成功", "不成功", "放弃回访", "成功率"]
for col, header in enumerate(headers, 1):
cell = ws.cell(row=3, column=col, value=header)
cell.font = Font(bold=True)
cell.fill = PatternFill(start_color="D9E1F2", end_color="D9E1F2", fill_type="solid")
cell.alignment = Alignment(horizontal="center")
# 填充数据
row = 4
total_records = 0
total_success = 0
for clinic_id, data in clinic_data.items():
records = data['records']
if not records:
continue
success_count = sum(1 for r in records if r['callback_result'] == '成功')
unsuccessful_count = sum(1 for r in records if r['callback_result'] == '不成功')
abandon_count = sum(1 for r in records if r['callback_result'] == '放弃回访')
total_records += len(records)
total_success += success_count
success_rate = (success_count / len(records)) * 100 if records else 0
ws.cell(row=row, column=1, value=data['clinic_name'])
ws.cell(row=row, column=2, value=len(records))
ws.cell(row=row, column=3, value=success_count)
ws.cell(row=row, column=4, value=unsuccessful_count)
ws.cell(row=row, column=5, value=abandon_count)
ws.cell(row=row, column=6, value=f"{success_rate:.1f}%")
row += 1
# 添加总计行
if total_records > 0:
total_success_rate = (total_success / total_records) * 100
ws.cell(row=row, column=1, value="总计")
ws.cell(row=row, column=2, value=total_records)
ws.cell(row=row, column=3, value=total_success)
ws.cell(row=row, column=6, value=f"{total_success_rate:.1f}%")
# 设置总计行样式
for col in range(1, 7):
cell = ws.cell(row=row, column=col)
cell.font = Font(bold=True)
cell.fill = PatternFill(start_color="FFE699", end_color="FFE699", fill_type="solid")
# 调整列宽
for col in range(1, 7):
ws.column_dimensions[get_column_letter(col)].width = 15
# 设置边框
self._add_borders(ws, 3, row, 6)
def _create_clinic_sheet(self, wb: Workbook, clinic_id: str, data: Dict[str, Any]):
"""创建诊所详细表"""
clinic_name = data['clinic_name']
sheet_name = clinic_name[:31] if len(clinic_name) > 31 else clinic_name # Excel工作表名限制31字符
ws = wb.create_sheet(sheet_name)
# 设置标题
title = f"{clinic_name} - 回访记录详情"
ws['A1'] = title
ws.merge_cells('A1:H1')
# 设置标题样式
title_font = Font(size=14, bold=True)
title_fill = PatternFill(start_color="70AD47", end_color="70AD47", fill_type="solid")
title_alignment = Alignment(horizontal="center", vertical="center")
ws['A1'].font = title_font
ws['A1'].fill = title_fill
ws['A1'].alignment = title_alignment
# 设置列标题
headers = ["序号", "病例号", "回访方式", "回访结果", "操作员", "创建时间", "更新时间", "详细记录"]
for col, header in enumerate(headers, 1):
cell = ws.cell(row=3, column=col, value=header)
cell.font = Font(bold=True)
cell.fill = PatternFill(start_color="C6EFCE", end_color="C6EFCE", fill_type="solid")
cell.alignment = Alignment(horizontal="center")
# 填充数据
for row_idx, record in enumerate(data['records'], 4):
ws.cell(row=row_idx, column=1, value=row_idx - 3)
ws.cell(row=row_idx, column=2, value=record['case_number'])
ws.cell(row=row_idx, column=3, value=', '.join(record['callback_methods']))
ws.cell(row=row_idx, column=4, value=record['callback_result'])
ws.cell(row=row_idx, column=5, value=record['operator'])
ws.cell(row=row_idx, column=6, value=record['create_time'].strftime("%Y-%m-%d %H:%M:%S") if record['create_time'] else "")
ws.cell(row=row_idx, column=7, value=record['update_time'].strftime("%Y-%m-%d %H:%M:%S") if record['update_time'] else "")
ws.cell(row=row_idx, column=8, value=record['callback_record'])
# 根据回访结果设置颜色
if record['callback_result'] == '成功':
fill_color = "C6EFCE" # 绿色
elif record['callback_result'] == '不成功':
fill_color = "FFC7CE" # 红色
elif record['callback_result'] == '放弃回访':
fill_color = "FFEB9C" # 黄色
else:
fill_color = "FFFFFF" # 白色
for col in range(1, 9):
ws.cell(row=row_idx, column=col).fill = PatternFill(start_color=fill_color, end_color=fill_color, fill_type="solid")
# 调整列宽
column_widths = [8, 15, 20, 12, 15, 20, 20, 50]
for col, width in enumerate(column_widths, 1):
ws.column_dimensions[get_column_letter(col)].width = width
# 设置边框
self._add_borders(ws, 3, len(data['records']) + 3, 8)
def _add_borders(self, ws, start_row: int, end_row: int, end_col: int):
"""添加边框"""
thin_border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
for row in range(start_row, end_row + 1):
for col in range(1, end_col + 1):
ws.cell(row=row, column=col).border = thin_border
def main():
"""主函数"""
exporter = DataExporter()
output_file = exporter.export_to_excel()
print(f"数据导出完成!文件保存为: {output_file}")
if __name__ == "__main__":
main()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
测试导出功能
"""
import requests
import json
def test_export_api():
"""测试导出API"""
base_url = "http://localhost:4002"
# 1. 测试登录
print("=== 测试登录 ===")
login_data = {
"username": "admin",
"password": "admin123"
}
login_response = requests.post(f"{base_url}/api/login", json=login_data)
print(f"登录状态: {login_response.status_code}")
if login_response.status_code != 200:
print("登录失败")
return
login_result = login_response.json()
print(f"登录结果: {login_result}")
# 2. 测试导出API
print("\n=== 测试导出API ===")
# 获取session_id
login_result = login_response.json()
session_id = login_result.get('session_id')
if not session_id:
print("未获取到session_id")
return
print(f"获取到session_id: {session_id}")
# 设置session cookie - 尝试不同的cookie名称
cookies = {
'session': session_id,
'session_id': session_id
}
# 也尝试在headers中传递
headers = {
'Cookie': f'session={session_id}'
}
# 调用导出API - 尝试不同的方式
print("尝试方式1: 使用cookies参数")
export_response = requests.get(f"{base_url}/api/export-data", cookies=cookies)
print(f"方式1结果: {export_response.status_code}")
if export_response.status_code != 200:
print("尝试方式2: 使用headers中的Cookie")
export_response = requests.get(f"{base_url}/api/export-data", headers=headers)
print(f"方式2结果: {export_response.status_code}")
if export_response.status_code != 200:
print("尝试方式3: 直接设置Cookie header")
export_response = requests.get(f"{base_url}/api/export-data", headers={'Cookie': f'session={session_id}'})
print(f"方式3结果: {export_response.status_code}")
print(f"导出API状态: {export_response.status_code}")
if export_response.status_code == 200:
export_result = export_response.json()
print(f"导出结果: {json.dumps(export_result, indent=2, ensure_ascii=False)}")
if export_result.get('success'):
filename = export_result.get('filename')
download_url = export_result.get('download_url')
print(f"文件名: {filename}")
print(f"下载链接: {download_url}")
# 测试下载
print("\n=== 测试文件下载 ===")
download_response = requests.get(f"{base_url}{download_url}", cookies=cookies)
print(f"下载状态: {download_response.status_code}")
if download_response.status_code == 200:
# 保存文件
with open(filename, 'wb') as f:
f.write(download_response.content)
print(f"文件已保存: {filename}")
# 检查文件大小
import os
file_size = os.path.getsize(filename)
print(f"文件大小: {file_size} 字节")
if file_size > 1000: # 大于1KB说明有数据
print("✅ 导出成功!文件包含数据")
else:
print("❌ 文件太小,可能没有数据")
else:
print(f"下载失败: {download_response.text}")
else:
print(f"导出失败: {export_result.get('message')}")
else:
print(f"导出API调用失败: {export_response.text}")
if __name__ == "__main__":
test_export_api()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
直接测试导出模块
"""
from export_data import DataExporter
import os
def test_export_direct():
"""直接测试导出模块"""
print("=== 直接测试导出模块 ===")
try:
# 创建导出器
exporter = DataExporter()
print("✅ 导出器创建成功")
# 获取数据
print("\n=== 获取数据 ===")
clinic_data = exporter.get_clinic_data()
print(f"获取到诊所数据: {len(clinic_data)} 个诊所")
for clinic_id, data in clinic_data.items():
print(f" {clinic_id}: {data['clinic_name']} - {len(data['records'])} 条记录")
# 导出到Excel
print("\n=== 导出到Excel ===")
output_file = exporter.export_to_excel()
print(f"导出文件: {output_file}")
# 检查文件
if os.path.exists(output_file):
file_size = os.path.getsize(output_file)
print(f"文件大小: {file_size} 字节")
if file_size > 1000:
print("✅ 导出成功!文件包含数据")
else:
print("❌ 文件太小,可能没有数据")
else:
print("❌ 文件未生成")
except Exception as e:
print(f"❌ 测试失败: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
test_export_direct()
\ No newline at end of file
# 📊 批量导出功能使用说明
# 📊 批量导出功能使用说明
## 🎯 功能概述
新增的批量导出功能允许管理员将所有诊所的回访记录数据导出为Excel文件,方便数据分析和报表制作。
## 🔐 权限要求
- **只有管理员用户** 可以使用此功能
- 普通门诊用户无法访问导出功能
- 需要先登录系统
## 🚀 使用方法
### 1. 登录系统
- 使用管理员账号登录系统
- 访问地址:`http://localhost:4002`
- 用户名:`admin`,密码:`admin123`
### 2. 进入管理员仪表板
- 登录成功后会自动跳转到管理员仪表板
- 或者访问:`http://localhost:4002/admin/dashboard`
### 3. 导出数据
- 在管理员仪表板的"系统概览"部分
- 找到绿色的"📊 导出所有诊所数据到Excel"按钮
- 点击按钮开始导出
### 4. 下载文件
- 导出成功后会自动下载Excel文件
- 文件名格式:`回访记录导出_YYYYMMDD_HHMMSS.xlsx`
- 文件会保存到浏览器的默认下载目录
## 📋 Excel文件内容
### 总览表 (总览)
包含所有诊所的统计信息:
- **诊所名称**: 各门诊的名称
- **总记录数**: 该门诊的回访记录总数
- **成功**: 回访成功的记录数
- **不成功**: 回访不成功的记录数
- **放弃回访**: 放弃回访的记录数
- **成功率**: 成功记录占总记录的比例
### 诊所详细表 (各诊所名称)
每个诊所一个工作表,包含详细的回访记录:
- **序号**: 记录编号
- **病例号**: 患者的病例编号
- **回访方式**: 使用的回访方法(打电话、发短信等)
- **回访结果**: 成功/不成功/放弃回访
- **操作员**: 执行回访的操作员
- **创建时间**: 回访记录创建时间
- **更新时间**: 记录最后更新时间
- **详细记录**: 完整的回访记录内容
## 🎨 文件特色
### 颜色编码
- **绿色**: 回访成功的记录
- **红色**: 回访不成功的记录
- **黄色**: 放弃回访的记录
### 数据统计
- 自动计算各诊所的成功率
- 提供总体统计数据
- 支持数据筛选和分析
## ⚠️ 注意事项
1. **文件大小**: 根据数据量,Excel文件可能较大
2. **导出时间**: 数据量大时,导出可能需要几分钟
3. **浏览器兼容**: 建议使用Chrome、Firefox等现代浏览器
4. **下载设置**: 确保浏览器允许自动下载文件
## 🔧 技术实现
### 后端API
- **导出接口**: `/api/export-data` (GET)
- **下载接口**: `/download/<filename>` (GET)
- **权限控制**: 仅管理员可访问
### 数据来源
- 从MySQL数据库读取所有回访记录
- 按诊所分组整理数据
- 使用openpyxl生成Excel文件
### 文件格式
- 文件格式:`.xlsx` (Excel 2007+)
- 字符编码:UTF-8
- 支持中文显示
## 📊 使用场景
1. **数据备份**: 定期导出数据作为备份
2. **报表制作**: 制作月度/季度回访统计报表
3. **数据分析**: 分析各诊所的回访效果
4. **质量评估**: 评估回访工作的质量
5. **决策支持**: 为管理决策提供数据支持
## 🆘 常见问题
### Q: 导出失败怎么办?
A: 检查是否以管理员身份登录,查看错误提示信息
### Q: 文件下载不了?
A: 检查浏览器下载设置,确保允许自动下载
### Q: 数据不完整?
A: 确保数据库连接正常,检查是否有权限访问所有数据
### Q: 文件太大?
A: 可以分批导出,或者联系技术支持优化导出逻辑
## 📞 技术支持
如果遇到问题,请联系系统管理员或技术支持团队。
---
**最后更新**: 2025年8月12日
**版本**: 1.0
**开发者**: AI助手
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment