This commit is contained in:
2026-03-23 14:46:34 +08:00
parent 52e44bd857
commit d517c1573d
2 changed files with 157 additions and 35 deletions

View File

@@ -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) {

View File

@@ -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) {