Files
evening_detective_frontend/src/components/IntroBlock.vue
T
2026-09-02 00:59:46 +07:00

202 lines
5.2 KiB
Vue

<script setup lang="ts">
import { NButton, NInput, NSpace, NText, NUpload, NUploadDragger } from 'naive-ui'
import { computed, type PropType } from 'vue'
import { getURL } from '@/api/generated/crabs/evening_detective_server/client'
import type { EditableIntroduction } from '@/types/editor'
import MessagePaper from './MessagePaper.vue'
const props = defineProps({
introduction: {
type: Object as PropType<EditableIntroduction>,
required: true,
},
mode: {
type: String,
required: true,
},
})
const emit = defineEmits<{
'update:mode': [value: string]
}>()
const updateMode = (mode: string) => {
emit('update:mode', mode)
}
const hasText = computed(() => Boolean(props.introduction?.text))
const hasAudio = computed(() => Boolean(props.introduction?.audio))
// Бэкенд отдаёт аудио полным URL (домен + имя файла); на случай голого
// имени файла строим URL до /api/files самостоятельно.
const audioUrl = computed(() => {
const value = props.introduction?.audio
if (!value) return undefined
if (/^https?:\/\//i.test(value)) return value
return `${getURL()}/api/files/${encodeURIComponent(value)}`
})
const removeAudio = () => {
if (props.introduction) {
props.introduction!.audio = ''
props.introduction!.fileList = undefined
}
}
</script>
<template>
<!-- Просмотр вступления командой (первый лист дела) -->
<div v-if="mode == 'show'">
<MessagePaper>
<div class="font">Вступление</div>
<hr class="intro-hr" />
<div class="intro-text font">{{ introduction.text }}</div>
<div v-if="hasAudio" class="intro-audio-block">
<audio :src="audioUrl" controls preload="none" class="intro-audio"></audio>
</div>
</MessagePaper>
</div>
<!-- Просмотр вступления в редакторе: клик — в режим редактирования -->
<div
v-if="mode == 'show-editor'"
class="place-block-hover show-editor-block"
@click="updateMode('edit')"
>
<div class="message-header">Вступление</div>
<hr class="hr" />
<div class="message-content">
<div v-if="hasAudio" class="intro-audio-block">
<audio :src="audioUrl" controls preload="none" class="intro-audio"></audio>
</div>
<span v-if="hasText">{{ introduction.text }}</span>
<span v-else class="intro-empty">Введение не задано — нажмите, чтобы добавить</span>
</div>
</div>
<!-- Редактирование вступления автором -->
<div v-if="mode == 'edit'" class="place-block">
<n-space vertical>
<p class="settings-header">Текст вступления</p>
<n-input
v-model:value="props.introduction!.text"
type="textarea"
:autosize="{ minRows: 4, maxRows: 12 }"
placeholder="Дорогие сыщики! В этом деле вам предстоит..."
/>
<p class="settings-header">Аудио вступления</p>
<div class="intro-edit-audio">
<n-space vertical>
<audio
v-if="hasAudio"
:src="audioUrl"
controls
preload="none"
class="intro-audio"
></audio>
<n-text v-else depth="3">Аудио не задано</n-text>
<n-button @click="removeAudio()" :disabled="!hasAudio">Убрать аудио</n-button>
</n-space>
<n-upload directory-dnd :max="1" v-model:file-list="props.introduction!.fileList">
<n-upload-dragger>
<n-text style="font-size: 16px">
Щелкните или перетащите аудиофайл в эту область для загрузки
</n-text>
</n-upload-dragger>
</n-upload>
</div>
<div class="slot-block">
<slot></slot>
</div>
</n-space>
</div>
</template>
<style scoped>
/* Бумажный лист вступления (просмотр командой и в каталоге) */
.font {
font-family: 'font_old_typer', serif;
font-size: 18px;
}
.intro-hr {
border: dashed 1px;
border-color: black;
margin: 10px 0;
}
.intro-text {
font-weight: 500;
white-space: pre-wrap;
}
/* Панели в стиле блоков точек (см. PlaceBlock.vue) */
.place-block-hover,
.place-block {
background-color: #222;
border: 1px solid #222;
border-radius: 10px;
margin: 10px 0;
padding: 20px;
}
.place-block-hover:hover {
border: 1px solid #63e2b7;
color: #63e2b7;
cursor: pointer;
}
.show-editor-block {
cursor: pointer;
}
.hr {
border: dashed 1px;
border-color: #eee;
margin: 10px 0;
}
.message-header {
font-size: 20px;
padding-right: 50px;
}
.message-content {
font-weight: 500;
white-space: pre-wrap;
}
.intro-empty {
color: #777;
}
.settings-header {
margin-top: 20px;
}
.intro-audio-block {
margin-top: 15px;
}
.intro-audio {
border-radius: 5px;
max-width: 460px;
width: 100%;
}
.intro-edit-audio {
align-items: flex-start;
display: flex;
flex-flow: row wrap;
gap: 20px;
}
.slot-block {
margin-top: 10px;
}
</style>