This commit is contained in:
2026-09-07 03:24:31 +07:00
parent fa8b7dfb80
commit 958a6575b1
44 changed files with 312 additions and 44 deletions
+108 -2
View File
@@ -19,7 +19,7 @@ import { useRoute } from 'vue-router'
import { getArchiveClient } from '@/api/archive_client'
import { getAuthClient } from '@/api/auth_client'
import type { Scenario } from '@/api/generated/crabs/evening_detective_server'
import type { Conclusion, Scenario } from '@/api/generated/crabs/evening_detective_server'
import HeaderMenu from '@/components/HeaderMenu.vue'
import IntroBlock from '@/components/IntroBlock.vue'
import PlaceBlock from '@/components/PlaceBlock.vue'
@@ -32,6 +32,7 @@ import type {
EditablePlace,
EditableQuestion,
} from '@/types/editor'
import { applicationFileURL } from '@/utils/application'
const authStore = useAuthStore()
const client = getAuthClient()
@@ -64,6 +65,54 @@ const savingBackground = ref(false)
const intro = ref<EditableIntroduction>({ text: '', audio: '' })
const introMode = ref('show-editor')
const conclusion = ref<Conclusion>({ text: '', video: '' })
const conclusionFiles = ref<UploadFileInfo[]>([])
const savingConclusion = ref(false)
const conclusionVideoURL = computed(() => applicationFileURL(conclusion.value.video))
function resetConclusion() {
conclusion.value = { text: '', video: '', ...scenario.value.story?.conclusion }
conclusionFiles.value = []
}
function removeConclusionVideo() {
conclusion.value.video = ''
conclusionFiles.value = []
}
async function updateConclusion(remove = false) {
savingConclusion.value = true
try {
let video = toRawFilename(conclusion.value.video)
if (!remove && conclusionFiles.value.length) {
const file = conclusionFiles.value[0].file
if (!file) return
const uploaded = await uploadFile(
file,
'scenarios_' + scenario.value.id + '_conclusion_' + file.name,
)
if (!uploaded.filename) return
if (uploaded.fileType !== 'video') {
message.error('Закрывашка должна содержать видео MP4 или WebM')
return
}
video = uploaded.filename
}
const saved = remove ? { text: '', video: '' } : { text: conclusion.value.text, video }
const res = await client.UpdateScenarioConclusion({ id: scenario.value.id, conclusion: saved })
if (res.error) {
message.error(res.error!)
return
}
scenario.value.story!.conclusion = saved
resetConclusion()
message.success(remove ? 'Закрывашка удалена' : 'Закрывашка сохранена')
} catch {
message.error('Не удалось сохранить закрывашку')
} finally {
savingConclusion.value = false
}
}
// Точки сценария со служебными полями редактора (oldCode, mode, fileList)
const editorPlaces = computed<EditablePlace[]>(() => {
@@ -82,6 +131,7 @@ async function getScenario(id: number) {
return
}
scenario.value = res.scenario!
resetConclusion()
scenario.value.story?.places?.forEach((item) => {
const editable = item as EditablePlace
editable.oldCode = editable.code
@@ -195,7 +245,7 @@ async function uploadFile(file: File | null, filename: string): Promise<UploadFi
return { filename: '', fileType: '' }
}
// file_type возвращает сервер по содержимому файла (image | pdf | audio) —
// file_type возвращает сервер по содержимому файла (image | pdf | audio | video) —
// редактор сохраняет его на улике.
return {
filename: resUploadFile.filename || '',
@@ -621,6 +671,58 @@ function getRandomComplimentForNastya() {
</n-flex>
</IntroBlock>
<n-card title="Закрывашка сценария">
<n-space vertical>
<n-text depth="3">Разгадка дела для автора. Игрокам не показывается.</n-text>
<n-input
v-model:value="conclusion.text"
type="textarea"
:autosize="{ minRows: 4, maxRows: 12 }"
placeholder="Текст закрывашки"
aria-label="Текст закрывашки"
:disabled="savingConclusion"
/>
<video
v-if="conclusionVideoURL"
:src="conclusionVideoURL"
controls
preload="none"
class="conclusion-video"
aria-label="Видео закрывашки"
></video>
<n-button
v-if="conclusion.video"
:disabled="savingConclusion"
@click="removeConclusionVideo()"
>Убрать видео</n-button
>
<n-upload
v-model:file-list="conclusionFiles"
accept=".mp4,.webm"
:max="1"
:default-upload="false"
:disabled="savingConclusion"
>
<n-button :disabled="savingConclusion">Выбрать видео MP4/WebM</n-button>
</n-upload>
<n-flex justify="end">
<n-button
v-if="scenario.story?.conclusion?.text || scenario.story?.conclusion?.video"
:disabled="savingConclusion"
@click="updateConclusion(true)"
>Удалить закрывашку</n-button
>
<n-button :disabled="savingConclusion" @click="resetConclusion()">Отмена</n-button>
<n-button
:loading="savingConclusion"
:disabled="savingConclusion || !scenario.story"
@click="updateConclusion()"
>Сохранить закрывашку</n-button
>
</n-flex>
</n-space>
</n-card>
<div class="questions-header">Вопросы дела</div>
<QuestionBlock
@@ -817,6 +919,10 @@ function getRandomComplimentForNastya() {
</template>
<style scoped>
.conclusion-video {
width: 100%;
}
.background-preview {
max-height: 200px;
max-width: 100%;