ERP/frontend/js/api.js

197 lines
7.9 KiB
JavaScript
Raw Normal View History

const API = (() => {
const base = '/api';
async function request(path, opts = {}) {
const overlay = document.getElementById('overlay');
overlay.classList.remove('hidden');
try {
const res = await fetch(base + path, {
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
...opts
});
if (!res.ok) throw new Error(await res.text());
return await res.json();
} catch (e) {
toast(e.message || '请求失败');
throw e;
} finally {
overlay.classList.add('hidden');
}
}
async function requestQuiet(path, opts = {}) {
// 创建超时控制器
const controller = opts.signal ? null : new AbortController();
const timeoutId = controller ? setTimeout(() => {
controller.abort();
console.warn('[API] 请求超时:', path);
}, 10000) : null; // 10秒超时
try {
const res = await fetch(base + path, {
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
signal: opts.signal || (controller ? controller.signal : undefined),
...opts
});
if (timeoutId) clearTimeout(timeoutId);
if (!res.ok) throw new Error(await res.text());
return await res.json();
} catch (e) {
if (timeoutId) clearTimeout(timeoutId);
// 忽略取消的请求
if (e.name === 'AbortError') {
return { list: [] };
}
throw e;
}
}
async function uploadFile(path, formData) {
const overlay = document.getElementById('overlay');
overlay.classList.remove('hidden');
try {
const res = await fetch(base + path, {
method: 'POST',
body: formData,
credentials: 'include'
});
if (!res.ok) throw new Error(await res.text());
return await res.json();
} catch (e) {
toast(e.message || '上传失败');
throw e;
} finally {
overlay.classList.add('hidden');
}
}
2025-11-22 12:40:46 +00:00
async function uploadFileWithProgress(path, formData, onProgress) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
// 上传进度
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable && onProgress) {
const percent = Math.round((e.loaded / e.total) * 100);
const loaded = e.loaded;
const total = e.total;
onProgress({ percent, loaded, total });
}
});
// 完成
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const result = JSON.parse(xhr.responseText);
resolve(result);
} catch (e) {
reject(new Error('解析响应失败'));
}
} else {
reject(new Error(xhr.responseText || '上传失败'));
}
});
// 错误
xhr.addEventListener('error', () => {
reject(new Error('网络错误'));
});
// 中止
xhr.addEventListener('abort', () => {
reject(new Error('上传已取消'));
});
xhr.open('POST', base + path);
xhr.withCredentials = true;
xhr.send(formData);
});
}
function toast(msg) {
const t = document.getElementById('toast');
t.textContent = msg;
t.classList.add('show');
setTimeout(() => t.classList.remove('show'), 2000);
}
return {
2025-12-08 03:20:28 +00:00
// 通用HTTP方法
get: (path) => request(path.replace('/api', '')),
post: (path, data) => request(path.replace('/api', ''), { method: 'POST', body: JSON.stringify(data) }),
put: (path, data) => request(path.replace('/api', ''), { method: 'PUT', body: JSON.stringify(data) }),
delete: (path) => request(path.replace('/api', ''), { method: 'DELETE' }),
login: (username, password) => request('/auth/login', { method: 'POST', body: JSON.stringify({ username, password }) }),
me: () => request('/auth/me'),
logout: () => request('/auth/logout', { method: 'POST' }),
dashboard: () => request('/dashboard'),
overview: () => request('/overview'),
uploadMac: data => request('/upload/mac', { method: 'POST', body: JSON.stringify(data) }),
uploadMacFile: file => {
const fd = new FormData();
fd.append('file', file);
return uploadFile('/upload/mac-file', fd);
},
uploadStats: data => request('/upload/stats', { method: 'POST', body: JSON.stringify(data) }),
uploadRepairs: data => request('/upload/repairs', { method: 'POST', body: JSON.stringify(data) }),
uploadDefects: data => request('/upload/defects', { method: 'POST', body: JSON.stringify(data) }),
uploadDefectsFile: file => {
const fd = new FormData();
fd.append('file', file);
return uploadFile('/upload/defects-file', fd);
},
uploadShipments: data => request('/upload/shipments', { method: 'POST', body: JSON.stringify(data) }),
devices: () => request('/collect/devices'),
environment: () => request('/collect/environment'),
personnel: () => request('/collect/personnel'),
qa: () => request('/collect/qa'),
production: () => request('/collect/production'),
addPersonnel: (name, role) => request('/collect/personnel', { method: 'POST', body: JSON.stringify({ name, role }) }),
listMac: () => request('/list/mac'),
listStats: () => request('/list/stats'),
listRepairs: () => request('/list/repairs'),
listDefects: () => request('/list/defects'),
listShipments: () => request('/list/shipments'),
auditPdd: (params={}) => request('/audit/pdd' + buildQuery(params)),
auditYt: (params={}) => request('/audit/yt' + buildQuery(params)),
auditPddQuiet: (params={}) => requestQuiet('/audit/pdd' + buildQuery(params)),
auditYtQuiet: (params={}) => requestQuiet('/audit/yt' + buildQuery(params)),
exportExcel: params => request('/export/excel', { method: 'POST', body: JSON.stringify(params) }),
exportPdf: params => request('/export/pdf', { method: 'POST', body: JSON.stringify(params) }),
toast,
adminUsers: () => request('/admin/users'),
resetPassword: (username, new_password) => request('/admin/reset-password', { method: 'POST', body: JSON.stringify({ username, new_password }) }),
changePassword: (username, new_password) => request('/admin/change-password', { method: 'POST', body: JSON.stringify({ username, new_password }) }),
deleteUser: username => request('/admin/delete-user', { method: 'POST', body: JSON.stringify({ username }) }),
clearModule: module => request('/admin/clear', { method: 'POST', body: JSON.stringify({ module }) }),
getNotifications: () => requestQuiet('/notifications'),
getUnreadCount: () => requestQuiet('/notifications/unread-count'),
markNotificationRead: id => requestQuiet('/notifications/mark-read', { method: 'POST', body: JSON.stringify({ id }) }),
markAllNotificationsRead: () => requestQuiet('/notifications/mark-all-read', { method: 'POST' }),
deleteReadNotifications: () => requestQuiet('/notifications/delete-read', { method: 'POST' }),
2025-11-22 12:40:46 +00:00
updateShipmentsPlatform: () => request('/shipments/update-platform', { method: 'POST' }),
listSopFiles: () => request('/sop/list'),
uploadSopFile: (file, description) => {
const fd = new FormData();
fd.append('file', file);
fd.append('description', description);
return uploadFile('/sop/upload', fd);
},
uploadSopFileWithProgress: (file, description, onProgress) => {
const fd = new FormData();
fd.append('file', file);
fd.append('description', description);
return uploadFileWithProgress('/sop/upload', fd, onProgress);
},
deleteSopFile: id => request(`/sop/delete/${id}`, { method: 'POST' })
};
})();
function buildQuery(params){
const q = new URLSearchParams();
if(params.start) q.set('start', params.start);
if(params.end) q.set('end', params.end);
if(params.limit) q.set('limit', params.limit);
if(params.order) q.set('order', params.order);
const s = q.toString();
return s ? ('?' + s) : '';
}