main
mula.liu 2026-02-06 17:14:37 +08:00
parent c791d053fa
commit 2eda8f9fc3
3 changed files with 65 additions and 16 deletions

View File

@ -14,23 +14,23 @@ class TerminalCheckMiddleware(BaseHTTPMiddleware):
# 2. 检查时间戳 (防重放/时钟同步)
# 优先从Header获取如果没有则尝试从Query Parameter获取
# client_time_str = request.headers.get("time") or request.query_params.get("time")
client_time_str = request.headers.get("time") or request.query_params.get("time")
# if client_time_str:
# try:
# client_time = int(client_time_str)
# server_time = int(time.time() * 1000)
if client_time_str:
try:
client_time = int(client_time_str)
server_time = int(time.time() * 1000)
# # 允许 10 分钟的误差 (10 * 60 * 1000 = 600000 ms)
# # 考虑到网络延迟和设备时间未校准,设置宽松一点
# if abs(server_time - client_time) > 600000:
# return create_api_response(
# code="400",
# message="设备时间与服务器时间差距过大,请校准时间"
# )
# except ValueError:
# # 时间格式错误,暂时忽略或返回错误
# pass
# 允许 10 分钟的误差 (10 * 60 * 1000 = 600000 ms)
# 考虑到网络延迟和设备时间未校准,设置宽松一点
if abs(server_time - client_time) > 600000:
return create_api_response(
code="400",
message="设备时间与服务器时间差距过大,请校准时间"
)
except ValueError:
# 时间格式错误,暂时忽略或返回错误
pass
# 3. 提取其他设备信息
device_type = request.headers.get("deviceType", "UNKNOWN")

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { LogOut, User, Users, Activity, Server, HardDrive, Cpu, MemoryStick, RefreshCw, UserX, ChevronDown, KeyRound, Shield, BookText, Waves, UserCog, Search } from 'lucide-react';
import { LogOut, User, Users, Activity, Server, HardDrive, Cpu, MemoryStick, RefreshCw, UserX, ChevronDown, KeyRound, Shield, BookText, Waves, UserCog, Search, FileJson } from 'lucide-react';
import apiClient from '../utils/apiClient';
import { buildApiUrl, API_ENDPOINTS } from '../config/api';
import Dropdown from '../components/Dropdown';
@ -264,6 +264,28 @@ const AdminDashboard = ({ user, onLogout }) => {
}
};
const handleDownloadTranscript = async (meetingId) => {
try {
const response = await apiClient.get(buildApiUrl(`/api/meetings/${meetingId}/transcript`));
if (response.code === '200') {
const dataStr = JSON.stringify(response.data, null, 2);
const blob = new Blob([dataStr], { type: "application/json" });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `transcript_${meetingId}.json`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} else {
showToast('获取转录数据失败', 'error');
}
} catch (err) {
console.error('Download transcript error:', err);
showToast('下载失败', 'error');
}
};
if (loading && !stats) {
return <PageLoading message="加载中..." />;
}
@ -690,6 +712,32 @@ const AdminDashboard = ({ user, onLogout }) => {
)}
</div>
</div>
{/* 转录结果下载 */}
<div className="detail-item">
<span className="detail-label">转录结果:</span>
<div className="detail-value">
<button
type="button"
onClick={() => handleDownloadTranscript(meetingDetails.meeting_id)}
style={{
background: 'none',
border: 'none',
padding: 0,
color: '#3b82f6',
textDecoration: 'underline',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: '4px',
fontSize: 'inherit'
}}
>
<FileJson size={14} />
下载JSON
</button>
</div>
</div>
</div>
) : (
<div className="empty-state">无法加载数据</div>

View File

@ -537,6 +537,7 @@
border-radius: 12px;
padding: 1.5rem;
backdrop-filter: blur(10px);
min-height: 200px; /* Prevent layout shift */
}
/* 转录状态显示样式 - 一行显示 */