Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
7c629762
提交
7c629762
authored
3月 24, 2026
作者:
hsx
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat:新闻的高亮实体使用站内搜索
上级
7f04e169
显示空白字符变更
内嵌
并排
正在显示
4 个修改的文件
包含
153 行增加
和
115 行删除
+153
-115
IntelligentEntityText.vue
src/components/base/texts/IntelligentEntityText.vue
+44
-43
TextTranslatePane.vue
src/components/base/texts/TextTranslatePane.vue
+43
-29
comprehensiveSearch.js
src/router/modules/comprehensiveSearch.js
+9
-10
NewsDetial.vue
src/views/newsBrief/NewsDetial.vue
+57
-33
没有找到文件。
src/components/base/texts/IntelligentEntityText.vue
浏览文件 @
7c629762
<
template
>
<p
class=
"p-regular-rereg"
>
<span
class=
"text-regular"
v-for=
"(segment, index) in processedText"
:key=
"index"
>
<a
v-if=
"segment.isEntity"
:href=
"`https://cn.bing.com/search?q=$
{segment.entity?.text_span}`"
class="entity-link" target="_blank" rel="noopener noreferrer">
<span
v-if=
"segment.isEntity"
@
click=
"$emit('onEntityClick', segment.entity)"
class=
"entity-link"
>
{{
segment
.
entity
?.
text_span
}}
<img
:src=
"SearchIcon"
:width=
"10"
:height=
"10"
alt=
"search"
/>
</a
>
</span
>
<span
v-else
>
{{
segment
.
text
}}
</span>
...
...
@@ -13,110 +12,112 @@
</p>
</
template
>
<
script
lang=
"ts"
setup
>
import
{
TextEntity
}
from
'@/api/intelligent'
;
import
{
ref
,
watch
,
onMounted
}
from
'vue'
;
import
SearchIcon
from
'./images/search.png'
import
{
TextEntity
}
from
"@/api/intelligent"
;
import
{
ref
,
watch
,
onMounted
}
from
"vue"
;
import
SearchIcon
from
"./images/search.png"
;
export
interface
ProcessedTextSegment
{
text
:
string
isEntity
:
boolean
entity
?:
TextEntity
text
:
string
;
isEntity
:
boolean
;
entity
?:
TextEntity
;
}
const
props
=
defineProps
({
text
:
{
type
:
String
,
default
:
''
default
:
""
},
entities
:
{
type
:
Array
<
TextEntity
>
,
default
:
()
=>
[]
}
})
})
;
const
emit
=
defineEmits
([
"onEntityClick"
]);
// 处理后的文本段
const
processedText
=
ref
<
ProcessedTextSegment
[]
>
([])
const
processedText
=
ref
<
ProcessedTextSegment
[]
>
([]);
// 处理文本,识别并替换实体
const
processText
=
()
=>
{
console
.
log
(
'props.entities.length'
,
props
.
entities
.
length
)
console
.
log
(
"props.entities.length"
,
props
.
entities
.
length
);
if
(
!
props
.
text
||
!
props
.
entities
)
{
// console.log('props.text', props.entities.length)
processedText
.
value
=
[{
text
:
''
,
isEntity
:
false
}]
return
processedText
.
value
=
[{
text
:
""
,
isEntity
:
false
}];
return
;
}
const
result
=
[]
let
currentPosition
=
0
const
result
=
[];
let
currentPosition
=
0
;
// 按实体文本长度排序,优先匹配长文本
const
sortedEntities
=
[...
props
.
entities
].
sort
((
a
,
b
)
=>
b
.
text_span
.
length
-
a
.
text_span
.
length
)
const
sortedEntities
=
[...
props
.
entities
].
sort
((
a
,
b
)
=>
b
.
text_span
.
length
-
a
.
text_span
.
length
);
while
(
currentPosition
<
props
.
text
.
length
)
{
let
matched
=
false
let
matched
=
false
;
for
(
const
entity
of
sortedEntities
)
{
const
entityText
=
entity
.
text_span
const
endPosition
=
currentPosition
+
entityText
.
length
const
entityText
=
entity
.
text_span
;
const
endPosition
=
currentPosition
+
entityText
.
length
;
if
(
props
.
text
.
substring
(
currentPosition
,
endPosition
)
===
entityText
)
{
// 如果当前位置是实体,添加到结果
result
.
push
({
isEntity
:
true
,
entity
:
{
...
entity
}
})
currentPosition
=
endPosition
matched
=
true
break
});
currentPosition
=
endPosition
;
matched
=
true
;
break
;
}
}
if
(
!
matched
)
{
// 如果不是实体,收集普通文本
let
nextEntityStart
=
props
.
text
.
length
let
nextEntityStart
=
props
.
text
.
length
;
for
(
const
entity
of
sortedEntities
)
{
const
pos
=
props
.
text
.
indexOf
(
entity
.
text_span
,
currentPosition
)
const
pos
=
props
.
text
.
indexOf
(
entity
.
text_span
,
currentPosition
);
if
(
pos
!==
-
1
&&
pos
<
nextEntityStart
)
{
nextEntityStart
=
pos
nextEntityStart
=
pos
;
}
}
if
(
nextEntityStart
>
currentPosition
)
{
const
plainText
=
props
.
text
.
substring
(
currentPosition
,
nextEntityStart
)
const
plainText
=
props
.
text
.
substring
(
currentPosition
,
nextEntityStart
);
result
.
push
({
text
:
plainText
,
isEntity
:
false
})
currentPosition
=
nextEntityStart
});
currentPosition
=
nextEntityStart
;
}
else
{
// 没有更多实体,添加剩余文本
const
remainingText
=
props
.
text
.
substring
(
currentPosition
)
const
remainingText
=
props
.
text
.
substring
(
currentPosition
);
if
(
remainingText
)
{
result
.
push
({
text
:
remainingText
,
isEntity
:
false
})
});
}
currentPosition
=
props
.
text
.
length
currentPosition
=
props
.
text
.
length
;
}
}
}
processedText
.
value
=
result
}
processedText
.
value
=
result
;
}
;
// 监听文本和实体变化
watch
(()
=>
props
.
text
,
processText
)
watch
(()
=>
props
.
entities
,
processText
,
{
deep
:
true
})
watch
(()
=>
props
.
text
,
processText
)
;
watch
(()
=>
props
.
entities
,
processText
,
{
deep
:
true
})
;
// 初始化处理
onMounted
(
processText
)
onMounted
(
processText
)
;
</
script
>
<
style
lang=
"scss"
scoped
>
@use
'@/styles/common.scss'
;
@use
"@/styles/common.scss"
;
.entity-link
{
color
:
var
(
--
color-primary-100
);
&
:hover
{
cursor
:
pointer
;
}
}
.p-regular-rereg
{
...
...
src/components/base/texts/TextTranslatePane.vue
浏览文件 @
7c629762
<
template
>
<div
class=
"full-width"
>
<div
class=
"flex-display"
style=
"align-items: center;"
>
<common-text
class=
"text-title-3-bold"
color=
"var(--text-primary-80-color)"
>
{{
isOpenTranslation
?
'中文'
:
'原文'
}}
</common-text>
<div
class=
"flex-fill"
style=
"margin: 0 10px;"
>
<div
class=
"flex-display"
style=
"align-items: center"
>
<common-text
class=
"text-title-3-bold"
color=
"var(--text-primary-80-color)"
>
{{
isOpenTranslation
?
"中文"
:
"原文"
}}
</common-text>
<div
class=
"flex-fill"
style=
"margin: 0 10px"
>
<el-divider></el-divider>
</div>
<el-button
v-if=
"showMoreVisible"
@
click=
"() =>
{ showMore = !showMore; updateText() }">
{{
showMore
?
'收起'
:
'展开'
}}
<el-button
v-if=
"showMoreVisible"
@
click=
"
() =>
{
showMore = !showMore;
updateText();
}
"
>
{{
showMore
?
"收起"
:
"展开"
}}
<el-icon>
<arrow-up
v-if=
"showMore"
/>
<arrow-down
v-else
/>
...
...
@@ -17,21 +26,24 @@
<el-row
:gutter=
"32"
>
<el-col
:span=
"textColSpan"
v-for=
"(item, index) in allTexts"
:key=
"index"
>
<!--
<p
class=
"p-news-content"
>
{{
item
}}
</p>
-->
<intelligent-entity-text
:text=
"item"
:entities=
"isHighlightEntity ? textEntities : []"
></intelligent-entity-text>
<intelligent-entity-text
:text=
"item"
@
on-entity-click=
"e => $emit('onEntityClick', e)"
:entities=
"isHighlightEntity ? textEntities : []"
></intelligent-entity-text>
</el-col>
</el-row>
</div>
</
template
>
<
script
lang=
"ts"
setup
>
import
'@/styles/container.scss'
;
import
'@/styles/common.scss'
;
import
"@/styles/container.scss"
;
import
"@/styles/common.scss"
;
import
{
ref
,
watch
,
onMounted
}
from
'vue'
;
import
{
TextEntity
}
from
'@/api/intelligent'
;
import
IntelligentEntityText
from
'@/components/base/texts/IntelligentEntityText.vue'
;
import
{
ElIcon
,
ElButton
,
ElDivider
,
ElRow
,
ElCol
}
from
'element-plus'
;
import
CommonText
from
'./CommonText.vue'
;
import
{
ref
,
watch
,
onMounted
}
from
"vue"
;
import
{
TextEntity
}
from
"@/api/intelligent"
;
import
IntelligentEntityText
from
"@/components/base/texts/IntelligentEntityText.vue"
;
import
{
ElIcon
,
ElButton
,
ElDivider
,
ElRow
,
ElCol
}
from
"element-plus"
;
import
CommonText
from
"./CommonText.vue"
;
const
allTexts
=
ref
([]);
const
textColSpan
=
ref
(
12
);
...
...
@@ -64,30 +76,30 @@ const props = defineProps({
type
:
Array
<
TextEntity
>
,
default
:
()
=>
[]
}
})
})
;
const
emit
=
defineEmits
([
"onEntityClick"
]);
function
updateText
()
{
const
tempTexts
=
[]
const
tempRaws
=
props
.
textsRaw
??
[]
const
tempTranslates
=
props
.
textsTranslate
??
[]
hasTranslation
.
value
=
tempTranslates
.
length
>
0
const
tempTexts
=
[];
const
tempRaws
=
props
.
textsRaw
??
[];
const
tempTranslates
=
props
.
textsTranslate
??
[];
hasTranslation
.
value
=
tempTranslates
.
length
>
0
;
if
(
hasTranslation
.
value
&&
props
.
isOpenTranslation
)
{
// 遍历原始文本和翻译文本,将它们交替添加到 tempTexts 中,并保持原始文本和翻译文本的的数量一致
const
maxCount
=
Math
.
max
(
tempRaws
.
length
,
tempTranslates
.
length
)
const
maxCount
=
Math
.
max
(
tempRaws
.
length
,
tempTranslates
.
length
);
for
(
let
i
=
0
;
i
<
maxCount
;
i
++
)
{
if
(
i
<
tempTranslates
.
length
)
{
tempTexts
.
push
(
tempTranslates
[
i
]);
}
else
{
tempTexts
.
push
(
''
);
tempTexts
.
push
(
""
);
}
if
(
i
<
tempRaws
.
length
)
{
tempTexts
.
push
(
tempRaws
[
i
]);
}
else
{
tempTexts
.
push
(
''
);
tempTexts
.
push
(
""
);
}
}
console
.
log
(
tempTexts
.
length
)
console
.
log
(
tempTexts
.
length
);
textColSpan
.
value
=
12
;
showMoreVisible
.
value
=
tempTexts
.
length
>
6
;
allTexts
.
value
=
showMore
.
value
?
tempTexts
:
tempTexts
.
slice
(
0
,
6
);
...
...
@@ -98,12 +110,14 @@ function updateText() {
}
}
watch
(()
=>
[
props
.
textsRaw
,
props
.
textsTranslate
,
props
.
isOpenTranslation
],
()
=>
{
watch
(
()
=>
[
props
.
textsRaw
,
props
.
textsTranslate
,
props
.
isOpenTranslation
],
()
=>
{
updateText
();
})
}
);
onMounted
(()
=>
{
updateText
();
})
})
;
</
script
>
src/router/modules/comprehensiveSearch.js
浏览文件 @
7c629762
// 综合搜索
const
ComprehensiveSearch
=
()
=>
import
(
'@/views/comprehensiveSearch/index.vue'
)
const
SearchResults
=
()
=>
import
(
'@/views/comprehensiveSearch/searchResults/index.vue'
)
const
Chat
=
()
=>
import
(
'@/views/comprehensiveSearch/chat/index.vue'
)
const
ComprehensiveSearch
=
()
=>
import
(
"@/views/comprehensiveSearch/index.vue"
);
const
SearchResults
=
()
=>
import
(
"@/views/comprehensiveSearch/searchResults/index.vue"
);
const
Chat
=
()
=>
import
(
"@/views/comprehensiveSearch/chat/index.vue"
);
const
comprehensiveSearchRoutes
=
[
// 综合搜索
...
...
@@ -29,19 +29,19 @@ const comprehensiveSearchRoutes = [
meta
:
{
title
:
"智能问答"
}
},
]
}
];
import
{
useGotoPage
}
from
"../common.js"
;
export
function
useGotoComprehensiveSearch
()
{
const
gotoPage
=
useGotoPage
();
return
(
isNewTabs
=
true
)
=>
gotoPage
(
"/comprehensiveSearch/"
,
{},
isNewTabs
)
return
(
isNewTabs
=
true
)
=>
gotoPage
(
"/comprehensiveSearch/"
,
{},
isNewTabs
);
}
export
function
useGotoSearchResults
()
{
const
gotoPage
=
useGotoPage
();
return
(
isNewTabs
=
true
)
=>
gotoPage
(
"/searchResults/"
,
{
searchText
,
areaName
},
isNewTabs
)
return
(
searchText
,
areaName
,
isNewTabs
=
true
)
=>
gotoPage
(
"/searchResults/"
,
{
searchText
,
areaName
},
isNewTabs
);
}
export
default
comprehensiveSearchRoutes
\ No newline at end of file
export
default
comprehensiveSearchRoutes
;
src/views/newsBrief/NewsDetial.vue
浏览文件 @
7c629762
<
template
>
<el-scrollbar>
<div
class=
"flex-display common-page top-box-news-deatail"
style=
"align-items: center;
"
>
<div
class=
"flex-display common-page top-box-news-deatail"
style=
"align-items: center
"
>
<!--
<color-svg
:svg-url=
"NewsLogo"
:size=
"72"
style=
"margin-right:10px"
></color-svg>
-->
<img
:src=
"NewsLogo"
style=
"margin-right:24px"
>
<img
:src=
"NewsLogo"
style=
"margin-right: 24px"
/
>
<el-space
direction=
"vertical"
class=
"flex-fill"
alignment=
"flex-start"
>
<common-text
class=
"text-title-1-bold"
color=
"var(--text-primary-80-color)"
>
{{
newsDetail
.
titleZh
}}
...
...
@@ -28,38 +28,63 @@
<
/div
>
<
div
class
=
"flex-display common-page content-box-news-detail"
>
<
el
-
space
direction
=
"vertical"
class
=
"background-as-card flex-fill"
fill
alignment
=
"flex-start"
>
<
div
style
=
"margin-top: 10px; margin-right: 24px;gap:
10px"
class
=
"flex-display"
>
<
div
style
=
"margin-top: 10px; margin-right: 24px; gap:
10px"
class
=
"flex-display"
>
<
color
-
prefix
-
title
height
=
"20px"
>
<
div
class
=
"text-title-2-bold"
>
新闻内容
<
/div
>
<
/color-prefix-title
>
<
div
class
=
"flex-fill"
><
/div
>
<
el
-
switch
v
-
model
=
"isHightLightEntity"
active
-
text
=
"高亮实体"
@
change
=
"handleHighlightEntity"
/>
<
el
-
button
v
-
if
=
"textZns.length > 0"
:
type
=
"isOpenTranslation ? 'primary' : ''"
plain
@
click
=
"handleTranslation"
>
<
color
-
svg
:
svg
-
url
=
"TranslationSvg"
color
=
"var(--color-primary-100)"
:
size
=
"18"
style
=
"margin-right:10px"
><
/color-svg
>
<
el
-
button
v
-
if
=
"textZns.length > 0"
:
type
=
"isOpenTranslation ? 'primary' : ''"
plain
@
click
=
"handleTranslation"
>
<
color
-
svg
:
svg
-
url
=
"TranslationSvg"
color
=
"var(--color-primary-100)"
:
size
=
"18"
style
=
"margin-right: 10px"
><
/color-svg
>
译文
<
/el-button
>
<
/div
>
<
text
-
translate
-
pane
class
=
"common-padding-h"
:
texts
-
raw
=
"textEns"
:
texts
-
translate
=
"textZns"
:
text
-
entities
=
"textEntities"
:
is
-
open
-
translation
=
"isOpenTranslation"
:
is
-
highlight
-
entity
=
"isHightLightEntity"
>
<
text
-
translate
-
pane
class
=
"common-padding-h"
:
texts
-
raw
=
"textEns"
:
texts
-
translate
=
"textZns"
:
text
-
entities
=
"textEntities"
:
is
-
open
-
translation
=
"isOpenTranslation"
:
is
-
highlight
-
entity
=
"isHightLightEntity"
@
on
-
entity
-
click
=
"e => gotoSearchResults(e.text_span, '')"
>
<
/text-translate-pane
>
<
div
>
<
img
v
-
if
=
"newsDetail.coverUrl"
class
=
"common-padding"
:
src
=
"newsDetail.coverUrl"
:
width
=
"320"
:
height
=
"240"
/>
<
img
v
-
if
=
"newsDetail.coverUrl"
class
=
"common-padding"
:
src
=
"newsDetail.coverUrl"
:
width
=
"320"
:
height
=
"240"
/>
<
/div
>
<
/el-space
>
<
el
-
space
direction
=
"vertical"
class
=
"background-as-card relation-news-box"
fill
>
<
el
-
space
style
=
"margin-top: 10px;
"
>
<
el
-
space
style
=
"margin-top: 10px
"
>
<
color
-
prefix
-
title
height
=
"20px"
>
<
div
class
=
"text-title-2-bold"
>
相关新闻
<
/div
>
<
/color-prefix-title
>
<
/el-space
>
<
el
-
space
v
-
if
=
"relationNews?.length > 0"
direction
=
"vertical"
fill
class
=
"common-padding"
>
<
news
-
item
-
mini
v
-
for
=
"item in relationNews"
:
key
=
"item.newsId"
:
news
=
"item"
:
img
=
"item.newsImage"
:
title
=
"item.newsTitle"
:
from
=
"`${item.newsDate
}
· ${item.newsOrg
}
`"
@
click
=
"gotoNewsDetail(item.newsId)"
/>
<
news
-
item
-
mini
v
-
for
=
"item in relationNews"
:
key
=
"item.newsId"
:
news
=
"item"
:
img
=
"item.newsImage"
:
title
=
"item.newsTitle"
:
from
=
"`${item.newsDate
}
· ${item.newsOrg
}
`"
@
click
=
"gotoNewsDetail(item.newsId)"
/>
<
/el-space
>
<
el
-
empty
v
-
else
style
=
""
><
/el-empty
>
<
/el-space
>
...
...
@@ -68,21 +93,22 @@
<
/template
>
<
script
setup
>
import
{
getNewsDetail
}
from
"@/api/news/newsBrief"
;
import
'@/styles/container.scss'
;
import
'@/styles/common.scss'
;
import
"@/styles/container.scss"
;
import
"@/styles/common.scss"
;
import
{
ref
,
onMounted
}
from
"vue"
;
import
{
useRoute
}
from
"vue-router"
;
import
{
ElSpace
,
ElButton
,
ElScrollbar
,
ElSwitch
,
ElEmpty
,
ElImage
}
from
"element-plus"
;
import
CommonText
from
"@/components/base/texts/CommonText.vue"
;
import
AreaTag
from
"@/components/base/AreaTag/index.vue"
;
import
ColorPrefixTitle
from
'@/components/base/texts/ColorPrefixTitle.vue'
;
import
ColorPrefixTitle
from
"@/components/base/texts/ColorPrefixTitle.vue"
;
import
{
getRelationNews
}
from
"@/api/news/newsDetail"
;
import
NewsItemMini
from
"@/components/base/newsList/NewsItemMini.vue"
;
import
ColorSvg
from
"@/components/base/images/ColorSvg.vue"
;
import
TranslationSvg
from
'./assets/images/翻译 1.svg'
;
import
NewsLogo
from
'./assets/images/组合 293.svg'
;
import
TranslationSvg
from
"./assets/images/翻译 1.svg"
;
import
NewsLogo
from
"./assets/images/组合 293.svg"
;
import
{
extractTextEntity
}
from
"@/api/intelligent"
;
import
{
useGotoNewsDetail
}
from
"@/router/modules/news"
;
import
{
useGotoSearchResults
}
from
"@/router/modules/comprehensiveSearch"
;
import
TextTranslatePane
from
"@/components/base/texts/TextTranslatePane.vue"
;
const
newsDetail
=
ref
({
}
);
...
...
@@ -94,36 +120,34 @@ const textZns = ref([]);
const
textEns
=
ref
([]);
const
route
=
useRoute
();
const
gotoNewsDetail
=
useGotoNewsDetail
();
const
gotoSearchResults
=
useGotoSearchResults
();
onMounted
(
async
()
=>
{
const
params
=
{
newsId
:
route
.
params
.
id
}
}
;
const
{
data
:
newsDetailData
}
=
await
getNewsDetail
(
params
);
newsDetail
.
value
=
newsDetailData
??
{
}
;
textZns
.
value
=
newsDetail
.
value
?.
contentZh
?.
split
(
'
\
n'
)
??
[];
textEns
.
value
=
newsDetail
.
value
?.
content
?.
split
(
'
\
n'
)
??
[];
textZns
.
value
=
newsDetail
.
value
?.
contentZh
?.
split
(
"
\
n"
)
??
[];
textEns
.
value
=
newsDetail
.
value
?.
content
?.
split
(
"
\
n"
)
??
[];
const
{
data
:
relationNewsData
}
=
await
getRelationNews
(
params
);
relationNews
.
value
=
relationNewsData
??
[];
await
handleHighlightEntity
();
}
);
async
function
handleHighlightEntity
()
{
if
(
textEntities
.
value
.
length
>
0
||
(
!
newsDetail
.
value
?.
contentZh
&&
!
newsDetail
.
value
?.
content
))
return
const
{
result
:
entityDataZh
}
=
await
extractTextEntity
(
newsDetail
.
value
.
contentZh
??
''
);
textEntities
.
value
=
[...
entityDataZh
??
[]]
if
(
textEntities
.
value
.
length
>
0
||
(
!
newsDetail
.
value
?.
contentZh
&&
!
newsDetail
.
value
?.
content
))
return
;
const
{
result
:
entityDataZh
}
=
await
extractTextEntity
(
newsDetail
.
value
.
contentZh
??
""
);
textEntities
.
value
=
[...(
entityDataZh
??
[])];
if
(
newsDetail
.
value
.
contentZh
!==
newsDetail
.
value
.
content
)
{
const
{
result
:
entityData
}
=
await
extractTextEntity
(
newsDetail
.
value
.
content
??
''
);
textEntities
.
value
=
[...
textEntities
.
value
,
...
entityData
??
[]]
const
{
result
:
entityData
}
=
await
extractTextEntity
(
newsDetail
.
value
.
content
??
""
);
textEntities
.
value
=
[...
textEntities
.
value
,
...(
entityData
??
[])];
}
console
.
log
(
isHightLightEntity
.
value
)
console
.
log
(
isHightLightEntity
.
value
);
}
function
handleTranslation
()
{
isOpenTranslation
.
value
=
!
isOpenTranslation
.
value
;
}
<
/script
>
<
style
lang
=
"scss"
scoped
>
@
import
url
(
"./style.css"
);
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论