Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
A
AI_BOOK_TOOL
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
template admin
AI_BOOK_TOOL
Commits
214b4d19
Commit
214b4d19
authored
Aug 02, 2025
by
liujiangnan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: AI 点选翻译
parent
f1c15699
Changes
3
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
954 additions
and
134 deletions
+954
-134
index.css
play/index.css
+307
-0
index.html
play/index.html
+110
-0
index.js
play/index.js
+537
-134
No files found.
play/index.css
View file @
214b4d19
...
@@ -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
;
...
...
play/index.html
View file @
214b4d19
...
@@ -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>
<!-- 控制按钮 -->
<!-- 控制按钮 -->
...
...
play/index.js
View file @
214b4d19
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment