This commit is contained in:
2026-10-13 13:50:34 +07:00
parent d77638308e
commit 0fa88bca22
7 changed files with 303 additions and 15 deletions
+78
View File
@@ -50,6 +50,7 @@ import GameScreensPanel from '@/components/GameScreensPanel.vue'
import GameStatePanel from '@/components/GameStatePanel.vue'
import GameTeamsPanel from '@/components/GameTeamsPanel.vue'
import HeaderMenu from '@/components/HeaderMenu.vue'
import TeamReceiptModal from '@/components/TeamReceiptModal.vue'
import TicketCheckModal from '@/components/TicketCheckModal.vue'
import UiBlock from '@/components/UiBlock.vue'
import { useDeviceContext } from '@/composables/useDeviceContext'
@@ -108,6 +109,13 @@ const deletedGameName = ref('')
const showQR = ref(false)
const urlQR = ref('')
const titleQR = ref('')
const showTeamReceipt = ref(false)
const teamReceipt = ref({ name: '', teamID: '' })
const isPrintingReceipt = ref(false)
const isPreparingReceipt = ref(false)
const receiptPreview = ref('')
const receiptPreparationError = ref('')
let receiptPreparationVersion = 0
const showTicketCheckModal = ref(false)
const paymentURL =
'https://www.sberbank.ru/ru/choise_bank?requisiteNumber=79609361994&bankCode=100000000111'
@@ -281,6 +289,8 @@ onMounted(() => {
})
onUnmounted(() => {
isDisposed = true
receiptPreparationVersion++
clearReceiptPreview()
window.removeEventListener('click', enableNotificationSound)
window.removeEventListener('keydown', enableNotificationSound)
void notificationAudio?.close().catch((error: unknown) => {
@@ -892,6 +902,62 @@ function openTeamQR(team: Team) {
openQR(getTeamStoryURL(team.ID!, team.password!), 'Подсмотреть игру')
}
function openTeamReceipt(team: Team) {
if (
uiStore.environment !== 'venue' ||
!team.ID ||
!team.name ||
team.participationStatus === 'cancelled'
) {
message.error('У команды нет данных для печати чека')
return
}
teamReceipt.value = { name: team.name, teamID: team.ID }
showTeamReceipt.value = true
}
watch(
[showTeamReceipt, () => teamReceipt.value.teamID, canManageGame, () => uiStore.environment],
prepareTeamReceipt,
)
function clearReceiptPreview() {
if (receiptPreview.value) URL.revokeObjectURL(receiptPreview.value)
receiptPreview.value = ''
}
async function prepareTeamReceipt() {
const version = ++receiptPreparationVersion
clearReceiptPreview()
receiptPreparationError.value = ''
isPreparingReceipt.value = false
if (!showTeamReceipt.value || !canManageGame.value || uiStore.environment !== 'venue') return
isPreparingReceipt.value = true
try {
const { blob } = await binaryClient.downloadTeamReceipt(Number(gameId), teamReceipt.value.teamID)
if (version === receiptPreparationVersion) receiptPreview.value = URL.createObjectURL(blob)
} catch (error) {
if (version === receiptPreparationVersion)
receiptPreparationError.value =
error instanceof Error ? error.message : 'Не удалось подготовить чек'
} finally {
if (version === receiptPreparationVersion) isPreparingReceipt.value = false
}
}
async function printTeamReceipt() {
if (!receiptPreview.value || isPrintingReceipt.value || isPreparingReceipt.value) return
isPrintingReceipt.value = true
try {
await client.PrintTeamReceipt({ gameID: Number(gameId), teamID: teamReceipt.value.teamID })
message.success('Чек напечатан')
} catch (error) {
message.error(error instanceof Error ? error.message : 'Не удалось напечатать чек')
} finally {
isPrintingReceipt.value = false
}
}
async function openQR(url: string, text: string) {
urlQR.value = url
titleQR.value = text
@@ -1276,9 +1342,11 @@ useSimplePolling(() => {
</n-flex>
<GameTeamsPanel
:teams="game.teams || []"
:can-print-receipts="uiStore.environment === 'venue'"
:participation-options="participationOptions"
@add="showAddTeamModal = true"
@open-qr="openTeamQR"
@print="openTeamReceipt"
@preview="(id, password) => toTeamStory(id, password, true)"
@delete="deleteTeam"
@give-application="giveApplication"
@@ -1492,6 +1560,16 @@ useSimplePolling(() => {
</div>
<TicketCheckModal v-if="canManageGame" v-model:show="showTicketCheckModal" />
<TeamReceiptModal
v-if="canManageGame && uiStore.environment === 'venue'"
v-model:show="showTeamReceipt"
:name="teamReceipt.name"
:preview="receiptPreview"
:preparation-error="receiptPreparationError"
:is-preparing="isPreparingReceipt"
:is-printing="isPrintingReceipt"
@print="printTeamReceipt"
/>
<!-- Окно выдачи улик -->
<n-modal v-if="canManageGame" v-model:show="showGiveApplicationModal">
+8
View File
@@ -6,12 +6,14 @@ import UiBlock from '@/components/UiBlock.vue'
defineProps<{
teams: Team[]
canPrintReceipts: boolean
participationOptions: { label: string; value: string }[]
}>()
const emit = defineEmits<{
add: []
'open-qr': [team: Team]
print: [team: Team]
preview: [id: string, password: string]
delete: [team: Team]
'give-application': [team: Team, application: Application]
@@ -47,6 +49,12 @@ const emit = defineEmits<{
</div>
</div>
<n-flex justify="end">
<n-button
v-if="canPrintReceipts && team.participationStatus !== 'cancelled'"
@click="emit('print', team)"
>
Печать чека
</n-button>
<n-button v-if="team.participationStatus !== 'cancelled'" @click="emit('open-qr', team)"
>QR</n-button
>
+68
View File
@@ -0,0 +1,68 @@
<script setup lang="ts">
import { NAlert, NButton, NCard, NFlex, NModal } from 'naive-ui'
defineProps<{
show: boolean
name: string
preview: string
preparationError: string
isPreparing: boolean
isPrinting: boolean
}>()
const emit = defineEmits<{
'update:show': [value: boolean]
print: []
}>()
</script>
<template>
<n-modal
:show="show"
:mask-closable="!isPrinting"
:close-on-esc="!isPrinting"
@update:show="emit('update:show', $event)"
>
<n-card class="receipt-modal" title="Чек команды" role="dialog" aria-modal="true">
<n-alert v-if="preparationError" type="error">{{ preparationError }}</n-alert>
<img
v-else-if="preview"
class="receipt-preview"
:src="preview"
:alt="`Чек команды ${name}`"
/>
<p v-else-if="isPreparing">Подготовка чека…</p>
<p>Включите MXW01 рядом с сервером площадки. Сервер напечатает один чек.</p>
<n-flex justify="end">
<n-button :disabled="isPrinting" @click="emit('update:show', false)">Закрыть</n-button>
<n-button
type="primary"
:loading="isPrinting"
:disabled="!preview || isPreparing || isPrinting"
@click="emit('print')"
>Печатать</n-button
>
</n-flex>
</n-card>
</n-modal>
</template>
<style scoped>
.receipt-modal {
max-height: 90vh;
max-width: 460px;
overflow-y: auto;
width: calc(100vw - 32px);
}
.receipt-preview {
display: block;
height: auto;
margin: 0 auto 16px;
max-width: 100%;
width: 384px;
}
.receipt-modal p {
margin: 16px 0;
}
</style>