автоподстановка homepage

This commit is contained in:
Archibald 2025-06-09 21:45:25 +05:00
parent 7e6320aa9e
commit e6cd2049e0
4 changed files with 93 additions and 35 deletions

View File

@ -4,7 +4,7 @@ from typing import Optional, Any, Coroutine
import aiofiles import aiofiles
from fastapi import HTTPException, status, UploadFile from fastapi import HTTPException, status, UploadFile
from magic import magic from magic import Magic
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from starlette.responses import FileResponse from starlette.responses import FileResponse
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
@ -152,7 +152,7 @@ class TeamsService:
@staticmethod @staticmethod
def validate_file_type(file: UploadFile): def validate_file_type(file: UploadFile):
mime = magic.Magic(mime=True) mime = Magic(mime=True)
file_type = mime.from_buffer(file.file.read(1024)) file_type = mime.from_buffer(file.file.read(1024))
file.file.seek(0) file.file.seek(0)

View File

@ -4,7 +4,7 @@ import CONFIG from '@/core/config.js';
const downloadProjectFile = async (fileId) => { const downloadProjectFile = async (fileId) => {
try { try {
const response = await axios.get( const response = await axios.get(
`${CONFIG.BASE_URL}/project_files/${fileId}/download`, `${CONFIG.BASE_URL}/project_files/${fileId}/download/`,
{ {
responseType: 'blob', responseType: 'blob',
withCredentials: true, withCredentials: true,

View File

@ -9,7 +9,7 @@ const uploadProjectFile = async (projectId, file) => {
formData.append("file", file); formData.append("file", file);
const response = await axios.post( const response = await axios.post(
`${CONFIG.BASE_URL}/project_files/projects/${projectId}/upload`, `${CONFIG.BASE_URL}/project_files/projects/${projectId}/upload/`,
formData, formData,
{ {
headers: { headers: {
@ -36,4 +36,4 @@ const uploadProjectFile = async (projectId, file) => {
} }
}; };
export default uploadProjectFile; export default uploadProjectFile;

View File

@ -2,13 +2,13 @@
<q-page class="home-page bg-violet-strong q-pa-md"> <q-page class="home-page bg-violet-strong q-pa-md">
<div class="flex justify-center q-mb-md"> <div class="flex justify-center q-mb-md">
<q-avatar size="140px" class="team-logo shadow-12"> <q-avatar v-if="teamLogo" size="140px" class="team-logo shadow-12">
<img :src="teamLogo" alt="Логотип команды"/> <img :src="teamLogo" alt="Логотип команды"/>
</q-avatar> </q-avatar>
</div> </div>
<div class="flex justify-center q-mb-xl"> <div class="flex justify-center q-mb-xl">
<q-card class="team-name-card"> <q-card v-if="teamName" class="team-name-card">
<q-card-section class="text-h4 text-center text-indigo-10 q-pa-md"> <q-card-section class="text-h4 text-center text-indigo-10 q-pa-md">
{{ teamName }} {{ teamName }}
</q-card-section> </q-card-section>
@ -122,13 +122,16 @@
</template> </template>
<script setup> <script setup>
import {ref, computed, onMounted} from 'vue' import { ref, computed, onMounted } from 'vue'
import {useRouter} from 'vue-router' import { useRouter } from 'vue-router'
import {Ripple, Notify} from 'quasar' import { Ripple, Notify } from 'quasar'
import axios from "axios"; import axios from 'axios'
import CONFIG from "@/core/config.js"; import CONFIG from '@/core/config.js'
import fetchTeams from '@/api/teams/getTeams.js'
import fetchProfiles from '@/api/profiles/getProfiles.js'
import fetchContests from '@/api/contests/getContests.js'
defineExpose({directives: {ripple: Ripple}}) defineExpose({ directives: { ripple: Ripple } })
const router = useRouter() const router = useRouter()
@ -151,32 +154,21 @@ const handleAuthAction = () => {
} }
// --- Данные команды --- // --- Данные команды ---
const teamLogo = ref('https://cdn.quasar.dev/logo-v2/svg/logo.svg') const teamLogo = ref('')
const teamName = ref('Digital Dream Team') const teamName = ref('')
// --- Участники --- // --- Участники ---
const members = ref([ const members = ref([])
{id: 1, name: 'Иван Иванов', role: 'Team Lead', avatar: 'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?q=80&w=1974&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D'},
{id: 2, name: 'Мария Петрова', role: 'Frontend', avatar: 'https://randomuser.me/api/portraits/women/44.jpg'},
{id: 3, name: 'Алексей Смирнов', role: 'Backend', avatar: 'https://randomuser.me/api/portraits/men/65.jpg'},
{id: 4, name: 'Анна Кузнецова', role: 'Designer', avatar: 'https://randomuser.me/api/portraits/women/56.jpg'},
{id: 5, name: 'Дмитрий Орлов', role: 'QA', avatar: 'https://randomuser.me/api/portraits/men/78.jpg'},
])
// --- Конкурсы --- // --- Конкурсы ---
const contests = ref([ const contests = ref([])
{id: 1, title: 'Hackathon 2024', description: 'Ежегодный хакатон для стартапов'},
{id: 2, title: 'CodeFest', description: 'Соревнование по программированию'},
{id: 3, title: 'UI/UX Challenge', description: 'Конкурс дизайна интерфейсов'},
{id: 4, title: 'AI Innovation', description: 'Инициатива в области искусственного интеллекта'},
])
// --- Активность --- // --- Активность ---
const activityData = ref([]); const activityData = ref([]);
const dayHeight = 14; const dayHeight = 14;
const squareSize = ref(12); const squareSize = ref(12);
// Подписи месяцев (с июня 2024 по май 2025, чтобы соответствовать текущему году) // Подписи месяцев (с июня 2024 по май 2025)
const monthLabels = ['июн.', 'июл.', 'авг.', 'сент.', 'окт.', 'нояб.', 'дек.', 'янв.', 'февр.', 'март', 'апр.', 'май']; const monthLabels = ['июн.', 'июл.', 'авг.', 'сент.', 'окт.', 'нояб.', 'дек.', 'янв.', 'февр.', 'март', 'апр.', 'май'];
// Дни недели (пн, ср, пт, как в Gitea) // Дни недели (пн, ср, пт, как в Gitea)
@ -186,7 +178,9 @@ const weekDays = ['пн', 'ср', 'пт'];
const activityGrid = computed(() => { const activityGrid = computed(() => {
const weeks = []; const weeks = [];
let week = []; let week = [];
const firstDay = new Date(); const firstDay
= new Date();
firstDay.setDate(firstDay.getDate() - 364); // Год назад от текущей даты firstDay.setDate(firstDay.getDate() - 364); // Год назад от текущей даты
const dayOfWeek = firstDay.getDay(); const dayOfWeek = firstDay.getDay();
const offset = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Смещение для выравнивания по понедельнику const offset = dayOfWeek === 0 ? 6 : dayOfWeek - 1; // Смещение для выравнивания по понедельнику
@ -228,7 +222,71 @@ function getMonthMargin(idx) {
return weekIndex * (squareSize.value + 4); // 4 = margin (2px + 2px) return weekIndex * (squareSize.value + 4); // 4 = margin (2px + 2px)
} }
// Загрузка активности из API // Загрузка данных команды
async function loadTeamData() {
try {
const teams = await fetchTeams();
const activeTeam = teams.find(team => team.is_active === true);
if (activeTeam) {
teamName.value = activeTeam.name;
teamLogo.value = activeTeam.logo;
} else {
Notify.create({
type: 'warning',
message: 'Активная команда не найдена',
icon: 'warning',
});
}
} catch (error) {
console.error('Ошибка загрузки данных команды:', error);
Notify.create({
type: 'negative',
message: 'Ошибка загрузки данных команды',
icon: 'error',
});
}
}
// Загрузка участников
async function loadMembers() {
try {
const profiles = await fetchProfiles();
members.value = profiles.map(profile => ({
id: profile.id,
name: profile.name || 'Без имени',
role: profile.role || 'Участник',
avatar: profile.avatar || 'https://randomuser.me/api/portraits/men/1.jpg',
}));
} catch (error) {
console.error('Ошибка загрузки участников:', error);
Notify.create({
type: 'negative',
message: error.message || 'Ошибка загрузки участников',
icon: 'error',
});
}
}
// Загрузка конкурсов
async function loadContests() {
try {
const fetchedContests = await fetchContests();
contests.value = fetchedContests.map(contest => ({
id: contest.id,
title: contest.title || 'Без названия',
description: contest.description || 'Описание отсутствует',
}));
} catch (error) {
console.error('Ошибка загрузки конкурсов:', error);
Notify.create({
type: 'negative',
message: error.message || 'Ошибка загрузки конкурсов',
icon: 'error',
});
}
}
// Загрузка активности
const username = 'archibald'; const username = 'archibald';
async function loadActivity() { async function loadActivity() {
@ -249,7 +307,7 @@ async function loadActivity() {
date.setDate(startDate.getDate() + i); date.setDate(startDate.getDate() + i);
const dateStr = date.toISOString().slice(0, 10); const dateStr = date.toISOString().slice(0, 10);
const count = dataMap.get(dateStr) || 0; const count = dataMap.get(dateStr) || 0;
activityData.value.push({date: dateStr, count}); activityData.value.push({ date: dateStr, count });
} }
} catch (error) { } catch (error) {
console.error('Ошибка загрузки активности:', error); console.error('Ошибка загрузки активности:', error);
@ -268,13 +326,13 @@ async function loadActivity() {
const date = new Date(startDate); const date = new Date(startDate);
date.setDate(startDate.getDate() + i); date.setDate(startDate.getDate() + i);
const dateStr = date.toISOString().slice(0, 10); const dateStr = date.toISOString().slice(0, 10);
activityData.value.push({date: dateStr, count: 0}); activityData.value.push({ date: dateStr, count: 0 });
} }
} }
} }
onMounted(() => { onMounted(async () => {
loadActivity(); await Promise.all([loadTeamData(), loadMembers(), loadContests(), loadActivity()]);
}); });
// Масштабирование // Масштабирование