diff --git a/WEB/src/api/projects/project_files/downloadProjectFile.js b/WEB/src/api/projects/project_files/downloadProjectFile.js
index 11b1347..8a5ea58 100644
--- a/WEB/src/api/projects/project_files/downloadProjectFile.js
+++ b/WEB/src/api/projects/project_files/downloadProjectFile.js
@@ -3,19 +3,14 @@ import CONFIG from '@/core/config.js';
const downloadProjectFile = async (fileId) => {
try {
- const token = localStorage.getItem('access_token');
const response = await axios.get(
`${CONFIG.BASE_URL}/project_files/${fileId}/download`,
{
- headers: {
- Authorization: `Bearer ${token}`,
- },
responseType: 'blob',
withCredentials: true,
}
);
- // Создаем ссылку для скачивания
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
@@ -34,17 +29,15 @@ const downloadProjectFile = async (fileId) => {
link.remove();
window.URL.revokeObjectURL(url);
- return { success: true, filename: filename };
+ return filename;
} catch (error) {
- const errorMessage = error.response?.data?.detail || error.message;
- console.error(`Ошибка скачивания файла проекта с ID ${fileId}:`, errorMessage);
if (error.response?.status === 401) {
throw new Error("Недостаточно прав для скачивания файла (401)");
}
if (error.response?.status === 404) {
throw new Error("Файл не найден (404)");
}
- throw new Error(errorMessage);
+ throw new Error(error.message);
}
};
diff --git a/WEB/src/pages/AdminPage.vue b/WEB/src/pages/AdminPage.vue
index 3e3b08b..b4e3209 100644
--- a/WEB/src/pages/AdminPage.vue
+++ b/WEB/src/pages/AdminPage.vue
@@ -215,6 +215,68 @@
+
+
+
Файлы проекта
+
+
+
+ Пока нет файлов.
+
+
+
+
+ {{ fileItem.filename }}
+ {{ fileItem.file_path }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -439,24 +501,22 @@ import uploadContestCarouselPhoto from '@/api/contests/contest_carousel_photos/u
import deleteContestCarouselPhoto from '@/api/contests/contest_carousel_photos/deleteContestPhoto.js'
import downloadContestCarouselPhotoFile from '@/api/contests/contest_carousel_photos/downloadContestPhotoFile.js'
-// --- Imports for Contest Files ---
import getContestFiles from '@/api/contests/contest_files/getContestFiles.js'
import uploadContestFile from '@/api/contests/contest_files/uploadContestFile.js'
import deleteContestFile from '@/api/contests/contest_files/deleteContestFile.js'
import downloadContestFile from '@/api/contests/contest_files/downloadContestFile.js'
-// --- Imports for Project Files ---
import getProjectFiles from '@/api/projects/project_files/getProjectFiles.js'
import uploadProjectFile from '@/api/projects/project_files/uploadProjectFile.js'
import deleteProjectFile from '@/api/projects/project_files/deleteProjectFile.js'
import downloadProjectFile from '@/api/projects/project_files/downloadProjectFile.js'
+import router from "@/router/index.js";
const $q = useQuasar()
const tab = ref('profiles')
-// --- Profiles ---
const profiles = ref([])
const loadingProfiles = ref(false)
const profileColumns = [
@@ -470,7 +530,6 @@ const profileColumns = [
{ name: 'team_id', label: 'Команда', field: 'team_id', sortable: true },
]
-// --- Teams ---
const teams = ref([])
const loadingTeams = ref(false)
const teamColumns = [
@@ -480,7 +539,6 @@ const teamColumns = [
{ name: 'git_url', label: 'Git URL', field: 'git_url', sortable: true },
]
-// --- Projects ---
const projects = ref([])
const loadingProjects = ref(false)
const projectColumns = [
@@ -489,7 +547,6 @@ const projectColumns = [
{ name: 'repository_url', label: 'Репозиторий', field: 'repository_url', sortable: true },
]
-// --- Contests ---
const contests = ref([])
const loadingContests = ref(false)
const contestColumns = [
@@ -503,42 +560,38 @@ const contestColumns = [
{ name: 'status_id', label: 'Статус', field: 'status_id', sortable: true },
]
-// Общие состояния для диалогов
const dialogVisible = ref(false)
const dialogData = ref({})
const dialogType = ref('')
-// --- Состояния для фотографий профиля ---
const profilePhotos = ref([])
const loadingProfilePhotos = ref(false)
const newProfilePhotoFile = ref(null)
-const uploadingPhoto = ref(false) // Общее состояние для загрузки фото
+const uploadingPhoto = ref(false)
-// --- Состояния для фотографий карусели конкурса ---
const contestPhotos = ref([])
const loadingContestPhotos = ref(false)
const newContestPhotoFile = ref(null)
-// --- Состояния для файлов конкурса ---
const contestFiles = ref([])
const loadingContestFiles = ref(false)
const newContestFile = ref(null)
-const uploadingFile = ref(false) // Отдельное состояние для загрузки файлов
+const uploadingFile = ref(false)
+
+const projectFiles = ref([])
+const loadingProjectFiles = ref(false)
+const newProjectFile = ref(null)
-// Функция для получения URL фото
const getPhotoUrl = (photoId, type) => {
if (type === 'profile') {
- // Используем шаблонные строки JavaScript для корректного формирования URL
return `${CONFIG.BASE_URL}/profile_photos/${photoId}/file`;
} else if (type === 'contest') {
- // Используем шаблонные строки JavaScript для корректного формирования URL
return `${CONFIG.BASE_URL}/contest_carousel_photos/${photoId}/file`;
}
return '';
}
-// Загрузка фотографий профиля при открытии диалога для профиля
async function loadProfilePhotos(profileId) {
loadingProfilePhotos.value = true;
try {
@@ -555,7 +608,6 @@ async function loadProfilePhotos(profileId) {
}
}
-// Загрузка фотографий карусели конкурса
async function loadContestPhotos(contestId) {
loadingContestPhotos.value = true;
try {
@@ -572,7 +624,6 @@ async function loadContestPhotos(contestId) {
}
}
-// Загрузка файлов конкурса
async function loadContestFiles(contestId) {
loadingContestFiles.value = true;
try {
@@ -589,23 +640,39 @@ async function loadContestFiles(contestId) {
}
}
-// Обработчик выбора нового файла для профиля
+async function loadProjectFiles(projectId) {
+ loadingProjectFiles.value = true;
+ try {
+ projectFiles.value = await getProjectFiles(projectId);
+ } catch (error) {
+ Notify.create({
+ type: 'negative',
+ message: `Ошибка загрузки файлов проекта: ${error.message}`,
+ icon: 'error',
+ });
+ projectFiles.value = [];
+ } finally {
+ loadingProjectFiles.value = false;
+ }
+}
+
function handleNewPhotoSelected(file) {
newProfilePhotoFile.value = file;
}
-// Обработчик выбора нового файла для конкурса (фото)
function handleNewContestPhotoSelected(file) {
newContestPhotoFile.value = file;
}
-// Обработчик выбора нового файла для конкурса (обычный файл)
function handleNewContestFileSelected(file) {
newContestFile.value = file;
}
+function handleNewProjectFileSelected(file) {
+ newProjectFile.value = file;
+}
+
-// Загрузка новой фотографии профиля
async function uploadNewProfilePhoto() {
if (!newProfilePhotoFile.value || !dialogData.value.id) {
Notify.create({
@@ -637,7 +704,6 @@ async function uploadNewProfilePhoto() {
}
}
-// Загрузка новой фотографии карусели конкурса
async function uploadNewContestPhoto() {
if (!newContestPhotoFile.value || !dialogData.value.id) {
Notify.create({
@@ -669,7 +735,6 @@ async function uploadNewContestPhoto() {
}
}
-// Загрузка нового файла конкурса
async function uploadNewContestFile() {
if (!newContestFile.value || !dialogData.value.id) {
Notify.create({
@@ -680,7 +745,7 @@ async function uploadNewContestFile() {
return;
}
- uploadingFile.value = true; // Use separate loading state
+ uploadingFile.value = true;
try {
const uploadedFile = await uploadContestFile(dialogData.value.id, newContestFile.value);
contestFiles.value.push(uploadedFile);
@@ -701,8 +766,38 @@ async function uploadNewContestFile() {
}
}
+async function uploadNewProjectFile() {
+ if (!newProjectFile.value || !dialogData.value.id) {
+ Notify.create({
+ type: 'warning',
+ message: 'Выберите файл и убедитесь, что проект выбран.',
+ icon: 'warning',
+ });
+ return;
+ }
+
+ uploadingFile.value = true;
+ try {
+ const uploadedFile = await uploadProjectFile(dialogData.value.id, newProjectFile.value);
+ projectFiles.value.push(uploadedFile);
+ newProjectFile.value = null;
+ Notify.create({
+ type: 'positive',
+ message: 'Файл проекта успешно загружен!',
+ icon: 'check_circle',
+ });
+ } catch (error) {
+ Notify.create({
+ type: 'negative',
+ message: `Ошибка загрузки файла проекта: ${error.message}`,
+ icon: 'error',
+ });
+ } finally {
+ uploadingFile.value = false;
+ }
+}
+
-// Подтверждение и удаление фотографии
function confirmDeletePhoto(photoId, type) {
$q.dialog({
title: 'Подтверждение удаления',
@@ -750,7 +845,6 @@ async function deleteExistingPhoto(photoId, type) {
}
}
-// Подтверждение и удаление файла конкурса
function confirmDeleteContestFile(fileId) {
$q.dialog({
title: 'Подтверждение удаления',
@@ -788,7 +882,6 @@ async function deleteExistingContestFile(fileId) {
}
}
-// Скачивание файла конкурса
async function downloadExistingContestFile(fileId) {
try {
await downloadContestFile(fileId);
@@ -806,6 +899,60 @@ async function downloadExistingContestFile(fileId) {
}
}
+function confirmDeleteProjectFile(fileId) {
+ $q.dialog({
+ title: 'Подтверждение удаления',
+ message: 'Вы уверены, что хотите удалить этот файл?',
+ cancel: true,
+ persistent: true,
+ ok: {
+ label: 'Удалить',
+ color: 'negative'
+ },
+ cancel: {
+ label: 'Отмена',
+ color: 'primary'
+ }
+ }).onOk(async () => {
+ await deleteExistingProjectFile(fileId);
+ });
+}
+
+async function deleteExistingProjectFile(fileId) {
+ try {
+ await deleteProjectFile(fileId);
+ projectFiles.value = projectFiles.value.filter(f => f.id !== fileId);
+ Notify.create({
+ type: 'positive',
+ message: 'Файл проекта успешно удален!',
+ icon: 'check_circle',
+ });
+ } catch (error) {
+ Notify.create({
+ type: 'negative',
+ message: `Ошибка удаления файла проекта: ${error.message}`,
+ icon: 'error',
+ });
+ }
+}
+
+async function downloadExistingProjectFile(fileId) {
+ try {
+ await downloadProjectFile(fileId);
+ Notify.create({
+ type: 'positive',
+ message: 'Файл проекта успешно скачан!',
+ icon: 'check_circle',
+ });
+ } catch (error) {
+ Notify.create({
+ type: 'negative',
+ message: `Ошибка скачивания файла проекта: ${error.message}`,
+ icon: 'error',
+ });
+ }
+}
+
function openEdit(type, row) {
dialogType.value = type
@@ -816,13 +963,14 @@ function openEdit(type, row) {
dialogData.value = { title: '', description: '', logo: '', git_url: '' }
} else if (type === 'projects') {
dialogData.value = { title: '', description: '', repository_url: '' }
+ projectFiles.value = [];
} else if (type === 'profiles') {
dialogData.value = { first_name: '', last_name: '', patronymic: '', birthday: '', email: '', phone: '', role_id: null, team_id: null }
profilePhotos.value = [];
} else if (type === 'contests') {
dialogData.value = { title: '', description: '', web_url: '', photo: '', results: '', is_win: false, project_id: null, status_id: null }
contestPhotos.value = [];
- contestFiles.value = []; // Clear contest files when opening dialog
+ contestFiles.value = [];
}
}
dialogVisible.value = true
@@ -831,11 +979,14 @@ function openEdit(type, row) {
loadProfilePhotos(dialogData.value.id);
} else if (type === 'contests' && dialogData.value.id) {
loadContestPhotos(dialogData.value.id);
- loadContestFiles(dialogData.value.id); // Load contest files
+ loadContestFiles(dialogData.value.id);
+ } else if (type === 'projects' && dialogData.value.id) {
+ loadProjectFiles(dialogData.value.id);
} else {
profilePhotos.value = [];
contestPhotos.value = [];
- contestFiles.value = []; // Clear contest files
+ contestFiles.value = [];
+ projectFiles.value = [];
}
}
@@ -849,10 +1000,12 @@ function closeDialog() {
newProfilePhotoFile.value = null;
contestPhotos.value = [];
newContestPhotoFile.value = null;
- contestFiles.value = []; // Clear contest files
- newContestFile.value = null; // Clear new contest file input
+ contestFiles.value = [];
+ newContestFile.value = null;
+ projectFiles.value = [];
+ newProjectFile.value = null;
uploadingPhoto.value = false;
- uploadingFile.value = false; // Reset file uploading state
+ uploadingFile.value = false;
}
async function saveChanges() {
@@ -1037,6 +1190,23 @@ onMounted(() => {
watch(tab, (newTab) => {
loadData(newTab)
})
+
+const handleAuthAction = () => {
+ const isAuthenticated = ref(!!localStorage.getItem('access_token'))
+ if (isAuthenticated.value) {
+ localStorage.removeItem('access_token')
+ localStorage.removeItem('user_id')
+ isAuthenticated.value = false
+
+
+ Notify.create({
+ type: 'positive',
+ message: 'Выход успешно осуществлен',
+ icon: 'check_circle',
+ })
+ router.push('/')
+ }
+}