Commit 214b4d19 authored by liujiangnan's avatar liujiangnan

feat: AI 点选翻译

parent f1c15699
...@@ -193,6 +193,38 @@ body { ...@@ -193,6 +193,38 @@ body {
background-color: rgba(76, 175, 80, 1); background-color: rgba(76, 175, 80, 1);
} }
/* AI翻译按钮样式 */
.translate-btn {
position: absolute;
top: 20px;
right: 120px;
z-index: 100;
padding: 10px 20px;
border: none;
border-radius: 25px;
background-color: rgba(255, 255, 255, 0.9);
color: #333;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
.translate-btn:hover {
background-color: rgba(255, 255, 255, 1);
transform: scale(1.05);
}
.translate-btn.active {
background-color: rgba(156, 39, 176, 0.9);
color: white;
}
.translate-btn.active:hover {
background-color: rgba(156, 39, 176, 1);
}
/* 点读模式下的可点击矩形框 */ /* 点读模式下的可点击矩形框 */
.highlight-rect.clickable { .highlight-rect.clickable {
pointer-events: auto; pointer-events: auto;
...@@ -217,6 +249,281 @@ body { ...@@ -217,6 +249,281 @@ body {
box-shadow: 0 0 10px rgba(255, 193, 7, 0.6); box-shadow: 0 0 10px rgba(255, 193, 7, 0.6);
} }
/* 翻译模式下的单词框 */
.translate-word-box {
position: absolute;
border: 2px dashed rgba(156, 39, 176, 0.6);
background-color: rgba(156, 39, 176, 0.1);
pointer-events: none;
z-index: 15;
transition: all 0.2s ease;
}
.translate-word-box.clickable {
pointer-events: auto;
cursor: pointer;
border: 2px dashed rgba(156, 39, 176, 0.8);
background-color: rgba(156, 39, 176, 0.15);
transition: all 0.2s ease;
}
.translate-word-box.clickable:hover {
background-color: rgba(156, 39, 176, 0.3);
border-color: rgba(156, 39, 176, 1);
transform: scale(1.02);
}
/* 翻译气泡框样式 */
.translate-bubble {
position: absolute;
width: 600px;
max-height: 400px;
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
border: 1px solid #e0e0e0;
z-index: 1000;
overflow: hidden;
}
.bubble-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background-color: #f8f9fa;
border-bottom: 1px solid #e0e0e0;
}
.bubble-title {
font-weight: bold;
color: #333;
font-size: 14px;
}
.ai-provider {
font-weight: normal;
color: #666;
font-size: 12px;
}
.bubble-close {
background: none;
border: none;
font-size: 18px;
color: #666;
cursor: pointer;
padding: 0;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.2s ease;
}
.bubble-close:hover {
background-color: #e0e0e0;
color: #333;
}
.bubble-content {
padding: 16px;
max-height: 300px;
overflow-y: auto;
overflow-x: hidden;
scroll-behavior: smooth;
display: flex;
flex-direction: column;
}
.translate-word {
font-weight: bold;
color: #333;
font-size: 16px;
margin-bottom: 8px;
padding: 4px 8px;
background-color: #f0f0f0;
border-radius: 4px;
display: inline-block;
}
.translate-result {
color: #666;
font-size: 14px;
line-height: 1.5;
white-space: pre-wrap;
word-wrap: break-word;
padding-right: 8px; /* 为滚动条留出空间 */
margin: 0;
min-height: 20px;
flex: 1;
}
/* 可视化翻译结果样式 */
.translate-visual {
flex: 1;
overflow-y: auto;
padding-right: 8px;
}
.word-section {
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #e0e0e0;
}
.word-display {
font-size: 24px;
font-weight: bold;
color: #333;
margin-bottom: 8px;
}
.phonetic-section {
display: flex;
gap: 16px;
flex-wrap: wrap;
}
.phonetic-item {
display: flex;
align-items: center;
gap: 4px;
}
.phonetic-label {
font-size: 12px;
color: #666;
font-weight: 500;
}
.phonetic-text {
font-size: 12px;
color: #007bff;
font-family: 'Courier New', monospace;
}
.translation-section {
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #e0e0e0;
}
.part-of-speech,
.contextual-translation,
.general-translations {
margin-bottom: 8px;
display: flex;
align-items: flex-start;
gap: 8px;
}
.pos-label,
.translation-label {
font-size: 12px;
color: #666;
font-weight: 500;
min-width: 60px;
}
.pos-text {
font-size: 14px;
color: #007bff;
font-weight: 500;
}
.translation-text {
font-size: 14px;
color: #333;
flex: 1;
}
.section-title {
font-size: 14px;
font-weight: bold;
color: #333;
margin-bottom: 8px;
padding-bottom: 4px;
border-bottom: 2px solid #007bff;
}
.syntax-section,
.phrases-section,
.examples-section,
.usage-section {
margin-bottom: 16px;
}
.syntax-content {
font-size: 13px;
line-height: 1.6;
color: #555;
text-align: justify;
}
.phrases-list,
.examples-list,
.usage-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.phrase-item,
.example-item {
padding: 8px;
background-color: #f8f9fa;
border-radius: 4px;
border-left: 3px solid #007bff;
}
.phrase-english,
.example-english {
font-size: 13px;
color: #333;
font-weight: 500;
margin-bottom: 4px;
}
.phrase-translation,
.example-translation {
font-size: 12px;
color: #666;
}
.usage-item {
font-size: 12px;
color: #555;
line-height: 1.5;
padding: 4px 0;
border-bottom: 1px solid #f0f0f0;
}
.usage-item:last-child {
border-bottom: none;
}
/* 自定义滚动条样式 */
.bubble-content::-webkit-scrollbar {
width: 8px;
}
.bubble-content::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.bubble-content::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
.bubble-content::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* 调试信息样式 */ /* 调试信息样式 */
.debug-info { .debug-info {
position: absolute; position: absolute;
......
...@@ -36,6 +36,16 @@ ...@@ -36,6 +36,16 @@
{{ isPointReadMode ? '退出点读' : '点读' }} {{ isPointReadMode ? '退出点读' : '点读' }}
</button> </button>
<!-- AI翻译按钮 -->
<button
class="translate-btn"
:class="{ active: isTranslateMode }"
@click="toggleTranslateMode"
title="AI翻译模式"
>
{{ isTranslateMode ? '退出翻译' : 'AI翻译' }}
</button>
<!-- 图片容器 --> <!-- 图片容器 -->
<div class="image-container"> <div class="image-container">
<img <img
...@@ -69,6 +79,106 @@ ...@@ -69,6 +79,106 @@
class="word-highlight" class="word-highlight"
:style="getWordStyle(word)" :style="getWordStyle(word)"
></div> ></div>
<!-- 翻译模式下的单词框 -->
<div
v-for="(word, index) in translateWords"
:key="'translate-word-' + index"
class="translate-word-box"
:class="{ clickable: isTranslateMode }"
:style="getWordStyle(word)"
@click="isTranslateMode ? showTranslateBubble(word, index) : null"
></div>
<!-- 翻译气泡框 -->
<div
v-if="translateBubble.show"
class="translate-bubble"
:style="translateBubble.style"
>
<div class="bubble-header">
<span class="bubble-title">AI翻译 <span class="ai-provider">(阿里云通义千问 qwen-plus)</span></span>
<button class="bubble-close" @click="closeTranslateBubble">×</button>
</div>
<div class="bubble-content">
<!-- 原始JSON显示(调试用) -->
<div v-if="translateBubble.result && !translateBubble.parsedData" class="translate-result" ref="translateResult">
{{ translateBubble.result }}
</div>
<!-- 可视化翻译结果 -->
<div v-if="translateBubble.parsedData" class="translate-visual">
<!-- 单词和音标 -->
<div class="word-section">
<div class="word-display">{{ translateBubble.parsedData.word }}</div>
<div class="phonetic-section">
<span class="phonetic-item">
<span class="phonetic-label">美音:</span>
<span class="phonetic-text">{{ translateBubble.parsedData.us_phonetic }}</span>
</span>
<span class="phonetic-item">
<span class="phonetic-label">英音:</span>
<span class="phonetic-text">{{ translateBubble.parsedData.uk_phonetic }}</span>
</span>
</div>
</div>
<!-- 词性和翻译 -->
<div class="translation-section">
<div class="part-of-speech">
<span class="pos-label">词性:</span>
<span class="pos-text">{{ translateBubble.parsedData.part_of_speech }}</span>
</div>
<div class="contextual-translation">
<span class="translation-label">语境翻译:</span>
<span class="translation-text">{{ translateBubble.parsedData.contextual_translation }}</span>
</div>
<div class="general-translations">
<span class="translation-label">一般翻译:</span>
<span class="translation-text">{{ translateBubble.parsedData.general_translations.join(', ') }}</span>
</div>
</div>
<!-- 语法分析 -->
<div class="syntax-section">
<div class="section-title">语法分析</div>
<div class="syntax-content">{{ translateBubble.parsedData.syntax_analysis }}</div>
</div>
<!-- 常用短语 -->
<div v-if="translateBubble.parsedData.common_phrases && translateBubble.parsedData.common_phrases.length > 0" class="phrases-section">
<div class="section-title">常用短语</div>
<div class="phrases-list">
<div v-for="(phrase, index) in translateBubble.parsedData.common_phrases" :key="index" class="phrase-item">
<div class="phrase-english">{{ phrase.phrase }}</div>
<div class="phrase-translation">{{ phrase.translation }}</div>
</div>
</div>
</div>
<!-- 例句 -->
<div v-if="translateBubble.parsedData.example_usage && translateBubble.parsedData.example_usage.length > 0" class="examples-section">
<div class="section-title">例句</div>
<div class="examples-list">
<div v-for="(example, index) in translateBubble.parsedData.example_usage" :key="index" class="example-item">
<div class="example-english">{{ example.example }}</div>
<div class="example-translation">{{ example.translation }}</div>
</div>
</div>
</div>
<!-- 用法说明 -->
<div v-if="translateBubble.parsedData.usage_notes && translateBubble.parsedData.usage_notes.length > 0" class="usage-section">
<div class="section-title">用法说明</div>
<div class="usage-list">
<div v-for="(note, index) in translateBubble.parsedData.usage_notes" :key="index" class="usage-item">
{{ note }}
</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
<!-- 控制按钮 --> <!-- 控制按钮 -->
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment