2.4.3
This commit is contained in:
@@ -14,9 +14,19 @@
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<span class="font-medium text-gray-800">源文本</span>
|
||||
<div class="flex gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="flex items-center gap-1 cursor-pointer">
|
||||
<input type="radio" v-model="inputMode" value="bulk" class="w-4 h-4" />
|
||||
<span class="text-sm text-gray-700">批量导入</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-1 cursor-pointer">
|
||||
<input type="radio" v-model="inputMode" value="individual" class="w-4 h-4" />
|
||||
<span class="text-sm text-gray-700">单个编辑</span>
|
||||
</label>
|
||||
</div>
|
||||
<button @click="addSentence"
|
||||
class="px-3 py-1 text-sm bg-blue-100 text-blue-700 border border-blue-300 rounded hover:bg-blue-200">
|
||||
新增一行
|
||||
新增
|
||||
</button>
|
||||
<button @click="fetchPrologue" :disabled="isFetching || !isElectronEnv"
|
||||
class="px-3 py-1 text-sm bg-purple-100 text-purple-700 border border-purple-300 rounded hover:bg-purple-200 disabled:opacity-50">
|
||||
@@ -29,12 +39,32 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<textarea :value="bulkText || sentences.join('\n')" @input="handleBulkChange" @paste="handlePaste"
|
||||
placeholder="每行一句打招呼内容..."
|
||||
class="w-full h-32 p-3 border border-gray-300 rounded text-sm text-gray-900 resize-none focus:border-blue-500 focus:outline-none" />
|
||||
<!-- 批量导入模式 -->
|
||||
<div v-if="inputMode === 'bulk'">
|
||||
<textarea :value="bulkText || sentences.join('\n')" @input="handleBulkChange"
|
||||
@paste="handlePaste" placeholder="每行一句打招呼内容..."
|
||||
class="w-full h-32 p-3 border border-gray-300 rounded text-sm text-gray-900 resize-none focus:border-blue-500 focus:outline-none" />
|
||||
|
||||
<div class="text-xs text-gray-500 mt-2">
|
||||
提示: 每行一句,可直接粘贴多行文本
|
||||
<div class="text-xs text-gray-500 mt-2">
|
||||
提示: 每行一句,可直接粘贴多行文本
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 单个编辑模式 -->
|
||||
<div v-else class="space-y-3">
|
||||
<div v-for="(sentence, index) in sentences" :key="index"
|
||||
class="bg-white rounded border border-gray-200 p-3">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span class="text-xs text-gray-500">话术 {{ index + 1 }}</span>
|
||||
<button @click="removeSentence(index)"
|
||||
class="text-red-500 hover:text-red-700 text-sm">
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
<textarea :value="sentence" @input="updateSentence($event.target.value, index)"
|
||||
placeholder="输入打招呼内容(可包含多行)..."
|
||||
class="w-full h-20 p-2 border border-gray-200 rounded text-sm text-gray-900 resize-none focus:border-blue-500 focus:outline-none" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -78,13 +108,15 @@
|
||||
]">
|
||||
{{ region }}
|
||||
</div>
|
||||
<div v-if="filteredRegions.length === 0" class="col-span-full text-center py-8 text-gray-400">
|
||||
<div v-if="filteredRegions.length === 0"
|
||||
class="col-span-full text-center py-8 text-gray-400">
|
||||
未找到相关大区
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 选中大区的语言预览 -->
|
||||
<div v-if="selectedRegions.length > 0" class="mb-4 p-3 bg-blue-50 border border-blue-200 rounded">
|
||||
<div v-if="selectedRegions.length > 0"
|
||||
class="mb-4 p-3 bg-blue-50 border border-blue-200 rounded">
|
||||
<div class="text-sm text-blue-800 font-medium mb-2">
|
||||
选中 {{ selectedRegions.length }} 个大区,将翻译以下 {{ selectedLanguages.length }} 种语言:
|
||||
</div>
|
||||
@@ -112,7 +144,8 @@
|
||||
<!-- 当前语言的翻译结果 -->
|
||||
<div class="space-y-2 max-h-40 overflow-auto">
|
||||
<template v-if="translations[activeTab]?.length">
|
||||
<div v-for="(t, i) in translations[activeTab]" :key="i" class="flex items-center gap-2">
|
||||
<div v-for="(t, i) in translations[activeTab]" :key="i"
|
||||
class="flex items-center gap-2">
|
||||
<input type="text" :value="t" @input="updateTranslation($event.target.value, i)"
|
||||
class="flex-1 px-3 py-1.5 text-sm text-gray-900 border border-gray-300 rounded focus:border-blue-500 focus:outline-none" />
|
||||
<div class="relative group/source">
|
||||
@@ -141,22 +174,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部 -->
|
||||
<div class="p-4 border-t border-gray-200 flex justify-between items-center">
|
||||
<span class="text-xs text-gray-500">
|
||||
共 {{ sentences.filter(Boolean).length }} 句 · 选择 {{ selectedRegions.length }} 个大区 · {{
|
||||
selectedLanguages.length }} 种语言
|
||||
</span>
|
||||
<div class="flex gap-3">
|
||||
<button @click="onClose" class="px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg">
|
||||
取消
|
||||
</button>
|
||||
<button @click="handleConfirm"
|
||||
class="px-4 py-2 text-sm bg-blue-500 text-white rounded-lg hover:bg-blue-600">
|
||||
确定
|
||||
</button>
|
||||
<!-- 底部 -->
|
||||
<div class="p-4 border-t border-gray-200 flex justify-between items-center">
|
||||
<span class="text-xs text-gray-500">
|
||||
共 {{ sentences.filter(Boolean).length }} 句 · 选择 {{ selectedRegions.length }} 个大区 · {{
|
||||
selectedLanguages.length }} 种语言
|
||||
</span>
|
||||
<div class="flex gap-3">
|
||||
<button @click="onClose"
|
||||
class="px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg">
|
||||
取消
|
||||
</button>
|
||||
<button @click="handleConfirm"
|
||||
class="px-4 py-2 text-sm bg-blue-500 text-white rounded-lg hover:bg-blue-600">
|
||||
确定
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -180,6 +214,7 @@ const REGION_LIST = getRegions()
|
||||
|
||||
const sentences = ref([''])
|
||||
const bulkText = ref('')
|
||||
const inputMode = ref('bulk') // 'bulk' 或 'individual'
|
||||
const selectedRegions = ref([])
|
||||
const translations = ref({})
|
||||
const activeTab = ref('')
|
||||
@@ -262,6 +297,7 @@ function loadFromStorage() {
|
||||
if (data.translations) translations.value = data.translations
|
||||
if (typeof data.needTranslate === 'boolean') needTranslate.value = data.needTranslate
|
||||
if (data.activeTab) activeTab.value = data.activeTab
|
||||
if (data.inputMode) inputMode.value = data.inputMode
|
||||
} catch (e) {
|
||||
console.error('加载本地数据失败:', e)
|
||||
}
|
||||
@@ -275,6 +311,7 @@ function saveToStorage() {
|
||||
translations: translations.value,
|
||||
needTranslate: needTranslate.value,
|
||||
activeTab: activeTab.value,
|
||||
inputMode: inputMode.value,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -285,6 +322,17 @@ const addSentence = () => {
|
||||
sentences.value.push('')
|
||||
}
|
||||
|
||||
const updateSentence = (value, index) => {
|
||||
sentences.value[index] = value
|
||||
}
|
||||
|
||||
const removeSentence = (index) => {
|
||||
sentences.value.splice(index, 1)
|
||||
if (sentences.value.length === 0) {
|
||||
sentences.value.push('')
|
||||
}
|
||||
}
|
||||
|
||||
const handlePaste = (e) => {
|
||||
// e.preventDefault() handled by Vue if needed but standard logic applies
|
||||
// We can rely on @paste event
|
||||
@@ -397,6 +445,7 @@ const handleTranslate = async () => {
|
||||
try {
|
||||
const result = await window.electronAPI.translate(joinedText, lang)
|
||||
if (result.success) {
|
||||
console.log(`翻译结果完整 ${lang} 成功:`, result)
|
||||
let translatedLines = result.result.split('\n').map(s => s.trim())
|
||||
|
||||
if (translatedLines.length > 0) {
|
||||
|
||||
@@ -133,20 +133,29 @@
|
||||
<span class="text-gray-500">已打招呼</span>
|
||||
<span class="text-blue-600 font-medium">{{ greetingStats.greetingCount }} 位</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-xs">
|
||||
<span class="text-gray-500">已发邀请</span>
|
||||
<span class="text-purple-600 font-medium">{{ greetingStats.inviteCount }} 个</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between text-xs">
|
||||
<span class="text-gray-500">已回复</span>
|
||||
<button @click="showReplyList"
|
||||
class="text-blue-500 hover:text-blue-600 hover:underline cursor-pointer" title="查看回复列表">
|
||||
历史回复列表
|
||||
</button>
|
||||
<div class="flex items-center gap-1">
|
||||
<button @click="showReplyList"
|
||||
class="text-blue-500 hover:text-blue-600 hover:underline cursor-pointer" title="查看回复列表">
|
||||
历史回复
|
||||
</button>
|
||||
|
||||
<span class="text-emerald-600 font-medium">{{ greetingStats.replyCount || 0 }} 条</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between text-xs">
|
||||
<span class="text-gray-500">已邀请</span>
|
||||
<button @click="showInviteList"
|
||||
class="text-purple-500 hover:text-purple-600 hover:underline cursor-pointer" title="查看邀请列表">
|
||||
回复邀请列表
|
||||
</button>
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="text-purple-600 font-medium">{{ greetingStats.inviteCount || 0 }} 个</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 回复列表弹出框 -->
|
||||
<div v-if="replyListVisible"
|
||||
class="absolute left-0 right-0 bottom-full mb-1 bg-white border border-gray-200 rounded-lg shadow-lg z-50 max-h-48 overflow-hidden"
|
||||
@@ -164,7 +173,7 @@
|
||||
<div v-if="repliedSessions.length === 0" class="text-gray-400 text-xs text-center py-3">
|
||||
暂无回复记录
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-els e>
|
||||
<div v-for="(session, index) in repliedSessions" :key="index"
|
||||
class="px-3 py-1.5 text-xs text-gray-700 hover:bg-gray-50 border-b border-gray-50 last:border-0 flex items-center justify-between">
|
||||
<div class="truncate w-16" :title="session.name">
|
||||
@@ -187,6 +196,47 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 邀请列表弹出框 -->
|
||||
<div v-if="inviteListVisible"
|
||||
class="absolute left-0 right-0 bottom-full mb-1 bg-white border border-gray-200 rounded-lg shadow-lg z-50 max-h-48 overflow-hidden"
|
||||
:style="{ maxWidth: sidebarWidth + 'px' }">
|
||||
<div class="flex items-center justify-between px-3 py-2 border-b border-gray-100 bg-gray-50">
|
||||
<span class="text-xs font-semibold text-purple-600">邀请列表</span>
|
||||
<button @click="inviteListVisible = false" class="text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="overflow-y-auto max-h-36">
|
||||
<div v-if="invitedSessions.length === 0" class="text-gray-400 text-xs text-center py-3">
|
||||
暂无邀请记录
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-for="(session, index) in invitedSessions" :key="index"
|
||||
class="px-3 py-1.5 text-xs text-gray-700 hover:bg-gray-50 border-b border-gray-50 last:border-0 flex items-center justify-between">
|
||||
<div class="truncate w-16" :title="session.name">
|
||||
{{ session.name }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-gray-400 text-[10px]">
|
||||
视图: {{ session.viewId }}
|
||||
</span>
|
||||
<button @click="copyAnchorId(session.anchorId)"
|
||||
class="text-purple-500 hover:text-purple-600 p-1 rounded hover:bg-purple-50 transition-colors"
|
||||
title="复制 ID">
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
@@ -213,18 +263,23 @@ const emit = defineEmits(['tabSwitch', 'goBack', 'stopAll'])
|
||||
|
||||
// 回复列表相关
|
||||
const replyListVisible = ref(false)
|
||||
/** @type {import('vue').Ref<Array<{name: string; id: string}>>} */
|
||||
const inviteListVisible = ref(false)
|
||||
/** @type {import('vue').Ref<Array<{name: string; anchorId: string; viewId: number; invited: boolean}>>} */
|
||||
const repliedSessions = ref([])
|
||||
/** @type {import('vue').Ref<Array<{name: string; anchorId: string; viewId: number; invited: boolean}>>} */
|
||||
const invitedSessions = ref([])
|
||||
|
||||
// 显示回复列表
|
||||
const showReplyList = async () => {
|
||||
replyListVisible.value = !replyListVisible.value
|
||||
inviteListVisible.value = false
|
||||
if (replyListVisible.value && window.electronAPI?.getRepliedSessions) {
|
||||
try {
|
||||
const result = await window.electronAPI.getRepliedSessions()
|
||||
console.log("回复列表里是", result)
|
||||
// 按倒序展示,最新的在最前面
|
||||
repliedSessions.value = (result || []).reverse()
|
||||
// 过滤出未邀请的会话
|
||||
repliedSessions.value = (result || []).filter(session => !session.invited).reverse()
|
||||
} catch (e) {
|
||||
console.error('获取回复列表失败:', e)
|
||||
repliedSessions.value = []
|
||||
@@ -232,6 +287,24 @@ const showReplyList = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 显示邀请列表
|
||||
const showInviteList = async () => {
|
||||
inviteListVisible.value = !inviteListVisible.value
|
||||
replyListVisible.value = false
|
||||
if (inviteListVisible.value && window.electronAPI?.getRepliedSessions) {
|
||||
try {
|
||||
const result = await window.electronAPI.getRepliedSessions()
|
||||
console.log("邀请列表里是", result)
|
||||
// 按倒序展示,最新的在最前面
|
||||
// 过滤出已邀请的会话
|
||||
invitedSessions.value = (result || []).filter(session => session.invited).reverse()
|
||||
} catch (e) {
|
||||
console.error('获取邀请列表失败:', e)
|
||||
invitedSessions.value = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 复制主播 ID
|
||||
const copyAnchorId = (id) => {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
|
||||
Reference in New Issue
Block a user