390 lines
13 KiB
Vue
390 lines
13 KiB
Vue
<template>
|
|
<q-layout view="lHh Lpr lFf" class="bg-violet-strong text-dark">
|
|
<q-header elevated class="bg-transparent">
|
|
<q-tabs
|
|
v-model="tab"
|
|
class="text-white"
|
|
active-color="white"
|
|
indicator-color="white"
|
|
align="justify"
|
|
dense
|
|
animated
|
|
@update:model-value="loadData"
|
|
>
|
|
<q-tab name="users" label="Пользователи" />
|
|
<q-tab name="teams" label="Команды" />
|
|
<q-tab name="projects" label="Проекты" />
|
|
<q-tab name="contests" label="Конкурсы" />
|
|
</q-tabs>
|
|
</q-header>
|
|
|
|
<q-page-container class="q-pa-md">
|
|
<q-tab-panels v-model="tab" animated transition-prev="slide-right" transition-next="slide-left">
|
|
|
|
<!-- Пользователи -->
|
|
<q-tab-panel name="users">
|
|
<div class="violet-card q-pa-md">
|
|
<div class="q-gutter-sm q-mb-sm row items-center justify-between">
|
|
<q-btn
|
|
label="Создание пользователя"
|
|
color="primary"
|
|
@click="createHandler"
|
|
/>
|
|
</div>
|
|
<q-table
|
|
title="Пользователи"
|
|
:rows="users"
|
|
:columns="userColumns"
|
|
row-key="id"
|
|
@row-click="onRowClick"
|
|
:loading="loadingUsers"
|
|
dense
|
|
flat
|
|
/>
|
|
</div>
|
|
</q-tab-panel>
|
|
|
|
<!-- Команды -->
|
|
<q-tab-panel name="teams">
|
|
<div class="violet-card q-pa-md">
|
|
<div class="q-gutter-sm q-mb-sm row items-center justify-between">
|
|
<q-btn
|
|
label="Создание команды"
|
|
color="primary"
|
|
@click="createHandler"
|
|
/>
|
|
</div>
|
|
<q-table
|
|
title="Команды"
|
|
:rows="teams"
|
|
:columns="teamColumns"
|
|
row-key="id"
|
|
@row-click="onRowClick"
|
|
:loading="loadingTeams"
|
|
dense
|
|
flat
|
|
/>
|
|
</div>
|
|
</q-tab-panel>
|
|
|
|
|
|
<!-- Проекты -->
|
|
<q-tab-panel name="projects">
|
|
<div class="violet-card q-pa-md">
|
|
<div class="q-gutter-sm q-mb-sm row items-center justify-between">
|
|
<q-btn
|
|
label="Создание проекта"
|
|
color="primary"
|
|
@click="createHandler"
|
|
/>
|
|
</div>
|
|
<q-table
|
|
title="Проекты"
|
|
:rows="projects"
|
|
:columns="projectColumns"
|
|
row-key="id"
|
|
@row-click="onRowClick"
|
|
:loading="loadingProjects"
|
|
dense
|
|
flat
|
|
/>
|
|
</div>
|
|
</q-tab-panel>
|
|
|
|
<!-- Конкурсы -->
|
|
<q-tab-panel name="contests">
|
|
<div class="violet-card q-pa-md">
|
|
<div class="q-gutter-sm q-mb-sm row items-center justify-between">
|
|
<q-btn
|
|
label="Создание конкурса"
|
|
color="primary"
|
|
@click="createHandler"
|
|
/>
|
|
</div>
|
|
<q-table
|
|
title="Конкурсы"
|
|
:rows="contests"
|
|
:columns="contestColumns"
|
|
row-key="id"
|
|
@row-click="onRowClick"
|
|
:loading="loadingContests"
|
|
dense
|
|
flat
|
|
/>
|
|
</div>
|
|
</q-tab-panel>
|
|
|
|
</q-tab-panels>
|
|
</q-page-container>
|
|
|
|
<!-- Модальное окно редактирования -->
|
|
<q-dialog v-model="dialogVisible" persistent>
|
|
<q-card style="min-width: 350px; max-width: 700px;">
|
|
<q-card-section>
|
|
<div class="text-h6">
|
|
<template v-if="dialogType === 'teams'">
|
|
Редактирование команды {{ dialogData.title || '' }}
|
|
</template>
|
|
<template v-else-if="dialogType === 'users'">
|
|
Редактирование пользователя {{ dialogData.name || dialogData.login || '' }}
|
|
</template>
|
|
<template v-else-if="dialogType === 'projects'">
|
|
Редактирование проекта {{ dialogData.title || '' }}
|
|
</template>
|
|
<template v-else-if="dialogType === 'contests'">
|
|
Редактирование конкурса {{ dialogData.title || '' }}
|
|
</template>
|
|
<template v-else>
|
|
Редактирование {{ dialogType }}
|
|
</template>
|
|
</div>
|
|
</q-card-section>
|
|
|
|
<q-separator />
|
|
|
|
<q-card-section>
|
|
<!-- Teams -->
|
|
<template v-if="dialogType === 'teams'">
|
|
<q-input v-model="dialogData.title" label="Название команды" dense autofocus clearable />
|
|
<q-input v-model="dialogData.description" label="Описание" dense clearable type="textarea" class="q-mt-sm" />
|
|
<q-input v-model="dialogData.logo" label="Логотип" dense clearable class="q-mt-sm" />
|
|
<q-input v-model="dialogData.git_url" label="Git URL" dense clearable class="q-mt-sm" />
|
|
</template>
|
|
|
|
<!-- Users -->
|
|
<template v-else-if="dialogType === 'users'">
|
|
<q-input v-model="dialogData.login" label="Логин" dense autofocus clearable />
|
|
<q-input v-model="dialogData.name" label="Имя" dense clearable class="q-mt-sm" />
|
|
<q-input v-model="dialogData.email" label="Email" dense clearable class="q-mt-sm" />
|
|
<!-- Добавь другие поля, которые нужны для пользователя -->
|
|
</template>
|
|
|
|
<!-- Projects -->
|
|
<template v-else-if="dialogType === 'projects'">
|
|
<q-input v-model="dialogData.title" label="Название проекта" dense autofocus clearable />
|
|
<q-input v-model="dialogData.description" label="Описание" dense clearable type="textarea" class="q-mt-sm" />
|
|
<q-input v-model="dialogData.repo_url" label="URL репозитория" dense clearable class="q-mt-sm" />
|
|
<!-- Другие поля проекта -->
|
|
</template>
|
|
|
|
<!-- Contests -->
|
|
<template v-else-if="dialogType === 'contests'">
|
|
<q-input v-model="dialogData.title" label="Название конкурса" dense autofocus clearable />
|
|
<q-input v-model="dialogData.description" label="Описание" dense clearable type="textarea" class="q-mt-sm" />
|
|
<q-input v-model="dialogData.start_date" label="Дата начала" dense clearable class="q-mt-sm" type="date" />
|
|
<q-input v-model="dialogData.end_date" label="Дата окончания" dense clearable class="q-mt-sm" type="date" />
|
|
<!-- Другие поля конкурса -->
|
|
</template>
|
|
|
|
<template v-else>
|
|
<pre>{{ dialogData }}</pre>
|
|
</template>
|
|
</q-card-section>
|
|
|
|
<q-card-actions align="right">
|
|
<q-btn
|
|
v-if="dialogType === 'teams'"
|
|
flat
|
|
label="Удалить"
|
|
color="negative"
|
|
@click="deleteItem"
|
|
/>
|
|
<q-btn
|
|
v-if="dialogType === 'users'"
|
|
flat
|
|
label="Удалить"
|
|
color="negative"
|
|
@click="deleteItem"
|
|
/>
|
|
<q-btn
|
|
v-if="dialogType === 'projects'"
|
|
flat
|
|
label="Удалить"
|
|
color="negative"
|
|
@click="deleteItem"
|
|
/>
|
|
<q-btn
|
|
v-if="dialogType === 'contests'"
|
|
flat
|
|
label="Удалить"
|
|
color="negative"
|
|
@click="deleteItem"
|
|
/>
|
|
<q-space />
|
|
<q-btn flat label="Закрыть" color="primary" @click="closeDialog" />
|
|
<q-btn
|
|
v-if="['teams', 'users', 'projects', 'contests'].includes(dialogType)"
|
|
flat
|
|
label="Сохранить"
|
|
color="primary"
|
|
@click="saveChanges"
|
|
/>
|
|
</q-card-actions>
|
|
</q-card>
|
|
</q-dialog>
|
|
</q-layout>
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
import { ref, watch, onMounted, onBeforeUnmount } from 'vue'
|
|
|
|
import fetchTeams from '@/api/teams/getTeams.js'
|
|
import updateTeam from '@/api/teams/updateTeam.js'
|
|
import deleteTeamById from '@/api/teams/deleteTeam.js'
|
|
import createTeam from '@/api/teams/createTeam.js'
|
|
|
|
import fetchProjects from '@/api/projects/getProjects.js'
|
|
import updateProject from '@/api/projects/updateProject.js'
|
|
import deleteProjectById from '@/api/projects/deleteProject.js'
|
|
import createProject from '@/api/projects/createProject.js'
|
|
|
|
// Текущая вкладка — 'teams' или 'projects'
|
|
const tab = ref('teams')
|
|
|
|
// --- Teams ---
|
|
const teams = ref([])
|
|
const loadingTeams = ref(false)
|
|
const teamColumns = [
|
|
{ name: 'title', label: 'Название команды', field: 'title', sortable: true },
|
|
{ name: 'description', label: 'Описание', field: 'description', sortable: true },
|
|
{ name: 'logo', label: 'Логотип', field: 'logo', sortable: true },
|
|
{ name: 'git_url', label: 'Git URL', field: 'git_url', sortable: true },
|
|
]
|
|
|
|
// --- Projects ---
|
|
const projects = ref([])
|
|
const loadingProjects = ref(false)
|
|
const projectColumns = [
|
|
{ name: 'title', label: 'Название проекта', field: 'title', sortable: true },
|
|
{ name: 'description', label: 'Описание', field: 'description', sortable: true },
|
|
{ name: 'repository_url', label: 'Репозиторий', field: 'repository_url', sortable: true },
|
|
]
|
|
|
|
// Общие состояния для диалогов
|
|
const dialogVisible = ref(false)
|
|
const dialogData = ref({})
|
|
const dialogType = ref('') // 'teams' или 'projects'
|
|
|
|
function openEdit(type, row) {
|
|
dialogType.value = type
|
|
if (row) {
|
|
dialogData.value = JSON.parse(JSON.stringify(row))
|
|
} else {
|
|
if (type === 'teams') {
|
|
dialogData.value = { title: '', description: '', logo: '', git_url: '' }
|
|
} else if (type === 'projects') {
|
|
dialogData.value = { name: '', summary: '', deadline: '' }
|
|
}
|
|
}
|
|
dialogVisible.value = true
|
|
}
|
|
|
|
function onRowClick(event, row) {
|
|
openEdit(tab.value, row)
|
|
}
|
|
|
|
function closeDialog() {
|
|
dialogVisible.value = false
|
|
}
|
|
|
|
async function saveChanges() {
|
|
try {
|
|
if (dialogType.value === 'teams') {
|
|
if (dialogData.value.id) {
|
|
await updateTeam(dialogData.value)
|
|
const idx = teams.value.findIndex(t => t.id === dialogData.value.id)
|
|
if (idx !== -1) teams.value[idx] = JSON.parse(JSON.stringify(dialogData.value))
|
|
} else {
|
|
const newTeam = await createTeam(dialogData.value)
|
|
teams.value.push(newTeam)
|
|
}
|
|
} else if (dialogType.value === 'projects') {
|
|
if (dialogData.value.id) {
|
|
await updateProject(dialogData.value)
|
|
const idx = projects.value.findIndex(p => p.id === dialogData.value.id)
|
|
if (idx !== -1) projects.value[idx] = JSON.parse(JSON.stringify(dialogData.value))
|
|
} else {
|
|
const newProject = await createProject(dialogData.value)
|
|
projects.value.push(newProject)
|
|
}
|
|
}
|
|
closeDialog()
|
|
} catch (error) {
|
|
console.error('Ошибка при сохранении:', error.message)
|
|
}
|
|
}
|
|
|
|
async function loadData(name) {
|
|
if (name === 'teams') {
|
|
loadingTeams.value = true
|
|
try {
|
|
teams.value = await fetchTeams() || []
|
|
} catch (error) {
|
|
teams.value = []
|
|
console.error(error.message)
|
|
} finally {
|
|
loadingTeams.value = false
|
|
}
|
|
} else if (name === 'projects') {
|
|
loadingProjects.value = true
|
|
try {
|
|
projects.value = await fetchProjects() || []
|
|
} catch (error) {
|
|
projects.value = []
|
|
console.error(error.message)
|
|
} finally {
|
|
loadingProjects.value = false
|
|
}
|
|
}
|
|
}
|
|
|
|
async function deleteItem() {
|
|
if (!dialogData.value.id) return
|
|
try {
|
|
if (dialogType.value === 'teams') {
|
|
await deleteTeamById(dialogData.value.id)
|
|
teams.value = teams.value.filter(t => t.id !== dialogData.value.id)
|
|
} else if (dialogType.value === 'projects') {
|
|
await deleteProjectById(dialogData.value.id)
|
|
projects.value = projects.value.filter(p => p.id !== dialogData.value.id)
|
|
}
|
|
closeDialog()
|
|
} catch (error) {
|
|
console.error('Ошибка при удалении:', error.message)
|
|
}
|
|
}
|
|
|
|
function createHandler() {
|
|
openEdit(tab.value, null)
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadData(tab.value)
|
|
|
|
const interval = setInterval(() => {
|
|
loadData(tab.value)
|
|
}, 5000)
|
|
|
|
onBeforeUnmount(() => {
|
|
clearInterval(interval)
|
|
})
|
|
})
|
|
|
|
watch(tab, (newTab) => {
|
|
loadData(newTab)
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.bg-violet-strong {
|
|
background: linear-gradient(135deg, #4f046f 0%, #7c3aed 100%);
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.violet-card {
|
|
border-radius: 22px;
|
|
background: #ede9fe;
|
|
box-shadow: 0 6px 32px rgba(124, 58, 237, 0.13), 0 1.5px 6px rgba(124, 58, 237, 0.10);
|
|
}
|
|
</style> |