159 lines
5.1 KiB
Vue
159 lines
5.1 KiB
Vue
<script setup>
|
||
import { computed, ref, watch } from "vue";
|
||
import TitleLineStyleEditor from "./TitleLineStyleEditor.vue";
|
||
import { syncTitleLinesFromReference } from "../../utils/subtitleTemplateDraft.js";
|
||
|
||
const config = defineModel("config", { type: Object, required: true });
|
||
|
||
const props = defineProps({
|
||
referenceTitle: { type: String, default: "" },
|
||
});
|
||
|
||
const titleConfig = computed({
|
||
get: () => config.value.titleSubtitleConfig,
|
||
set: (v) => {
|
||
config.value.titleSubtitleConfig = v;
|
||
},
|
||
});
|
||
|
||
const enabled = computed({
|
||
get: () => titleConfig.value?.enabled !== false,
|
||
set: (v) => {
|
||
titleConfig.value.enabled = v;
|
||
},
|
||
});
|
||
|
||
const openLineIndex = ref(0);
|
||
|
||
const displayTitle = computed(
|
||
() => props.referenceTitle?.trim() || titleConfig.value?.sourceTitle || "(暂无标题,请先在步骤 04 生成)",
|
||
);
|
||
|
||
function reloadFromReference() {
|
||
syncTitleLinesFromReference(
|
||
{ config: config.value },
|
||
props.referenceTitle || titleConfig.value?.sourceTitle,
|
||
);
|
||
}
|
||
|
||
const linePreviews = computed(() => titleConfig.value?.lines || []);
|
||
|
||
const maxCharMarks = [8, 12, 15, 20];
|
||
|
||
function markLeft(value) {
|
||
return `${((value - 8) / (20 - 8)) * 100}%`;
|
||
}
|
||
|
||
watch(
|
||
() => titleConfig.value?.maxCharsPerLine,
|
||
() => {
|
||
reloadFromReference();
|
||
},
|
||
);
|
||
</script>
|
||
|
||
<template>
|
||
<div class="title-subtitle-settings space-y-4 rounded-lg border border-slate-600/40 bg-slate-800/30 p-4">
|
||
<div class="flex items-center gap-3">
|
||
<Checkbox v-model="enabled" binary />
|
||
<span class="font-medium text-slate-100">标题字幕</span>
|
||
</div>
|
||
|
||
<div v-if="enabled" class="space-y-4">
|
||
<div>
|
||
<div class="mb-2 text-xs text-slate-400">
|
||
当前标题(自动读取步骤 04「标题标签关键词」生成结果):
|
||
</div>
|
||
<div class="flex flex-wrap items-center gap-2 rounded bg-slate-900/50 p-3">
|
||
<div class="flex-1 text-sm text-slate-200">{{ displayTitle }}</div>
|
||
<Button label="重新加载" size="small" severity="secondary" @click="reloadFromReference" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="space-y-4">
|
||
<div class="flex items-center gap-3">
|
||
<div class="w-28 shrink-0 text-sm text-slate-300">每行最大字符数:</div>
|
||
<div class="relative flex-1 pb-5">
|
||
<Slider
|
||
v-model="titleConfig.maxCharsPerLine"
|
||
:min="8"
|
||
:max="20"
|
||
:step="1"
|
||
class="w-full"
|
||
/>
|
||
<div class="pointer-events-none absolute inset-x-0 top-6 text-xs text-slate-500">
|
||
<span
|
||
v-for="mark in maxCharMarks"
|
||
:key="mark"
|
||
class="absolute -translate-x-1/2"
|
||
:style="{ left: markLeft(mark) }"
|
||
>
|
||
{{ mark }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-center gap-3">
|
||
<div class="w-28 shrink-0 text-sm text-slate-300">行间距(%):</div>
|
||
<InputNumber
|
||
v-model="titleConfig.lineSpacingPercent"
|
||
:min="3"
|
||
:max="15"
|
||
class="w-32"
|
||
/>
|
||
<span class="text-xs text-slate-500">相对于视频高度</span>
|
||
</div>
|
||
<div class="flex items-center gap-3">
|
||
<div class="w-28 shrink-0 text-sm text-slate-300">顶部边距(%):</div>
|
||
<InputNumber
|
||
v-model="titleConfig.topMarginPercent"
|
||
:min="2"
|
||
:max="20"
|
||
class="w-32"
|
||
/>
|
||
<span class="text-xs text-slate-500">相对于视频高度</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="linePreviews.length">
|
||
<div class="mb-2 text-xs text-slate-400">分行预览(共 {{ linePreviews.length }} 行)</div>
|
||
<div class="space-y-1">
|
||
<div
|
||
v-for="(line, i) in linePreviews"
|
||
:key="i"
|
||
class="rounded bg-slate-900/40 px-2 py-1 text-sm text-slate-300"
|
||
>
|
||
<span class="text-slate-500">第{{ i + 1 }}行:</span>{{ line.text }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<div class="mb-2 text-sm font-medium text-slate-200">每行样式配置</div>
|
||
<div class="space-y-2">
|
||
<div
|
||
v-for="(line, index) in titleConfig.lines"
|
||
:key="index"
|
||
class="overflow-hidden rounded-lg border border-slate-600/50"
|
||
>
|
||
<button
|
||
type="button"
|
||
class="flex w-full items-center justify-between bg-slate-900/50 px-3 py-2 text-left text-sm text-slate-200 hover:bg-slate-900/70"
|
||
@click="openLineIndex = openLineIndex === index ? -1 : index"
|
||
>
|
||
<span>第{{ index + 1 }}行:{{ line.text || "(空)" }}</span>
|
||
<i
|
||
class="pi text-xs"
|
||
:class="openLineIndex === index ? 'pi-chevron-up' : 'pi-chevron-down'"
|
||
/>
|
||
</button>
|
||
<div v-show="openLineIndex === index" class="border-t border-slate-600/40 bg-white p-3">
|
||
<TitleLineStyleEditor v-model:line="titleConfig.lines[index]" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|