Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
50a8c95b
提交
50a8c95b
authored
4月 22, 2026
作者:
张伊明
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat 智能翻译接入接口、部分优化
上级
7f1603ac
流水线
#581
已失败 于阶段
in 1 分 3 秒
变更
3
流水线
1
显示空白字符变更
内嵌
并排
正在显示
3 个修改的文件
包含
248 行增加
和
72 行删除
+248
-72
index.js
src/api/intelligentTranslation/index.js
+34
-0
documentCompare.vue
src/views/intelligentTranslation/documentCompare.vue
+177
-63
index.vue
src/views/intelligentTranslation/index.vue
+37
-9
没有找到文件。
src/api/intelligentTranslation/index.js
0 → 100644
浏览文件 @
50a8c95b
import
request
from
'@/api/request.js'
const
INTELLIGENT_TRANSLATION_BASE_URL
=
'/doc-detail-pdf-api'
const
INTELLIGENT_TRANSLATION_HEADERS
=
{
'Client-ID'
:
'test_client'
,
'X-API-Key'
:
'test_key'
}
export
function
submitTranslationTask
(
file
)
{
const
formData
=
new
FormData
()
formData
.
append
(
'files'
,
file
)
return
request
({
url
:
`
${
INTELLIGENT_TRANSLATION_BASE_URL
}
/submit`
,
method
:
'POST'
,
data
:
formData
,
headers
:
INTELLIGENT_TRANSLATION_HEADERS
})
}
export
function
queryTranslationTaskStatus
(
taskId
)
{
return
request
({
url
:
`
${
INTELLIGENT_TRANSLATION_BASE_URL
}
/status/
${
taskId
}
`
,
method
:
'GET'
,
headers
:
INTELLIGENT_TRANSLATION_HEADERS
})
}
export
function
queryTranslationTaskResult
(
taskId
)
{
return
request
({
url
:
`
${
INTELLIGENT_TRANSLATION_BASE_URL
}
/result/
${
taskId
}
`
,
method
:
'GET'
,
headers
:
INTELLIGENT_TRANSLATION_HEADERS
})
}
src/views/intelligentTranslation/documentCompare.vue
浏览文件 @
50a8c95b
...
...
@@ -59,35 +59,68 @@
class=
"page-block"
:class=
"
{ 'page-block--active': currentPageId === page.pageId }"
>
<div
class=
"page-block__header"
>
{{
page
.
pageTitle
}}
</div>
<div
class=
"page-block__body"
>
<div
class=
"page-block__body"
:class=
"
{ 'page-block__body--absolute': page.pageLayoutMode === 'absolute' }">
<template
v-if=
"page.pageLayoutMode === 'absolute'"
>
<div
class=
"page-column"
>
<div
class=
"page-canvas-wrapper"
>
<div
class=
"page-canvas"
>
<article
v-for=
"item in page.items"
:key=
"`$
{page.pageId}-${item.id}`"
class="page-item"
:key=
"`$
{page.pageId}-${item.id}-en`"
class="page-node page-node--en"
:class="[getBlockVariantClass(item.blockType), shouldCenterBlock(item) ? 'page-node--center' : '', ENABLE_DEBUG_BOX ? 'page-node--debug' : '']"
:style="getBlockStyle(item)"
:data-section-id="item.id"
>
<div
class=
"page-item__text page-item__text--en"
:class=
"[getBlockVariantClass(item.type), shouldCenterBlock(item) ? 'page-item__text--center' : '']
"
>
<div
v-if=
"item.t
ype === 'table' && item.tableHtml"
class=
"page-item__html"
v-html=
"item.tableHtml"
></div>
<div
class=
"page-node__content
"
>
<div
v-if=
"item.blockT
ype === 'table' && item.tableHtml"
class=
"page-item__html"
v-html=
"item.tableHtml"
></div>
<img
v-else-if=
"item.type === 'image' && item.previewImgPath
"
v-else-if=
"item.blockType === 'image'
"
class=
"page-item__image"
:src=
"item.previewImgPath
"
:src=
"getEnglishImageSrc(item)
"
alt=
""
@
error=
"onEnglishImageError(item)"
/>
<div
v-else
class=
"page-item__content"
v-html=
"renderBlockHtml(item.text, item.styleSpans)"
></div>
</div>
<div
class=
"page-item__text page-item__text--zh"
:class=
"[getBlockVariantClass(item.type), shouldCenterBlock(item) ? 'page-item__text--center' : '']"
>
<div
v-if=
"item.type === 'table' && item.translatedTableHtml"
class=
"page-item__html"
v-html=
"item.translatedTableHtml"
></div>
<span
v-if=
"ENABLE_DEBUG_BOX"
class=
"page-node__debug-label"
>
{{
getBlockTypeLabel
(
item
.
blockType
)
}}
</span>
</article>
</div>
</div>
</div>
<div
class=
"page-column"
>
<div
class=
"page-canvas-wrapper"
>
<div
class=
"page-canvas"
>
<slot
name=
"zh-page-canvas"
:page=
"page"
></slot>
</div>
</div>
</div>
</
template
>
<
template
v-else
>
<article
v-for=
"item in page.items"
:key=
"`$
{page.pageId}-${item.id}`"
class="page-item"
:data-section-id="item.id"
>
<div
class=
"page-item__text page-item__text--en"
:class=
"[getBlockVariantClass(item.blockType), shouldCenterBlock(item) ? 'page-item__text--center' : '']"
>
<div
v-if=
"item.blockType === 'table' && item.tableHtml"
class=
"page-item__html"
v-html=
"item.tableHtml"
></div>
<img
v-else-if=
"item.type === 'image' && item.previewImgPath
"
v-else-if=
"item.blockType === 'image'
"
class=
"page-item__image"
:src=
"item.previewImgPath
"
:src=
"getEnglishImageSrc(item)
"
alt=
""
@
error=
"onEnglishImageError(item)"
/>
<div
v-else
class=
"page-item__content"
v-html=
"renderBlockHtml(item.translatedText || item.text || '(待接入译文)', item.styleSpans)"
></div>
<div
v-else
class=
"page-item__content"
v-html=
"renderBlockHtml(item.text, item.styleSpans)"
></div>
</div>
<div
class=
"page-item__text page-item__text--zh"
:class=
"[getBlockVariantClass(item.blockType), shouldCenterBlock(item) ? 'page-item__text--center' : '']"
>
<slot
name=
"zh-flow-item"
:item=
"item"
:page=
"page"
></slot>
</div>
</article>
</
template
>
</div>
</section>
</div>
...
...
@@ -107,6 +140,9 @@ import comparePlaceholderUrl from './icons/container-1885-placeholder.png'
import
mergedAns
from
'./merged_ans.json'
const
route
=
useRoute
()
const
ENABLE_ABSOLUTE_LAYOUT
=
true
const
ENABLE_DEBUG_BOX
=
false
const
PAGE_BBOX_VALID_THRESHOLD
=
0.7
const
ESTIMATED_SECONDS
=
65
const
headerActions
=
[
...
...
@@ -128,60 +164,34 @@ const isCompareLoading = ref(true)
const
elapsedSeconds
=
ref
(
0
)
const
translationProgress
=
ref
(
0
)
const
timerId
=
ref
(
null
)
const
imageLoadErrorMap
=
ref
({})
const
treeProps
=
{
label
:
'title'
,
children
:
'children'
}
const
translatedTextLookup
=
new
Map
([
[
'page0_node0'
,
'I. 基本信息'
],
[
'page0_node1'
,
'美国能源部(DOE)'
],
[
'page0_node2'
,
'科学办公室(SC)、关键矿产与能源创新办公室(CMEI)、环境管理办公室(EM)、电力办公室(OE)、核能办公室(NE)以及碳氢化合物与地热能源办公室(HGEO)'
],
[
'page0_node3'
,
'执行摘要'
],
[
'page0_node4'
,
'DOE 相关办公室现公开征集跨学科团队申请,围绕 Genesis Mission 国家科学与技术挑战,推动利用新型人工智能模型和框架加速科学发现与研发流程。'
],
[
'page0_node5'
,
'DOE 正在征集 FY26 第一阶段小团队与第二阶段大团队申请,涵盖先进制造、生物技术、关键材料、核裂变、核聚变、量子信息科学、半导体与微电子、发现科学和能源等领域。'
],
[
'page0_node6'
,
'本次 RFA 将继续开放,允许 FY26 第一阶段获奖者申请更大规模的第二阶段资助。'
],
[
'page0_node7'
,
'在对应截止日期之后,仍可提交第一阶段和第二阶段申请,但 DOE 保留拒绝此类申请且不予审查的权利。'
],
[
'page0_node8'
,
'1. Genesis Mission 联盟成员'
],
[
'page0_node9'
,
'Genesis Mission 联盟成员可提交申请,资金将通过 DOE 的其他交易授权(OTA)提供。'
],
[
'page1_node0'
,
'就 OTA 而言,本 RFA 的部分行政规定可能不适用于申请人或其分包商。'
],
[
'page1_node1'
,
'请注意,是否属于 Genesis Mission 联盟成员并非申请本 RFA 或参与入选项目的必要条件。'
],
[
'page1_node2'
,
'所有申请人注意事项:'
],
[
'page1_node3'
,
'获得本 RFA 下的奖励并不赋予,也不要求申请人加入 Genesis Mission 联盟。'
],
[
'page1_node4'
,
'申请人可在申请中提议非本土实体作为分包方。'
],
[
'page1_node5'
,
'资助详情'
],
[
'page1_node6'
,
'预计总可用资金、奖项数量、单项资助金额与项目周期见表。'
],
[
'page1_node7'
,
'关键信息'
],
[
'page1_node8'
,
'RFA 名称、编号、公告类型、援助清单、法定授权与适用法规。'
],
[
'page2_node0'
,
'表格信息摘要'
],
[
'page2_node1'
,
'关键日期'
],
[
'page2_node2'
,
'关键日期见本 RFA 封面页。'
],
[
'page2_node3'
,
'提交组队要求'
],
[
'page2_node4'
,
'本 RFA 要求提交内容为多机构团队。'
],
[
'page2_node5'
,
'机构联系信息'
],
[
'page2_node6'
,
'Grants.gov 客服与项目联系人信息。'
],
[
'page2_node7'
,
'说明性网络研讨会 / 办公时间'
],
[
'page2_node8'
,
'DOE 将于 2026 年 3 月 26 日星期四下午 3 点(东部时间)举办说明性网络研讨会。'
],
[
'page2_node9'
,
'建议'
],
[
'page2_node10'
,
'建议尽早完成各系统注册并尽早提交申请。'
],
[
'page2_node11'
,
'3'
]
])
const
displayFileName
=
computed
(()
=>
{
const
fileName
=
route
.
query
.
fileName
return
typeof
fileName
===
'string'
&&
fileName
?
fileName
:
'未命名文档(占位)'
})
const
compareLoadingText
=
computed
(()
=>
`文档解析中,预计用时
${
ESTIMATED_SECONDS
}
秒,已用时
${
elapsedSeconds
.
value
}
秒...`
)
const
pageBlocks
=
computed
(()
=>
{
return
normalizedPages
.
value
.
map
((
page
)
=>
({
return
normalizedPages
.
value
.
map
((
page
)
=>
{
const
pageItems
=
normalizedBlocks
.
value
.
filter
((
block
)
=>
block
.
pageIdx
===
page
.
pageIdx
)
.
sort
((
a
,
b
)
=>
a
.
readingOrder
-
b
.
readingOrder
)
const
absoluteCount
=
pageItems
.
filter
((
item
)
=>
item
.
layoutMode
===
'absolute'
).
length
const
hitRate
=
pageItems
.
length
?
absoluteCount
/
pageItems
.
length
:
0
const
pageLayoutMode
=
ENABLE_ABSOLUTE_LAYOUT
&&
pageItems
.
length
>
0
&&
hitRate
>=
PAGE_BBOX_VALID_THRESHOLD
?
'absolute'
:
'flow'
return
{
pageId
:
`page-
${
page
.
pageIdx
}
`
,
pageTitle
:
`第
${
page
.
pageIdx
+
1
}
页`
,
items
:
normalizedBlocks
.
value
.
filter
((
block
)
=>
block
.
pageIdx
===
page
.
pageIdx
)
}))
pageLayoutMode
,
items
:
pageItems
}
})
})
function
shouldCenterBlock
(
block
)
{
if
(
!
block
)
return
false
if
(
block
.
t
ype
!==
'heading'
)
return
false
if
(
block
.
blockT
ype
!==
'heading'
)
return
false
const
text
=
String
(
block
.
text
||
''
).
trim
()
if
(
!
text
)
return
false
const
strongHeading
=
block
.
headingLevel
!=
null
...
...
@@ -210,6 +220,39 @@ function getBlockVariantClass(type) {
return
`document-block--
${
type
||
'unknown'
}
`
}
function
isValidBboxNorm
(
rawBboxNorm
)
{
if
(
!
Array
.
isArray
(
rawBboxNorm
)
||
rawBboxNorm
.
length
!==
4
)
return
false
const
[
l
,
t
,
r
,
b
]
=
rawBboxNorm
.
map
((
value
)
=>
Number
(
value
))
if
(
!
[
l
,
t
,
r
,
b
].
every
((
value
)
=>
Number
.
isFinite
(
value
)))
return
false
if
(
l
<
0
||
t
<
0
||
r
>
1
||
b
>
1
)
return
false
if
(
r
<=
l
||
b
<=
t
)
return
false
return
true
}
function
normalizeBboxFromPixel
(
rawBbox
,
pageMeta
)
{
if
(
!
Array
.
isArray
(
rawBbox
)
||
rawBbox
.
length
!==
4
)
return
[]
const
pageWidth
=
Number
(
pageMeta
?.
width
||
0
)
const
pageHeight
=
Number
(
pageMeta
?.
height
||
0
)
if
(
pageWidth
<=
0
||
pageHeight
<=
0
)
return
[]
const
[
l
,
t
,
r
,
b
]
=
rawBbox
.
map
((
value
)
=>
Number
(
value
))
if
(
!
[
l
,
t
,
r
,
b
].
every
((
value
)
=>
Number
.
isFinite
(
value
)))
return
[]
return
[
l
/
pageWidth
,
t
/
pageHeight
,
r
/
pageWidth
,
b
/
pageHeight
]
}
function
getBlockStyle
(
item
)
{
if
(
!
isValidBboxNorm
(
item
?.
bboxNorm
))
return
{}
const
[
l
,
t
,
r
,
b
]
=
item
.
bboxNorm
const
baseHeightPercent
=
(
b
-
t
)
*
100
return
{
left
:
`
${
l
*
100
}
%`
,
top
:
`
${
t
*
100
}
%`
,
width
:
`
${(
r
-
l
)
*
100
}
%`
,
height
:
`
${
Math
.
min
(
100
-
t
*
100
,
baseHeightPercent
)}
%`
,
position
:
'absolute'
,
overflow
:
'hidden'
}
}
function
escapeHtml
(
text
=
''
)
{
return
String
(
text
)
.
replaceAll
(
'&'
,
'&'
)
...
...
@@ -247,9 +290,42 @@ function renderBlockHtml(text = '', styleSpans = []) {
return
html
.
replaceAll
(
'
\
n'
,
'<br />'
)
}
function
resolveImageUrl
(
rawPath
)
{
const
path
=
String
(
rawPath
||
''
).
trim
()
if
(
!
path
)
return
''
if
(
/^
(
https
?
:
)?\/\/
/i
.
test
(
path
)
||
/^
(
data|blob
)
:/i
.
test
(
path
))
return
path
const
normalizedPath
=
path
.
replaceAll
(
'
\
\'
, '
/
')
const baseUrl = String(
import.meta.env.VITE_INTELLIGENT_TRANSLATION_ASSET_BASE_URL || import.meta.env.VITE_BASE_FILE_URL || ''
).trim()
if (baseUrl) {
return `${baseUrl.replace(/
\
/+$/, '')}/${normalizedPath.replace(/^
\
/+/, '')}`
}
return normalizedPath.startsWith('
/
') ? normalizedPath : `/${normalizedPath}`
}
function getEnglishImageSrc(item) {
if (!item?.id) return comparePlaceholderUrl
if (imageLoadErrorMap.value[item.id]) return comparePlaceholderUrl
const resolvedUrl = resolveImageUrl(item.imagePath)
return resolvedUrl || comparePlaceholderUrl
}
function onEnglishImageError(item) {
if (!item?.id) return
imageLoadErrorMap.value = {
...imageLoadErrorMap.value,
[item.id]: true
}
}
function buildNormalizedBlocks(rawData) {
const pages = Array.isArray(rawData?.data?.pages) ? rawData.data.pages : []
const nodes = Array.isArray(rawData?.data?.merged_nodes) ? rawData.data.merged_nodes : []
const pageSizeMap = pages.reduce((acc, page) => {
acc[page.page_idx] = { width: page.width, height: page.height }
return acc
}, {})
normalizedPages.value = pages.map((page) => ({
pageIdx: page.page_idx,
...
...
@@ -261,25 +337,36 @@ function buildNormalizedBlocks(rawData) {
normalizedBlocks.value = nodes.map((node, index) => {
const contentPayload = node?.content_payload || {}
const
type
=
mapSemanticTypeToNodeType
(
node
.
semantic_type
,
contentPayload
)
const blockType = mapSemanticTypeToNodeType(node.semantic_type, contentPayload)
const bbox = Array.isArray(node?.layout?.bbox) ? node.layout.bbox : []
const originBboxNorm = Array.isArray(node?.layout?.bbox_norm) ? node.layout.bbox_norm : []
const pageMeta = pageSizeMap[node.page_idx] || null
const convertedBboxNorm = normalizeBboxFromPixel(bbox, pageMeta)
const bboxNorm = isValidBboxNorm(originBboxNorm) ? originBboxNorm : (isValidBboxNorm(convertedBboxNorm) ? convertedBboxNorm : [])
const layoutMode = isValidBboxNorm(bboxNorm) ? '
absolute
' : '
flow
'
return {
id: node.node_id || `node_${index}`,
nodeId: node.node_id || '',
pageIdx: node.page_idx ?? null,
type
,
type: blockType,
blockType,
semanticType: node.semantic_type || '
unknown
',
readingOrder: node?.layout?.reading_order ?? index,
title: contentPayload.text || node.node_id || `未命名块${index + 1}`,
text: contentPayload.text || '',
translatedText
:
translatedTextLookup
.
get
(
node
.
node_id
)
||
''
,
translatedText: '',
html: contentPayload.html || '',
headingLevel: contentPayload.heading_level ?? null,
tableHtml: contentPayload.table_body_html || '',
translatedTableHtml: contentPayload.table_body_html || '',
imagePath: contentPayload.img_path || contentPayload.preview_img_path || '',
previewImgPath: contentPayload.preview_img_path || '',
bbox,
bboxNorm,
layoutMode,
layout: {
bbox
:
node
?.
layout
?.
bbox
||
[]
,
bboxNorm
:
node
?.
layout
?.
bbox_norm
||
[]
,
bbox,
bboxNorm,
angle: node?.layout?.angle ?? 0,
link: node?.layout?.link ?? null,
zIndex: node?.layout?.z_index ?? null
...
...
@@ -297,7 +384,7 @@ function buildNavigationTree(rawData) {
const nodeType = mapSemanticTypeToNodeType(node.semantic_type, contentPayload)
const text = contentPayload.text || node.node_id || '
未命名块
'
if (nodeType !== '
heading
') return null
if
(
!
shouldCenterBlock
({
...
node
,
text
}))
return
null
if (!shouldCenterBlock({ ...node,
blockType: nodeType, headingLevel: contentPayload.heading_level,
text })) return null
return {
id: node.node_id || `heading_${index}`,
title: text,
...
...
@@ -353,7 +440,7 @@ function getFirstNavigableNode(nodes) {
return nodes.find((node) => node?.id) || null
}
onMounted
(
async
()
=>
{
onMounted(() => {
const requestStartTime = Date.now()
const updateElapsedSeconds = () => {
elapsedSeconds.value = Math.floor((Date.now() - requestStartTime) / 1000)
...
...
@@ -371,6 +458,8 @@ onMounted(async () => {
currentNodeKey.value = firstNode.id
currentPageId.value = `page-${firstNode.pageIdx ?? 0}`
}
} catch (error) {
ElMessage.error(error?.message || '
本地
JSON
加载失败
')
} finally {
isCompareLoading.value = false
}
...
...
@@ -451,21 +540,46 @@ onUnmounted(() => {
.sync-compare-scroll
{
flex
:
1
;
min-width
:
0
;
min-height
:
0
;
overflow-y
:
auto
;
overflow-x
:
hidden
;
border
:
1px
solid
#e8ecf2
;
border-radius
:
8px
;
background
:
#fff
;
padding
:
16px
;
box-sizing
:
border-box
;
}
.page-block
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
padding
:
12px
0
20px
;
border-bottom
:
1px
solid
#d9e2ec
;
}
.page-block--active
{
background
:
#f8fafc
;
}
.page-block__header
{
font-family
:
'Source Han Sans CN'
,
sans-serif
;
font-size
:
15px
;
font-weight
:
700
;
line-height
:
24px
;
color
:
#101828
;
}
.page-block__body
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
}
.page-block__body--absolute
{
flex-direction
:
row
;
align-items
:
flex-start
;
}
.page-column
{
width
:
calc
(
50%
-
6px
);
min-width
:
0
;
}
.page-canvas-wrapper
{
width
:
100%
;
}
.page-canvas
{
position
:
relative
;
width
:
100%
;
aspect-ratio
:
21
/
29
.7
;
border
:
1px
solid
#e4e7ec
;
border-radius
:
8px
;
overflow
:
hidden
;
background
:
#fff
;
}
.page-node
{
position
:
absolute
;
box-sizing
:
border-box
;
padding
:
0
;
}
.page-node--en
{
border-right
:
1px
solid
#eef2f7
;
}
.page-node__content
{
width
:
100%
;
height
:
100%
;
overflow
:
hidden
;
padding
:
1px
2px
4px
;
box-sizing
:
border-box
;
}
.page-node--center
.page-item__content
{
text-align
:
center
;
}
.page-node--debug
{
border
:
1px
dashed
#1677ff
;
}
.page-node__debug-label
{
position
:
absolute
;
left
:
0
;
top
:
0
;
font-size
:
10px
;
line-height
:
14px
;
padding
:
0
4px
;
color
:
#fff
;
background
:
rgba
(
22
,
119
,
255
,
0
.8
);
}
.page-item
{
display
:
grid
;
grid-template-columns
:
1fr
1fr
;
gap
:
12px
;
align-items
:
stretch
;
}
.page-item__text
{
min-height
:
72px
;
padding
:
0
;
background
:
transparent
;
border
:
none
;
}
.page-item__text--en
{
border-right
:
1px
solid
#eef2f7
;
padding-right
:
12px
;
}
.page-item__text--zh
{
padding-left
:
12px
;
}
.page-item__text--center
.page-item__content
{
text-align
:
center
;
}
.page-item__content
{
font-size
:
14px
;
line-height
:
1
.8
;
color
:
#344054
;
white-space
:
pre-wrap
;
word-break
:
break-word
;
}
.page-item__content
{
font-size
:
14px
;
line-height
:
1
.8
5
;
color
:
#344054
;
white-space
:
pre-wrap
;
word-break
:
break-word
;
}
.page-item__html
:deep
(
table
)
{
width
:
100%
;
border-collapse
:
collapse
;
font-size
:
13px
;
}
.page-item__html
:deep
(
td
),
.page-item__html
:deep
(
th
)
{
border
:
1px
solid
#d0d5dd
;
padding
:
8px
;
vertical-align
:
top
;
}
.page-item__image
{
max-width
:
100%
;
display
:
block
;
border-radius
:
6px
;
}
.page-item__image
{
width
:
100%
;
height
:
100%
;
object-fit
:
contain
;
display
:
block
;
border-radius
:
6px
;
}
.page-item--heading
.page-item__content
{
font-size
:
18px
;
font-weight
:
700
;
color
:
#101828
;
}
.page-item--paragraph
.page-item__content
{
font-size
:
14px
;
}
.page-item--table
.page-item__content
{
font-size
:
13px
;
}
.page-item--image
.page-item__content
{
font-size
:
13px
;
color
:
#667085
;
}
.page-item--page_number
.page-item__content
{
font-size
:
12px
;
color
:
#98a2b3
;
}
.page-item--unknown
.page-item__content
{
color
:
#667085
;
font-style
:
italic
;
}
.page-node--en.document-block--heading
.page-item__content
,
.page-node--en.document-block--paragraph
.page-item__content
{
line-height
:
1
.9
;
padding-bottom
:
3px
;
}
.page-node--en.document-block--table
.page-item__content
,
.page-node--en.document-block--page_number
.page-item__content
{
line-height
:
1
.9
;
padding-bottom
:
3px
;
}
@media
(
max-width
:
1360px
)
{
.page-block__body
{
gap
:
10px
;
}
.page-block__body--absolute
{
flex-direction
:
column
;
}
.page-column
{
width
:
100%
;
}
}
</
style
>
src/views/intelligentTranslation/index.vue
浏览文件 @
50a8c95b
...
...
@@ -10,8 +10,8 @@
<img
class=
"workspace-toolbar-icon"
src=
"@/assets/icons/tool-item-icon2.png"
alt=
""
/>
<span
class=
"workspace-toolbar-heading"
>
智能翻译
</span>
</div>
<button
class=
"translate-btn"
type=
"button"
@
click=
"onTranslate"
>
AI翻译
<button
class=
"translate-btn"
type=
"button"
:disabled=
"submitLoading"
@
click=
"onTranslate"
>
{{
submitLoading
?
'提交中...'
:
'AI翻译'
}}
</button>
</div>
...
...
@@ -22,7 +22,7 @@
<div
v-else
class=
"upload-file-panel"
>
<ul
class=
"upload-file-list"
>
<li
v-for=
"(file, index) in selectedFiles"
:key=
"`$
{file.name}-${
index
}`" class="upload-file-item">
<li
v-for=
"(file, index) in selectedFiles"
:key=
"`$
{file.name}-${
file.lastModified}-${file.size
}`" class="upload-file-item">
<img
class=
"upload-file-type-icon"
:src=
"fileTypeIconUrl"
alt=
""
/>
<span
class=
"upload-file-name"
>
{{
file
.
name
}}
</span>
<button
type=
"button"
class=
"upload-file-item-delete"
aria-label=
"删除该文件"
@
click=
"removeFileAt(index)"
>
...
...
@@ -90,8 +90,10 @@
<
script
setup
>
import
{
computed
,
ref
}
from
'vue'
import
{
ElMessage
}
from
'element-plus'
import
{
useRouter
}
from
'vue-router'
import
IntelligenceLeftTabBar
from
'@/components/intelligenceLeftTabBar/index.vue'
import
{
submitTranslationTask
}
from
'@/api/intelligentTranslation/index'
import
{
uploadRecordMock
}
from
'./mock'
import
fileTypeIconUrl
from
'./icons/file-type-icon.png'
import
lineUploadIconSvg
from
'./icons/Line_Upload.svg?raw'
...
...
@@ -106,24 +108,45 @@ const sourceText = ref('')
const
translatedText
=
ref
(
'点击“翻译”后展示占位结果'
)
const
selectedFiles
=
ref
([])
const
uploadRecords
=
ref
(
uploadRecordMock
)
const
submitLoading
=
ref
(
false
)
const
isTextMode
=
computed
(()
=>
selectedFiles
.
value
.
length
===
0
)
const
onTranslate
=
()
=>
{
const
onTranslate
=
async
()
=>
{
if
(
isTextMode
.
value
)
{
const
text
=
sourceText
.
value
.
trim
()
translatedText
.
value
=
text
?
`【Mock译文】
${
text
}
`
:
'【Mock译文】当前没有输入文本,请先填写原文内容。'
ElMessage
.
warning
(
'请先上传 PDF 文件后再发起翻译'
)
return
}
const
targetFile
=
selectedFiles
.
value
[
0
]
if
(
!
targetFile
)
{
ElMessage
.
warning
(
'请先上传 PDF 文件'
)
return
}
if
(
submitLoading
.
value
)
{
return
}
submitLoading
.
value
=
true
try
{
const
response
=
await
submitTranslationTask
(
targetFile
)
const
taskId
=
response
?.
data
?.
task_id
||
response
?.
data
?.
task_ids
?.[
0
]
if
(
!
taskId
)
{
throw
new
Error
(
'未获取到任务ID'
)
}
router
.
push
({
name
:
'intelligentTranslationDocument'
,
query
:
{
fileName
:
selectedFiles
.
value
[
0
]?.
name
||
''
taskId
,
fileName
:
targetFile
.
name
||
''
}
})
}
catch
(
error
)
{
ElMessage
.
error
(
error
?.
message
||
'翻译任务提交失败,请稍后重试'
)
}
finally
{
submitLoading
.
value
=
false
}
}
const
onFileChange
=
(
event
)
=>
{
...
...
@@ -291,6 +314,11 @@ function formatRecordDateOnly(timeStr) {
cursor
:
pointer
;
}
.translate-btn
:disabled
{
opacity
:
0
.7
;
cursor
:
not
-
allowed
;
}
.workspace-source-block
{
box-sizing
:
border-box
;
flex
:
1
;
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论