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