add modal
This commit is contained in:
parent
f37d1151f1
commit
282e6444e9
@ -1,13 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import router from '@/router';
|
||||
import type { Game, Team, Teams } from './models';
|
||||
import type { Application, Game, Team, Teams } from './models';
|
||||
import { apiGetTeams, apiAddTeam, apiGetGame, apiStartGame, apiStopGame, apiGaveApplication, apiDownloadQrCodesFile } from './client';
|
||||
import TeamQRCode from '../components/TeamQRCode.vue'
|
||||
import HeaderBlock from './HeaderBlock.vue';
|
||||
import ModalWindow from './ModalWindow.vue';
|
||||
|
||||
const qrurl = ref("-")
|
||||
const qrteam = ref("-")
|
||||
const isOpenModal = ref(false)
|
||||
|
||||
const gameState = ref("")
|
||||
const game = ref<Game | undefined>()
|
||||
@ -62,6 +64,37 @@ onMounted(async () => {
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
const gaveApplicationTeam = ref<Team>({
|
||||
id: 0,
|
||||
name: '',
|
||||
password: '',
|
||||
url: '',
|
||||
spendTime: 0,
|
||||
applications: []
|
||||
})
|
||||
|
||||
const gaveApplicationApplication = ref<Application>({
|
||||
id: 0,
|
||||
name: ''
|
||||
})
|
||||
|
||||
function gaveApplication(team: Team, application: Application) {
|
||||
gaveApplicationTeam.value = team
|
||||
gaveApplicationApplication.value = application
|
||||
isOpenModal.value = true
|
||||
}
|
||||
|
||||
async function confirm() {
|
||||
console.log("confirm")
|
||||
await apiGaveApplication(gaveApplicationTeam.value.id, gaveApplicationApplication.value.id)
|
||||
isOpenModal.value = false
|
||||
}
|
||||
|
||||
function close() {
|
||||
console.log("close")
|
||||
isOpenModal.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -85,13 +118,18 @@ onMounted(async () => {
|
||||
<div class="team-block" v-for="team in teams.teams" :key="team.name">
|
||||
<div class="team-content-block">
|
||||
<div class="team-name-block">
|
||||
<a v-on:click="qrurl = team.url, qrteam = team.name">QR</a>
|
||||
<a v-on:click="qrurl = team.url, qrteam = team.name">
|
||||
QR
|
||||
</a>
|
||||
<a :href="team.url" class="url-block" target="_blank">
|
||||
URL
|
||||
</a>
|
||||
{{ team.name }}
|
||||
</div>
|
||||
<div>Поездки: {{ team.spendTime }}</div>
|
||||
</div>
|
||||
<div v-for="application in team.applications" :key="application.id" class="link-button"
|
||||
@click="apiGaveApplication(team.id, application.id)">
|
||||
@click="gaveApplication(team, application)">
|
||||
Выдать: {{ application.name }}
|
||||
</div>
|
||||
</div>
|
||||
@ -106,6 +144,15 @@ onMounted(async () => {
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<ModalWindow :is-open="isOpenModal" @confirm="confirm" @close="close">
|
||||
<div>
|
||||
Команда: {{ gaveApplicationTeam.name }}
|
||||
</div>
|
||||
<div>
|
||||
Приложение: {{ gaveApplicationApplication.name }}
|
||||
</div>
|
||||
</ModalWindow>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@ -117,6 +164,10 @@ onMounted(async () => {
|
||||
margin: 5px 10px 5px 0;
|
||||
}
|
||||
|
||||
.url-block {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.link-button {
|
||||
/* Основные стили */
|
||||
border: none;
|
||||
|
||||
185
src/components/ModalWindow.vue
Normal file
185
src/components/ModalWindow.vue
Normal file
@ -0,0 +1,185 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
|
||||
// Пропсы (входящие данные)
|
||||
const props = defineProps({
|
||||
isOpen: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Внимание'
|
||||
}
|
||||
})
|
||||
|
||||
// Эмиты (события, отправляемые родителю)
|
||||
const emit = defineEmits(['close', 'confirm'])
|
||||
|
||||
const closeModal = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
// Функция СОХРАНЕНИЯ
|
||||
const confirmModal = () => {
|
||||
emit('confirm')
|
||||
}
|
||||
|
||||
// Закрытие по кнопке Esc
|
||||
const handleKeydown = (e: { key: string }) => {
|
||||
if (props.isOpen && e.key === 'Escape') {
|
||||
closeModal()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('keydown', handleKeydown)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('keydown', handleKeydown)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Teleport переносит модалку в конец тега <body> -->
|
||||
<Teleport to="body">
|
||||
<!-- Transition добавляет плавную анимацию -->
|
||||
<Transition name="modal">
|
||||
<div v-if="isOpen" class="modal-mask" @click="closeModal">
|
||||
<!-- @click.stop предотвращает закрытие при клике внутри самого окна -->
|
||||
<div class="modal-wrapper">
|
||||
<div class="modal-container" @click.stop>
|
||||
|
||||
<!-- Шапка (Header) -->
|
||||
<div class="modal-header">
|
||||
<slot name="header">
|
||||
<h3>{{ title }}</h3>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<!-- Тело (Body) -->
|
||||
<div class="modal-body">
|
||||
<slot>Дефолтный текст модального окна</slot>
|
||||
</div>
|
||||
|
||||
<!-- Подвал (Footer) -->
|
||||
<div class="modal-footer">
|
||||
<slot name="footer">
|
||||
<button class="btn-cancel" @click="closeModal">Отмена</button>
|
||||
<button class="btn-primary" @click="confirmModal">Выдано</button>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Затемнение фона */
|
||||
.modal-mask {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: table;
|
||||
transition: opacity 0.3s ease;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.modal-wrapper {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* Само окно */
|
||||
.modal-container {
|
||||
width: 400px;
|
||||
margin: 0px auto;
|
||||
padding: 20px 30px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 25px;
|
||||
color: var(--main-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
margin: 20px 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.btn-cancel,
|
||||
.btn-primary {
|
||||
padding: 12px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background-color: #fff;
|
||||
color: var(--main-color);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--main-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: var(--second-color);
|
||||
}
|
||||
|
||||
/* Анимации Vue <Transition> */
|
||||
.modal-enter-from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.modal-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.modal-enter-from .modal-container,
|
||||
.modal-leave-to .modal-container {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user