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
This diff is collapsed. Click to expand it.
#!/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