refactor(api): 重构数据库访问为SQLAlchemy绑定的session
- 统一移除手动创建的数据库session,统一使用models模块中的db.session - 修正项目创建接口,增加开始和结束日期的格式验证与处理 - 更新导入项目接口,使用枚举类型校验项目类型并优化异常处理 - 更新统计接口,避免多次查询假期数据,优化日期字符串处理 - 删除回滚前多余的session关闭调用,改为使用db.session.rollback() - app.py中重构数据库初始化:统一配置SQLAlchemy,动态创建数据库路径和表 - 项目模型新增开始日期和结束日期字段支持 - 添加导入批次历史记录模型支持 - 优化工具函数中日期类型提示,移除无用导入 - 更新requirements.txt依赖版本回退,确保兼容性 - 前端菜单添加导入历史导航入口,实现页面访问路由绑定
This commit is contained in:
373
static/js/common.js
Normal file
373
static/js/common.js
Normal file
@@ -0,0 +1,373 @@
|
||||
// 公共工具函数和API调用
|
||||
|
||||
// API 基础 URL
|
||||
const API_BASE_URL = '';
|
||||
|
||||
// 通用 API 调用函数
|
||||
async function apiCall(url, options = {}) {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}${url}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
},
|
||||
...options
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || `HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('API调用失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// GET 请求
|
||||
async function apiGet(url) {
|
||||
return apiCall(url, { method: 'GET' });
|
||||
}
|
||||
|
||||
// POST 请求
|
||||
async function apiPost(url, data) {
|
||||
return apiCall(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
}
|
||||
|
||||
// PUT 请求
|
||||
async function apiPut(url, data) {
|
||||
return apiCall(url, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
}
|
||||
|
||||
// DELETE 请求
|
||||
async function apiDelete(url) {
|
||||
return apiCall(url, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
// 显示成功消息
|
||||
function showSuccess(message) {
|
||||
showNotification(message, 'success');
|
||||
}
|
||||
|
||||
// 显示错误消息
|
||||
function showError(message) {
|
||||
showNotification(message, 'error');
|
||||
}
|
||||
|
||||
// 显示通知
|
||||
function showNotification(message, type = 'info') {
|
||||
// 创建通知元素
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification ${type}`;
|
||||
notification.innerHTML = `
|
||||
<span>${message}</span>
|
||||
<button onclick="this.parentElement.remove()">×</button>
|
||||
`;
|
||||
|
||||
// 添加到页面
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// 自动隐藏
|
||||
setTimeout(() => {
|
||||
if (notification.parentElement) {
|
||||
notification.remove();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
function formatDate(dateString) {
|
||||
if (!dateString) return '';
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('zh-CN');
|
||||
}
|
||||
|
||||
// 格式化日期时间
|
||||
function formatDateTime(dateString) {
|
||||
if (!dateString) return '';
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('zh-CN');
|
||||
}
|
||||
|
||||
// 获取今天的日期字符串
|
||||
function getTodayString() {
|
||||
const today = new Date();
|
||||
return today.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
// 获取本周的开始和结束日期
|
||||
function getThisWeekRange() {
|
||||
const today = new Date();
|
||||
const dayOfWeek = today.getDay();
|
||||
const startOfWeek = new Date(today);
|
||||
startOfWeek.setDate(today.getDate() - dayOfWeek + 1); // 周一
|
||||
|
||||
const endOfWeek = new Date(startOfWeek);
|
||||
endOfWeek.setDate(startOfWeek.getDate() + 6); // 周日
|
||||
|
||||
return {
|
||||
start: startOfWeek.toISOString().split('T')[0],
|
||||
end: endOfWeek.toISOString().split('T')[0]
|
||||
};
|
||||
}
|
||||
|
||||
// 模态框控制
|
||||
function showModal(modalId) {
|
||||
const modal = document.getElementById(modalId);
|
||||
if (modal) {
|
||||
modal.classList.add('show');
|
||||
modal.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal(modalId) {
|
||||
const modal = document.getElementById(modalId);
|
||||
if (modal) {
|
||||
modal.classList.remove('show');
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 表单重置
|
||||
function resetForm(formId) {
|
||||
const form = document.getElementById(formId);
|
||||
if (form) {
|
||||
form.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// 获取表单数据
|
||||
function getFormData(formId) {
|
||||
const form = document.getElementById(formId);
|
||||
if (!form) return {};
|
||||
|
||||
const formData = new FormData(form);
|
||||
const data = {};
|
||||
|
||||
for (let [key, value] of formData.entries()) {
|
||||
data[key] = value;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// 填充表单数据
|
||||
function fillForm(formId, data) {
|
||||
const form = document.getElementById(formId);
|
||||
if (!form) return;
|
||||
|
||||
Object.keys(data).forEach(key => {
|
||||
const field = form.querySelector(`[name="${key}"]`);
|
||||
if (field) {
|
||||
field.value = data[key] || '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 工时格式化函数
|
||||
function formatHours(hours) {
|
||||
if (!hours || hours === '-' || hours === '0:00') {
|
||||
return hours || '-';
|
||||
}
|
||||
|
||||
// 如果包含冒号,直接返回
|
||||
if (hours.includes(':')) {
|
||||
return hours;
|
||||
}
|
||||
|
||||
// 如果是小数格式,转换为时:分格式
|
||||
const decimal = parseFloat(hours);
|
||||
if (!isNaN(decimal)) {
|
||||
const h = Math.floor(decimal);
|
||||
const m = Math.round((decimal - h) * 60);
|
||||
return `${h}:${m.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
return hours;
|
||||
}
|
||||
|
||||
// 工时转换为小数
|
||||
function hoursToDecimal(hours) {
|
||||
if (!hours || hours === '-') return 0;
|
||||
|
||||
if (hours.includes(':')) {
|
||||
const [h, m] = hours.split(':').map(Number);
|
||||
return h + (m || 0) / 60;
|
||||
}
|
||||
|
||||
return parseFloat(hours) || 0;
|
||||
}
|
||||
|
||||
// 小数转换为工时格式
|
||||
function decimalToHours(decimal) {
|
||||
if (decimal === 0) return '0:00';
|
||||
|
||||
const hours = Math.floor(decimal);
|
||||
const minutes = Math.round((decimal - hours) * 60);
|
||||
return `${hours}:${minutes.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
// 计算工时
|
||||
function calculateHours(startTime, endTime) {
|
||||
if (!startTime || !endTime || startTime === '-' || endTime === '-') {
|
||||
return '-';
|
||||
}
|
||||
|
||||
try {
|
||||
const [startH, startM] = startTime.split(':').map(Number);
|
||||
const [endH, endM] = endTime.split(':').map(Number);
|
||||
|
||||
let startMinutes = startH * 60 + startM;
|
||||
let endMinutes = endH * 60 + endM;
|
||||
|
||||
// 处理跨日情况
|
||||
if (endMinutes < startMinutes) {
|
||||
endMinutes += 24 * 60;
|
||||
}
|
||||
|
||||
const diffMinutes = endMinutes - startMinutes;
|
||||
const hours = Math.floor(diffMinutes / 60);
|
||||
const minutes = diffMinutes % 60;
|
||||
|
||||
return `${hours}:${minutes.toString().padStart(2, '0')}`;
|
||||
} catch (error) {
|
||||
console.error('计算工时失败:', error);
|
||||
return '0:00';
|
||||
}
|
||||
}
|
||||
|
||||
// 星期中文显示
|
||||
function getDayOfWeekChinese(dateString) {
|
||||
const date = new Date(dateString);
|
||||
const days = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
|
||||
return days[date.getDay()];
|
||||
}
|
||||
|
||||
// 判断是否为今天
|
||||
function isToday(dateString) {
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
return dateString === today;
|
||||
}
|
||||
|
||||
// 文件下载
|
||||
function downloadFile(content, filename, type = 'text/plain') {
|
||||
const blob = new Blob([content], { type });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// CSV 生成
|
||||
function generateCSV(data, headers) {
|
||||
const csvContent = [
|
||||
headers.join(','),
|
||||
...data.map(row => headers.map(header => {
|
||||
const value = row[header] || '';
|
||||
// 如果值包含逗号、引号或换行符,需要用引号包围
|
||||
if (value.toString().includes(',') || value.toString().includes('"') || value.toString().includes('\n')) {
|
||||
return `"${value.toString().replace(/"/g, '""')}"`;
|
||||
}
|
||||
return value;
|
||||
}).join(','))
|
||||
].join('\n');
|
||||
|
||||
return csvContent;
|
||||
}
|
||||
|
||||
// 页面加载完成后的初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 添加点击外部关闭模态框的功能
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.classList.contains('modal')) {
|
||||
e.target.classList.remove('show');
|
||||
e.target.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// 添加ESC键关闭模态框的功能
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
const openModal = document.querySelector('.modal.show');
|
||||
if (openModal) {
|
||||
openModal.classList.remove('show');
|
||||
openModal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 添加通知样式到头部(如果不存在)
|
||||
if (!document.querySelector('#notification-styles')) {
|
||||
const style = document.createElement('style');
|
||||
style.id = 'notification-styles';
|
||||
style.textContent = `
|
||||
.notification {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 15px 20px;
|
||||
border-radius: 5px;
|
||||
color: white;
|
||||
z-index: 10000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
max-width: 400px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
.notification.success {
|
||||
background-color: #27ae60;
|
||||
}
|
||||
|
||||
.notification.error {
|
||||
background-color: #e74c3c;
|
||||
}
|
||||
|
||||
.notification.info {
|
||||
background-color: #3498db;
|
||||
}
|
||||
|
||||
.notification button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
Reference in New Issue
Block a user