This commit is contained in:
fengchuanhn@gmail.com
2026-05-22 18:42:26 +08:00
parent 2abc2134a8
commit d3af2e1124
19 changed files with 1856 additions and 116 deletions

View File

@@ -0,0 +1,119 @@
<script setup>
import { ref } from "vue";
const visible = defineModel("visible", { type: Boolean, default: false });
const emit = defineEmits(["submit"]);
const lines = ref([{ text: "" }]);
const pasteVisible = ref(false);
const pasteRaw = ref("");
function addLine() {
lines.value.push({ text: "" });
}
function removeLine(index) {
lines.value.splice(index, 1);
}
function openPaste() {
pasteRaw.value = "";
pasteVisible.value = true;
}
function confirmPaste() {
const items = pasteRaw.value
.split("\n")
.map((s) => s.trim())
.filter(Boolean);
if (items.length) {
lines.value = items.map((text) => ({ text }));
}
pasteVisible.value = false;
}
function onSubmit() {
const payload = lines.value.map((l) => ({ text: String(l.text || "").trim() }));
if (!payload.length || payload.some((l) => !l.text)) {
window.alert("所有内容不能为空");
return;
}
emit("submit", payload);
visible.value = false;
lines.value = [{ text: "" }];
}
</script>
<template>
<Dialog
v-model:visible="visible"
modal
header="批量文本合成"
:style="{ width: '80vw', maxWidth: '960px' }"
:draggable="false"
>
<div class="mb-3 flex flex-wrap items-center gap-2">
<Button label="添加一条" size="small" icon="pi pi-plus" @click="addLine" />
<Button
label="批量粘贴"
size="small"
icon="pi pi-clone"
severity="secondary"
outlined
@click="openPaste"
/>
<span class="text-sm text-slate-500"> {{ lines.length }} </span>
</div>
<div v-if="!lines.length" class="py-12 text-center text-sm text-slate-500">
暂无内容
</div>
<div v-else class="max-h-[50vh] space-y-2 overflow-y-auto pr-1">
<div
v-for="(line, index) in lines"
:key="index"
class="flex gap-2 rounded-lg border border-white/10 bg-white/5 p-3"
>
<Textarea
v-model="line.text"
rows="2"
auto-resize
class="flex-1"
placeholder="输入内容"
:maxlength="1000"
/>
<Button
icon="pi pi-trash"
size="small"
severity="danger"
text
aria-label="删除"
@click="removeLine(index)"
/>
</div>
</div>
<template #footer>
<Button label="取消" severity="secondary" text @click="visible = false" />
<Button label="提交合成" @click="onSubmit" />
</template>
</Dialog>
<Dialog
v-model:visible="pasteVisible"
modal
header="批量粘贴,每行一条"
:style="{ width: '70vw', maxWidth: '720px' }"
>
<Textarea
v-model="pasteRaw"
rows="12"
class="w-full"
placeholder="批量粘贴,每行一个"
/>
<template #footer>
<Button label="确定" @click="confirmPaste" />
</template>
</Dialog>
</template>

View File

@@ -13,8 +13,44 @@ import {
import { previewVoice } from "../../services/voicePreview.js";
import VoiceCloneEditDialog from "./VoiceCloneEditDialog.vue";
const props = defineProps({
/** 声音页等独立场景:由父组件控制显隐与选中音色 */
standalone: { type: Boolean, default: false },
visible: { type: Boolean, default: false },
selectedVoiceId: { type: String, default: "sys_Cherry" },
});
const emit = defineEmits(["update:visible", "update:selectedVoiceId", "select"]);
const workflow = useWorkflowStore();
const { voiceManageModalVisible, selectedVoiceId } = storeToRefs(workflow);
const { voiceManageModalVisible, selectedVoiceId: workflowVoiceId } =
storeToRefs(workflow);
const dialogVisible = computed({
get() {
return props.standalone ? props.visible : voiceManageModalVisible.value;
},
set(v) {
if (props.standalone) {
emit("update:visible", v);
} else {
voiceManageModalVisible.value = v;
}
},
});
const currentVoiceId = computed({
get() {
return props.standalone ? props.selectedVoiceId : workflowVoiceId.value;
},
set(v) {
if (props.standalone) {
emit("update:selectedVoiceId", v);
} else {
workflowVoiceId.value = v;
}
},
});
const activeTab = ref("system");
const voiceCategory = ref("putonghua");
@@ -41,7 +77,7 @@ function loadCloneVoices() {
}
}
watch(voiceManageModalVisible, (open) => {
watch(dialogVisible, (open) => {
if (open) {
activeTab.value = "system";
voiceCategory.value = "putonghua";
@@ -63,11 +99,21 @@ function stopPreview() {
}
function onClose() {
workflow.closeVoiceManageDialog();
if (props.standalone) {
dialogVisible.value = false;
} else {
workflow.closeVoiceManageDialog();
}
}
function onSelectVoice(voiceId) {
workflow.selectVoice(voiceId);
if (props.standalone) {
currentVoiceId.value = voiceId;
emit("select", voiceId);
dialogVisible.value = false;
} else {
workflow.selectVoice(voiceId);
}
}
function onAddClone() {
@@ -81,8 +127,12 @@ function onEditClone(record) {
function onDeleteClone(record) {
if (!window.confirm(`确认删除音色「${record.title}」?`)) return;
deleteCloneVoice(record.id);
if (selectedVoiceId.value === record.id) {
workflow.selectVoice("sys_Cherry");
if (currentVoiceId.value === record.id) {
if (props.standalone) {
currentVoiceId.value = "sys_Cherry";
} else {
workflow.selectVoice("sys_Cherry");
}
}
if (previewPanelKey.value === record.id) {
stopPreview();
@@ -144,7 +194,7 @@ function showPreviewPlayer(key) {
<template>
<Dialog
v-model:visible="voiceManageModalVisible"
v-model:visible="dialogVisible"
modal
header="音色管理"
:style="{ width: '900px' }"