Files
bf1942 8938ce2708 refactor(api): 重构数据库访问为SQLAlchemy绑定的session
- 统一移除手动创建的数据库session,统一使用models模块中的db.session
- 修正项目创建接口,增加开始和结束日期的格式验证与处理
- 更新导入项目接口,使用枚举类型校验项目类型并优化异常处理
- 更新统计接口,避免多次查询假期数据,优化日期字符串处理
- 删除回滚前多余的session关闭调用,改为使用db.session.rollback()
- app.py中重构数据库初始化:统一配置SQLAlchemy,动态创建数据库路径和表
- 项目模型新增开始日期和结束日期字段支持
- 添加导入批次历史记录模型支持
- 优化工具函数中日期类型提示,移除无用导入
- 更新requirements.txt依赖版本回退,确保兼容性
- 前端菜单添加导入历史导航入口,实现页面访问路由绑定
2025-09-04 18:12:24 +08:00

208 lines
9.5 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>导入历史记录 - 个人工时记录系统</title>
<link rel="stylesheet" href="/static/css/styles.css">
</head>
<body>
<nav class="navbar">
<div class="nav-container">
<div class="nav-brand">
<h1>个人工时记录系统</h1>
</div>
<ul class="nav-menu">
<li><a href="/" class="nav-link">首页</a></li>
<li><a href="/projects" class="nav-link">项目管理</a></li>
<li><a href="/timerecords" class="nav-link">工时记录</a></li>
<li><a href="/statistics" class="nav-link">统计分析</a></li>
<li><a href="/import" class="nav-link active">导入历史</a></li>
</ul>
</div>
</nav>
<main class="main-content">
<div class="container">
<div class="page-header">
<h2>导入历史工时记录</h2>
</div>
<!-- 导入工具 -->
<div class="card" id="import-tool-card">
<div class="card-header">
<h3><a href="#" onclick="toggleCardBody(this); return false;" style="text-decoration: none; color: inherit;">手动导入数据 &#9662;</a></h3>
</div>
<div class="card-body" style="display: none;">
<p>请在下面的文本框中粘贴您的历史工时记录,每行一条。格式为:<code>月日 项目名 开始时间 结束时间 ActivityNum</code></p>
<p>例如:<code>8月20日 长鑫CODE/02C-FBV 9:00 17:00 9296892</code></p>
<form id="import-form">
<div class="form-group">
<label for="records-input">工时记录:</label>
<textarea id="records-input" name="records" class="form-control" rows="10" placeholder="请在此处粘贴记录..."></textarea>
</div>
<button type="submit" class="btn btn-primary">开始导入</button>
</form>
<div id="import-results" class="import-results-container" style="display: none; margin-top: 1.5rem;">
<h4>导入结果</h4>
<p>成功导入 <strong id="success-count">0</strong> 条记录。</p>
<div id="failed-records-section" style="display: none;">
<p>以下 <strong id="failure-count">0</strong> 条记录导入失败:</p>
<ul id="failed-records-list" class="failures-list"></ul>
</div>
</div>
</div>
</div>
<!-- 导入历史记录 -->
<div class="history-section" style="margin-top: 2rem;">
<h3>历史记录</h3>
<div id="import-history-list" class="import-history-grid">
<!-- 历史记录卡片将由JS动态加载 -->
<p>正在加载历史记录...</p>
</div>
</div>
</div>
</main>
<script>
// 切换卡片可见性
function toggleCardBody(element) {
const cardBody = element.closest('.card').querySelector('.card-body');
const isVisible = cardBody.style.display !== 'none';
cardBody.style.display = isVisible ? 'none' : 'block';
element.innerHTML = isVisible ? '手动导入数据 &#9662;' : '手动导入数据 &#9652;';
}
// 格式化日期
function formatImportDate(isoString) {
const date = new Date(isoString);
return date.toLocaleString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' });
}
// 加载导入历史
async function loadImportHistory() {
const listContainer = document.getElementById('import-history-list');
try {
const response = await fetch('/api/import/history');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const history = await response.json();
if (history.length === 0) {
listContainer.innerHTML = '<p>暂无导入历史记录。</p>';
return;
}
listContainer.innerHTML = ''; // 清空加载提示
history.forEach(batch => {
let statusClass = '';
switch (batch.status) {
case '成功': statusClass = 'status-success'; break;
case '部分成功': statusClass = 'status-partial'; break;
case '失败': statusClass = 'status-fail'; break;
}
const card = document.createElement('div');
card.className = 'import-card';
card.innerHTML = `
<div class="import-card-header">
<h4>批次 #${batch.id}</h4>
<span class="import-date">${formatImportDate(batch.import_date)}</span>
</div>
<div class="import-card-body">
<div class="import-card-stats">
<div class="stat-item">
<span class="stat-value" style="color: var(--success-color);">${batch.success_count}</span>
<span class="stat-label">成功</span>
</div>
<div class="stat-item">
<span class="stat-value" style="color: var(--danger-color);">${batch.failure_count}</span>
<span class="stat-label">失败</span>
</div>
<div class="stat-item">
<span class="stat-value">${batch.total_records}</span>
<span class="stat-label">总计</span>
</div>
</div>
<p><strong>源数据预览:</strong></p>
<div class="source-preview">${batch.source_preview || '无预览'}</div>
</div>
<div class="import-card-footer">
<span class="status-badge ${statusClass}">${batch.status}</span>
</div>
`;
listContainer.appendChild(card);
});
} catch (error) {
console.error('加载导入历史失败:', error);
listContainer.innerHTML = '<p style="color: var(--danger-color);">无法加载导入历史记录,请稍后重试。</p>';
}
}
// 处理导入表单提交
document.getElementById('import-form').addEventListener('submit', async function(event) {
event.preventDefault();
const recordsText = document.getElementById('records-input').value;
if (!recordsText.trim()) {
alert('请输入要导入的记录。');
return;
}
try {
const response = await fetch('/import', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ records: recordsText })
});
const result = await response.json();
const resultsContainer = document.getElementById('import-results');
const successCount = document.getElementById('success-count');
const failedSection = document.getElementById('failed-records-section');
const failureCount = document.getElementById('failure-count');
const failedList = document.getElementById('failed-records-list');
successCount.textContent = result.success_count;
failedList.innerHTML = '';
if (result.failures && result.failures.length > 0) {
failureCount.textContent = result.failure_count;
result.failures.forEach(fail => {
const li = document.createElement('li');
li.textContent = `[${fail.reason}] ${fail.line}`;
failedList.appendChild(li);
});
failedSection.style.display = 'block';
} else {
failedSection.style.display = 'none';
}
resultsContainer.style.display = 'block';
// 导入成功后清空输入框并重新加载历史
if (result.success_count > 0) {
document.getElementById('records-input').value = '';
loadImportHistory();
}
} catch (error) {
console.error('导入请求失败:', error);
alert('导入请求失败,请检查网络连接或联系管理员。');
}
});
// 页面加载时执行
document.addEventListener('DOMContentLoaded', loadImportHistory);
</script>
</body>
</html>