refactor(api): 重构数据库访问为SQLAlchemy绑定的session

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

210
static/js/dashboard.js Normal file
View File

@@ -0,0 +1,210 @@
// 首页面板JavaScript
// 页面加载时初始化
document.addEventListener('DOMContentLoaded', function() {
loadDashboardStats();
loadRecentRecords();
});
// 加载仪表板统计数据
async function loadDashboardStats() {
try {
// 并行加载项目和工时统计
const [projectsResponse, recentRecordsResponse] = await Promise.all([
apiGet('/api/projects'),
loadThisWeekHours()
]);
// 更新活跃项目数
const activeProjects = projectsResponse.data.filter(p => p.is_active).length;
updateStatValue('total-projects', activeProjects);
// 更新本周工时
updateStatValue('this-week-hours', recentRecordsResponse.weeklyHours || '0:00');
// 加载本月记录数
const thisMonthCount = await loadThisMonthRecordsCount();
updateStatValue('this-month-records', thisMonthCount);
} catch (error) {
console.error('加载仪表板统计失败:', error);
// 显示默认值
updateStatValue('total-projects', '0');
updateStatValue('this-week-hours', '0:00');
updateStatValue('this-month-records', '0');
}
}
// 更新统计值
function updateStatValue(elementId, value) {
const element = document.getElementById(elementId);
if (element) {
element.textContent = value;
}
}
// 加载本周工时
async function loadThisWeekHours() {
try {
const weekRange = getThisWeekRange();
const url = `/api/timerecords?start_date=${weekRange.start}&end_date=${weekRange.end}`;
const response = await apiGet(url);
// 计算本周总工时
let totalHours = 0;
response.data.forEach(record => {
if (record.hours && record.hours !== '-') {
totalHours += hoursToDecimal(record.hours);
}
});
return {
weeklyHours: decimalToHours(totalHours),
recordCount: response.data.length
};
} catch (error) {
console.error('加载本周工时失败:', error);
return { weeklyHours: '0:00', recordCount: 0 };
}
}
// 加载本月记录数
async function loadThisMonthRecordsCount() {
try {
const today = new Date();
const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
const lastDayOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0);
const startDate = firstDayOfMonth.toISOString().split('T')[0];
const endDate = lastDayOfMonth.toISOString().split('T')[0];
const url = `/api/timerecords?start_date=${startDate}&end_date=${endDate}`;
const response = await apiGet(url);
return response.data.length;
} catch (error) {
console.error('加载本月记录数失败:', error);
return 0;
}
}
// 加载最近记录
async function loadRecentRecords() {
try {
// 获取最近7天的记录
const endDate = new Date();
const startDate = new Date();
startDate.setDate(endDate.getDate() - 6); // 最近7天
const url = `/api/timerecords?start_date=${startDate.toISOString().split('T')[0]}&end_date=${endDate.toISOString().split('T')[0]}`;
const response = await apiGet(url);
renderRecentRecords(response.data.slice(0, 5)); // 只显示最近5条
} catch (error) {
console.error('加载最近记录失败:', error);
const container = document.getElementById('recent-records-list');
if (container) {
container.innerHTML = '<p class="text-center">加载失败</p>';
}
}
}
// 渲染最近记录
function renderRecentRecords(records) {
const container = document.getElementById('recent-records-list');
if (!container) return;
if (records.length === 0) {
container.innerHTML = '<p class="text-center">暂无最近记录</p>';
return;
}
container.innerHTML = `
<div class="recent-records-table">
<table class="data-table">
<thead>
<tr>
<th>日期</th>
<th>事件</th>
<th>项目</th>
<th>工时</th>
</tr>
</thead>
<tbody>
${records.map(record => {
const projectDisplay = record.project
? (record.project.project_type === 'traditional'
? `${record.project.customer_name} ${record.project.project_code}`
: `${record.project.customer_name} ${record.project.contract_number}`)
: '-';
const rowClass = getRowClass(record);
return `
<tr class="${rowClass}">
<td>
${formatDate(record.date)}
${isToday(record.date) ? '<span class="today-badge">今天</span>' : ''}
</td>
<td>${escapeHtml(record.event_description || '-')}</td>
<td>${escapeHtml(projectDisplay)}</td>
<td>${record.hours || '-'}</td>
</tr>
`;
}).join('')}
</tbody>
</table>
</div>
`;
}
// 获取行的CSS类名根据是否为休息日
function getRowClass(record) {
if (record.is_holiday) {
if (record.is_working_on_holiday) {
return 'working-holiday-row'; // 休息日工作
} else {
return 'holiday-row'; // 休息日休息
}
}
return '';
}
// HTML转义函数
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 添加今天徽章样式
if (!document.querySelector('#today-badge-styles')) {
const style = document.createElement('style');
style.id = 'today-badge-styles';
style.textContent = `
.today-badge {
background-color: #3498db;
color: white;
padding: 2px 6px;
border-radius: 3px;
font-size: 10px;
font-weight: 600;
margin-left: 5px;
}
.recent-records-table {
overflow-x: auto;
}
.recent-records-table .data-table {
margin-bottom: 0;
}
.recent-records-table .data-table td {
font-size: 13px;
padding: 10px 8px;
}
`;
document.head.appendChild(style);
}