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