- 统一移除手动创建的数据库session,统一使用models模块中的db.session - 修正项目创建接口,增加开始和结束日期的格式验证与处理 - 更新导入项目接口,使用枚举类型校验项目类型并优化异常处理 - 更新统计接口,避免多次查询假期数据,优化日期字符串处理 - 删除回滚前多余的session关闭调用,改为使用db.session.rollback() - app.py中重构数据库初始化:统一配置SQLAlchemy,动态创建数据库路径和表 - 项目模型新增开始日期和结束日期字段支持 - 添加导入批次历史记录模型支持 - 优化工具函数中日期类型提示,移除无用导入 - 更新requirements.txt依赖版本回退,确保兼容性 - 前端菜单添加导入历史导航入口,实现页面访问路由绑定
347 lines
11 KiB
JavaScript
347 lines
11 KiB
JavaScript
// 工时记录页面JavaScript
|
||
|
||
let timeRecords = [];
|
||
let projects = [];
|
||
let currentEditingRecord = null;
|
||
|
||
// 页面加载时初始化
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
initializePage();
|
||
setupEventListeners();
|
||
});
|
||
|
||
// 初始化页面
|
||
async function initializePage() {
|
||
await Promise.all([
|
||
loadProjects(),
|
||
loadTimeRecords()
|
||
]);
|
||
|
||
// 设置默认筛选日期为本周
|
||
const weekRange = getThisWeekRange();
|
||
document.getElementById('filter-start-date').value = weekRange.start;
|
||
document.getElementById('filter-end-date').value = weekRange.end;
|
||
|
||
// 设置默认记录日期为今天
|
||
document.getElementById('record_date').value = getTodayString();
|
||
}
|
||
|
||
// 设置事件监听器
|
||
function setupEventListeners() {
|
||
// 工时记录表单提交
|
||
const recordForm = document.getElementById('timerecord-form');
|
||
if (recordForm) {
|
||
recordForm.addEventListener('submit', handleRecordSubmit);
|
||
}
|
||
|
||
// 时间输入变化时自动计算工时
|
||
const startTimeInput = document.getElementById('start_time');
|
||
const endTimeInput = document.getElementById('end_time');
|
||
|
||
if (startTimeInput) {
|
||
startTimeInput.addEventListener('change', calculateHours);
|
||
}
|
||
if (endTimeInput) {
|
||
endTimeInput.addEventListener('change', calculateHours);
|
||
}
|
||
}
|
||
|
||
// 加载项目列表
|
||
async function loadProjects() {
|
||
try {
|
||
const response = await apiGet('/api/projects');
|
||
projects = response.data;
|
||
populateProjectSelect();
|
||
} catch (error) {
|
||
showError('加载项目失败: ' + error.message);
|
||
console.error('加载项目失败:', error);
|
||
}
|
||
}
|
||
|
||
// 填充项目选择框
|
||
function populateProjectSelect() {
|
||
const selects = ['project_id', 'filter-project'];
|
||
|
||
selects.forEach(selectId => {
|
||
const select = document.getElementById(selectId);
|
||
if (!select) return;
|
||
|
||
// 清空现有选项(保留第一个默认选项)
|
||
const firstOption = select.querySelector('option');
|
||
select.innerHTML = '';
|
||
if (firstOption) {
|
||
select.appendChild(firstOption);
|
||
}
|
||
|
||
// 添加项目选项
|
||
projects.forEach(project => {
|
||
const option = document.createElement('option');
|
||
option.value = project.id;
|
||
option.textContent = project.project_name;
|
||
select.appendChild(option);
|
||
});
|
||
});
|
||
}
|
||
|
||
// 加载工时记录
|
||
async function loadTimeRecords() {
|
||
try {
|
||
const params = new URLSearchParams();
|
||
|
||
const startDate = document.getElementById('filter-start-date').value;
|
||
const endDate = document.getElementById('filter-end-date').value;
|
||
const projectId = document.getElementById('filter-project').value;
|
||
|
||
if (startDate) params.append('start_date', startDate);
|
||
if (endDate) params.append('end_date', endDate);
|
||
if (projectId) params.append('project_id', projectId);
|
||
|
||
const url = `/api/timerecords${params.toString() ? '?' + params.toString() : ''}`;
|
||
const response = await apiGet(url);
|
||
timeRecords = response.data;
|
||
renderTimeRecordsTable();
|
||
} catch (error) {
|
||
showError('加载工时记录失败: ' + error.message);
|
||
console.error('加载工时记录失败:', error);
|
||
}
|
||
}
|
||
|
||
// 渲染工时记录表格
|
||
function renderTimeRecordsTable() {
|
||
const tbody = document.getElementById('timerecords-tbody');
|
||
if (!tbody) return;
|
||
|
||
if (timeRecords.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="9" class="text-center">暂无工时记录</td></tr>';
|
||
return;
|
||
}
|
||
|
||
tbody.innerHTML = timeRecords.map(record => {
|
||
const projectDisplay = record.project ? record.project.project_name : '-';
|
||
|
||
const rowClass = getRowClass(record);
|
||
|
||
return `
|
||
<tr class="${rowClass}">
|
||
<td>${formatDate(record.date)}</td>
|
||
<td>${record.day_of_week || getDayOfWeekChinese(record.date)}</td>
|
||
<td>${escapeHtml(record.event_description || '-')}</td>
|
||
<td>${escapeHtml(projectDisplay)}</td>
|
||
<td>${record.start_time || '-'}</td>
|
||
<td>${record.end_time || '-'}</td>
|
||
<td>${escapeHtml(record.activity_num || '-')}</td>
|
||
<td>${record.hours || '-'}</td>
|
||
<td>
|
||
<button class="btn btn-sm btn-outline" onclick="editRecord(${record.id})">编辑</button>
|
||
<button class="btn btn-sm btn-danger" onclick="deleteRecord(${record.id})">删除</button>
|
||
</td>
|
||
</tr>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
// 获取行的CSS类名(根据是否为休息日)
|
||
function getRowClass(record) {
|
||
if (record.is_holiday) {
|
||
if (record.is_working_on_holiday) {
|
||
return 'working-holiday-row'; // 休息日工作
|
||
} else {
|
||
return 'holiday-row'; // 休息日休息
|
||
}
|
||
}
|
||
return '';
|
||
}
|
||
|
||
// 重置筛选条件
|
||
function resetFilters() {
|
||
document.getElementById('filter-start-date').value = '';
|
||
document.getElementById('filter-end-date').value = '';
|
||
document.getElementById('filter-project').value = '';
|
||
loadTimeRecords();
|
||
}
|
||
|
||
// 显示创建记录模态框
|
||
function showCreateRecordModal() {
|
||
currentEditingRecord = null;
|
||
resetForm('timerecord-form');
|
||
document.getElementById('timerecord-modal-title').textContent = '新建工时记录';
|
||
|
||
// 设置默认日期为今天
|
||
document.getElementById('record_date').value = getTodayString();
|
||
|
||
// 设置默认时间
|
||
document.getElementById('start_time').value = '09:00';
|
||
document.getElementById('end_time').value = '17:00';
|
||
|
||
// 自动计算工时
|
||
updateHoursInput();
|
||
|
||
// 隐藏休息日信息和警告
|
||
document.getElementById('holiday-info').style.display = 'none';
|
||
document.getElementById('holiday-warning').style.display = 'none';
|
||
|
||
showModal('timerecord-modal');
|
||
|
||
// 检查今天是否为休息日
|
||
checkHoliday();
|
||
}
|
||
|
||
// 检查休息日
|
||
async function checkHoliday() {
|
||
const dateInput = document.getElementById('record_date');
|
||
const date = dateInput.value;
|
||
|
||
if (!date) return;
|
||
|
||
try {
|
||
const response = await apiGet(`/api/timerecords/check_holiday/${date}`);
|
||
const holidayInfo = response.data;
|
||
|
||
updateHolidayInfo(holidayInfo);
|
||
} catch (error) {
|
||
console.error('检查休息日失败:', error);
|
||
// 如果API调用失败,使用本地判断
|
||
const dayOfWeek = new Date(date).getDay();
|
||
const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
|
||
|
||
updateHolidayInfo({
|
||
is_holiday: isWeekend,
|
||
holiday_type: isWeekend ? 'weekend' : null,
|
||
holiday_name: null,
|
||
day_of_week: getDayOfWeekChinese(date)
|
||
});
|
||
}
|
||
}
|
||
|
||
// 更新休息日信息显示
|
||
function updateHolidayInfo(holidayInfo) {
|
||
const holidayInfoDiv = document.getElementById('holiday-info');
|
||
const holidayBadge = document.getElementById('holiday-badge');
|
||
const holidayText = document.getElementById('holiday-text');
|
||
const holidayWarning = document.getElementById('holiday-warning');
|
||
|
||
if (holidayInfo.is_holiday) {
|
||
// 显示休息日信息
|
||
holidayInfoDiv.style.display = 'flex';
|
||
holidayWarning.style.display = 'block';
|
||
|
||
// 设置徽章样式和文本
|
||
if (holidayInfo.holiday_type === 'weekend') {
|
||
holidayBadge.className = 'badge weekend';
|
||
holidayBadge.textContent = '周末';
|
||
} else if (holidayInfo.holiday_type === 'national_holiday') {
|
||
holidayBadge.className = 'badge national-holiday';
|
||
holidayBadge.textContent = '节假日';
|
||
} else {
|
||
holidayBadge.className = 'badge weekend';
|
||
holidayBadge.textContent = '休息日';
|
||
}
|
||
|
||
holidayText.textContent = holidayInfo.holiday_name || `${holidayInfo.day_of_week} 休息日`;
|
||
} else {
|
||
// 隐藏休息日信息
|
||
holidayInfoDiv.style.display = 'none';
|
||
holidayWarning.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// 更新工时输入框
|
||
function updateHoursInput() {
|
||
const startTime = document.getElementById('start_time').value;
|
||
const endTime = document.getElementById('end_time').value;
|
||
const hoursField = document.getElementById('hours');
|
||
|
||
if (startTime && endTime) {
|
||
const calculated = calculateHours(startTime, endTime); // 调用 common.js 中的全局函数
|
||
hoursField.value = calculated;
|
||
} else {
|
||
hoursField.value = '';
|
||
}
|
||
}
|
||
|
||
// 处理记录表单提交
|
||
async function handleRecordSubmit(e) {
|
||
e.preventDefault();
|
||
|
||
const formData = getFormData('timerecord-form');
|
||
|
||
// 验证必填字段
|
||
if (!formData.date) {
|
||
showError('请选择日期');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
let response;
|
||
if (currentEditingRecord) {
|
||
// 更新记录
|
||
response = await apiPut(`/api/timerecords/${currentEditingRecord.id}`, formData);
|
||
} else {
|
||
// 创建新记录
|
||
response = await apiPost('/api/timerecords', formData);
|
||
}
|
||
|
||
showSuccess(currentEditingRecord ? '工时记录更新成功' : '工时记录创建成功');
|
||
closeModal('timerecord-modal');
|
||
location.reload(); // 刷新页面以显示最新数据
|
||
} catch (error) {
|
||
showError(error.message);
|
||
}
|
||
}
|
||
|
||
// 编辑记录
|
||
function editRecord(recordId) {
|
||
const record = timeRecords.find(r => r.id === recordId);
|
||
if (!record) {
|
||
showError('记录不存在');
|
||
return;
|
||
}
|
||
|
||
currentEditingRecord = record;
|
||
document.getElementById('timerecord-modal-title').textContent = '编辑工时记录';
|
||
|
||
// 填充表单数据
|
||
fillForm('timerecord-form', {
|
||
date: record.date,
|
||
event_description: record.event_description || '',
|
||
project_id: record.project_id || '',
|
||
start_time: record.start_time || '',
|
||
end_time: record.end_time || '',
|
||
activity_num: record.activity_num || '',
|
||
hours: record.hours || ''
|
||
});
|
||
|
||
showModal('timerecord-modal');
|
||
|
||
// 检查是否为休息日
|
||
checkHoliday();
|
||
}
|
||
|
||
// 删除记录
|
||
async function deleteRecord(recordId) {
|
||
const record = timeRecords.find(r => r.id === recordId);
|
||
if (!record) {
|
||
showError('记录不存在');
|
||
return;
|
||
}
|
||
|
||
if (!confirm(`确定要删除这条工时记录吗?`)) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await apiDelete(`/api/timerecords/${recordId}`);
|
||
showSuccess('工时记录删除成功');
|
||
location.reload(); // 刷新页面以显示最新数据
|
||
} catch (error) {
|
||
showError('删除工时记录失败: ' + error.message);
|
||
}
|
||
}
|
||
|
||
// HTML转义函数
|
||
function escapeHtml(text) {
|
||
if (!text) return '';
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
} |