feat(students): multi-photo gallery, group photos in export, KIBERone rebrand
This commit is contained in:
@@ -183,10 +183,12 @@ async function exportStudentProfile(id) {
|
||||
}
|
||||
|
||||
let profileDataId = null;
|
||||
let profilePhotos = [];
|
||||
|
||||
function openProfileData(id) {
|
||||
profileDataId = id;
|
||||
loadGroupOptions();
|
||||
loadStudentPhotos(id);
|
||||
fetch(`${API}/api/students/${id}/profile`, { headers: hdr() })
|
||||
.then(r => (r.ok ? r.json() : Promise.reject(new Error('load'))))
|
||||
.then(data => fillProfileForm(data.profile, data.name, data.photo_path))
|
||||
@@ -199,6 +201,94 @@ function closeProfileData() {
|
||||
profileDataId = null;
|
||||
}
|
||||
|
||||
async function loadStudentPhotos(id) {
|
||||
try {
|
||||
const res = await fetch(`${API}/api/students/${id}/photos`, { headers: hdr() });
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
profilePhotos = data.photos || [];
|
||||
document.getElementById('pfPhoto').value = data.photo_path || '';
|
||||
renderStudentPhotos();
|
||||
} catch (err) {
|
||||
profilePhotos = [];
|
||||
}
|
||||
}
|
||||
|
||||
function renderStudentPhotos() {
|
||||
const wrap = document.getElementById('pfPhotos');
|
||||
if (!wrap) return;
|
||||
const main = document.getElementById('pfPhoto').value.trim();
|
||||
if (!profilePhotos.length) {
|
||||
wrap.innerHTML = '<span class="hint" style="margin:0">Пока нет загруженных фото</span>';
|
||||
return;
|
||||
}
|
||||
wrap.innerHTML = profilePhotos.map(p => `
|
||||
<div class="pf-photo${p.photo_path === main ? ' main' : ''}" data-pf-set="${p.id}" title="Сделать главным">
|
||||
<img src="${esc(p.photo_path)}" alt="" loading="lazy">
|
||||
${p.photo_path === main ? '<span class="pf-badge">Главная</span>' : ''}
|
||||
<button type="button" class="pf-del" data-pf-del="${p.id}" title="Удалить фото">✕</button>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
async function uploadStudentPhoto(file) {
|
||||
if (!profileDataId || !file) return;
|
||||
if (!file.type.startsWith('image/')) {
|
||||
alert('Можно загружать только изображения');
|
||||
return;
|
||||
}
|
||||
showToast('Загружаем фото...');
|
||||
const fd = new FormData();
|
||||
fd.append('photo', file);
|
||||
try {
|
||||
const res = await fetch(`${API}/api/students/${profileDataId}/photos`, { method: 'POST', headers: hdr(), body: fd });
|
||||
if (res.ok) {
|
||||
await loadStudentPhotos(profileDataId);
|
||||
showToast('Фото загружено');
|
||||
} else {
|
||||
const e = await res.json().catch(() => ({}));
|
||||
alert(e.error || 'Не удалось загрузить фото');
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Ошибка при загрузке фото');
|
||||
}
|
||||
}
|
||||
|
||||
async function setMainStudentPhoto(pid) {
|
||||
if (!profileDataId) return;
|
||||
try {
|
||||
const res = await fetch(`${API}/api/students/${profileDataId}/photos/${pid}/main`, { method: 'PUT', headers: hdr() });
|
||||
if (res.ok) {
|
||||
const r = await res.json();
|
||||
document.getElementById('pfPhoto').value = r.photo_path || '';
|
||||
renderStudentPhotos();
|
||||
showToast('Главное фото обновлено');
|
||||
} else {
|
||||
const e = await res.json().catch(() => ({}));
|
||||
alert(e.error || 'Не удалось выбрать главное фото');
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Ошибка при выборе главного фото');
|
||||
}
|
||||
}
|
||||
|
||||
async function delStudentPhoto(pid) {
|
||||
if (!profileDataId) return;
|
||||
if (!confirm('Удалить это фото?')) return;
|
||||
try {
|
||||
const res = await fetch(`${API}/api/students/${profileDataId}/photos/${pid}`, { method: 'DELETE', headers: hdr() });
|
||||
if (res.ok) {
|
||||
await loadStudentPhotos(profileDataId);
|
||||
showToast('Фото удалено');
|
||||
} else {
|
||||
const e = await res.json().catch(() => ({}));
|
||||
alert(e.error || 'Не удалось удалить фото');
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Ошибка при удалении фото');
|
||||
}
|
||||
}
|
||||
|
||||
function fillProfileForm(profile, name, photoPath) {
|
||||
const p = profile || {};
|
||||
const set = (id, v) => { document.getElementById(id).value = v === null || v === undefined ? '' : String(v); };
|
||||
@@ -449,6 +539,34 @@ document.getElementById('studentList').addEventListener('click', e => {
|
||||
|
||||
document.getElementById('profileDataForm').addEventListener('submit', saveProfileData);
|
||||
document.getElementById('profileDataCancelBtn').addEventListener('click', closeProfileData);
|
||||
document.getElementById('pfPhotoUploadBtn').addEventListener('click', () => document.getElementById('pfPhotoFile').click());
|
||||
document.getElementById('pfPhotoFile').addEventListener('change', e => {
|
||||
const f = e.target.files && e.target.files[0];
|
||||
e.target.value = '';
|
||||
if (f) uploadStudentPhoto(f);
|
||||
});
|
||||
document.getElementById('pfPhotos').addEventListener('click', e => {
|
||||
const del = e.target.closest('[data-pf-del]');
|
||||
if (del) { delStudentPhoto(parseInt(del.dataset.pfDel, 10)); return; }
|
||||
const set = e.target.closest('[data-pf-set]');
|
||||
if (set) setMainStudentPhoto(parseInt(set.dataset.pfSet, 10));
|
||||
});
|
||||
document.addEventListener('paste', e => {
|
||||
if (!profileDataId) return;
|
||||
if (!document.getElementById('profileDataModal').classList.contains('open')) return;
|
||||
const items = e.clipboardData && e.clipboardData.items;
|
||||
if (!items) return;
|
||||
for (const it of items) {
|
||||
if (it.type && it.type.startsWith('image/')) {
|
||||
const f = it.getAsFile();
|
||||
if (f) {
|
||||
e.preventDefault();
|
||||
uploadStudentPhoto(f);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
document.getElementById('profileDataModal').addEventListener('click', e => { if (e.target === e.currentTarget) closeProfileData(); });
|
||||
|
||||
(async () => {
|
||||
|
||||
Reference in New Issue
Block a user