generated from VLADIMIR/template_frontend
202 lines
5.2 KiB
Vue
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>
|