Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
08768dcd
提交
08768dcd
authored
3月 17, 2026
作者:
张烨
浏览文件
操作
浏览文件
下载
差异文件
代码合并
上级
17049543
7e175ede
隐藏空白字符变更
内嵌
并排
正在显示
11 个修改的文件
包含
898 行增加
和
857 行删除
+898
-857
download.png
src/components/base/DecreeOriginal/assets/icons/download.png
+0
-0
search.png
src/components/base/DecreeOriginal/assets/icons/search.png
+0
-0
index.vue
src/components/base/DecreeOriginal/index.vue
+396
-0
wordCloudChart.js
src/components/base/WordCloundChart/wordCloudChart.js
+22
-36
ProcessOverviewDetailDialog.vue
src/views/bill/ProcessOverviewDetailDialog.vue
+292
-0
index.vue
src/views/bill/background/index.vue
+2
-2
index.vue
src/views/bill/billHome/index.vue
+6
-6
index.vue
src/views/bill/deepDig/poliContribution/index.vue
+4
-59
index.vue
src/views/bill/deepDig/processOverview/index.vue
+7
-459
index.vue
src/views/bill/template/index.vue
+159
-4
index.vue
src/views/decree/decreeOriginal/index.vue
+10
-291
没有找到文件。
src/components/base/DecreeOriginal/assets/icons/download.png
0 → 100644
浏览文件 @
08768dcd
350 Bytes
src/components/base/DecreeOriginal/assets/icons/search.png
0 → 100644
浏览文件 @
08768dcd
399 Bytes
src/components/base/DecreeOriginal/index.vue
0 → 100644
浏览文件 @
08768dcd
<
template
>
<div
class=
"layout-container"
>
<div
class=
"layout-main"
>
<div
class=
"layout-main-center"
>
<div
class=
"report-header"
>
<div
class=
"report-title"
>
政令原文
</div>
<el-switch
v-model=
"isHighlight"
/>
<div
class=
"switch-label switch-label-left"
>
高亮实体
</div>
<el-switch
v-model=
"isTranslate"
/>
<div
class=
"switch-label"
>
原文显示
</div>
<div
class=
"btn"
@
click=
"emits('download')"
>
<div
class=
"icon icon-gap-4"
>
<img
:src=
"defaultDownloadIcon"
alt=
""
/>
</div>
<div
class=
"text"
>
下载
</div>
</div>
<div
class=
"btn"
@
click=
"handleFindWord('open')"
>
<div
class=
"icon icon-gap-6"
>
<img
:src=
"defaultSearchIcon"
alt=
""
/>
</div>
<div
class=
"text"
>
查找
</div>
</div>
<div
class=
"find-word-box"
v-if=
"findWordBox"
>
<div
class=
"find-word-input"
>
<el-input
v-model=
"findWordTxt"
placeholder=
"查找原文内容"
@
input=
"handleUpdateWord"
/>
</div>
<div
class=
"find-word-limit"
>
{{
findWordNum
}}
/
{{
findWordMax
}}
</div>
<div
class=
"find-word-icon"
@
click=
"handleFindWord('last')"
>
<el-icon><ArrowUp
/></el-icon>
</div>
<div
class=
"find-word-icon"
@
click=
"handleFindWord('next')"
>
<el-icon><ArrowDown
/></el-icon>
</div>
<div
class=
"find-word-icon"
@
click=
"handleFindWord('close')"
>
<el-icon><Close
/></el-icon>
</div>
</div>
</div>
<div
class=
"report-main"
>
<div
v-if=
"!displayReportData.length"
class=
"noContent"
>
{{
"暂无数据"
}}
</div>
<el-scrollbar
height=
"100%"
v-else
>
<div
v-for=
"item in displayReportData"
:key=
"item.num"
:class=
"['content-row',
{ 'high-light': isHighlight }]"
>
<div
:class=
"['content-cn',
{ 'translate-cn': !isTranslate }]" v-html="item.content">
</div>
<div
class=
"content-en"
v-html=
"item.contentEn"
v-if=
"isTranslate"
></div>
</div>
</el-scrollbar>
</div>
</div>
</div>
</div>
</
template
>
<
script
setup
>
import
defaultDownloadIcon
from
"./assets/icons/download.png"
;
import
defaultSearchIcon
from
"./assets/icons/search.png"
;
import
{
nextTick
,
ref
,
watch
}
from
"vue"
;
import
{
debounce
}
from
"lodash"
;
// 该组件为公共展示组件:网络请求/下载等业务由父组件处理;查找、高亮、显示切换等通用交互在组件内部封装。
// 图标资源固定使用组件内置文件,保证组件资源自包含。
const
props
=
defineProps
({
reportData
:
{
type
:
Array
,
default
:
()
=>
[]
},
});
const
emits
=
defineEmits
([
"download"
]);
const
isHighlight
=
ref
(
false
);
const
isTranslate
=
ref
(
true
);
const
findWordTxt
=
ref
(
""
);
const
findWordBox
=
ref
(
false
);
const
findWordNum
=
ref
(
0
);
const
findWordMax
=
ref
(
0
);
const
originReportData
=
ref
([]);
const
displayReportData
=
ref
([]);
function
escapeRegExp
(
text
)
{
return
text
.
replace
(
/
[
.*+?^${}()|[
\]\\]
/g
,
"
\\
$&"
);
}
function
applyHighlightToText
(
text
,
searchTerm
)
{
if
(
!
text
||
!
searchTerm
)
{
return
{
html
:
text
||
""
,
count
:
0
};
}
const
escapedTerm
=
escapeRegExp
(
searchTerm
);
let
count
=
0
;
const
html
=
String
(
text
).
replace
(
new
RegExp
(
escapedTerm
,
"g"
),
(
match
)
=>
{
count
+=
1
;
return
`<span class="highlight">
${
match
}
</span>`
;
});
return
{
html
,
count
};
}
function
setDisplayFromOrigin
()
{
displayReportData
.
value
=
originReportData
.
value
.
map
((
item
)
=>
({
...
item
,
content
:
item
.
content
||
""
,
contentEn
:
item
.
contentEn
||
""
,
}));
}
function
updateActiveHighlight
()
{
const
spans
=
document
.
querySelectorAll
(
"span.highlight"
);
spans
.
forEach
((
span
,
index
)
=>
{
if
(
index
+
1
===
findWordNum
.
value
)
{
span
.
scrollIntoView
({});
span
.
style
.
backgroundColor
=
"#ff9632"
;
}
else
{
span
.
style
.
backgroundColor
=
"#ffff00"
;
}
});
}
const
doUpdateWord
=
()
=>
{
findWordNum
.
value
=
0
;
findWordMax
.
value
=
0
;
const
term
=
findWordTxt
.
value
?.
trim
();
if
(
!
term
)
{
setDisplayFromOrigin
();
return
;
}
displayReportData
.
value
=
originReportData
.
value
.
map
((
item
)
=>
{
const
cn
=
applyHighlightToText
(
item
.
content
,
term
);
const
en
=
isTranslate
.
value
?
applyHighlightToText
(
item
.
contentEn
,
term
)
:
{
html
:
item
.
contentEn
,
count
:
0
};
findWordMax
.
value
+=
cn
.
count
+
en
.
count
;
return
{
...
item
,
content
:
cn
.
html
,
contentEn
:
en
.
html
,
};
});
if
(
findWordMax
.
value
>
0
)
{
nextTick
(()
=>
{
findWordNum
.
value
=
1
;
updateActiveHighlight
();
});
}
};
const
handleUpdateWord
=
debounce
(()
=>
{
doUpdateWord
();
},
300
);
function
handleFindWord
(
event
)
{
switch
(
event
)
{
case
"open"
:
findWordBox
.
value
=
true
;
break
;
case
"last"
:
if
(
findWordMax
.
value
>
1
)
{
findWordNum
.
value
=
findWordNum
.
value
===
1
?
findWordMax
.
value
:
findWordNum
.
value
-
1
;
updateActiveHighlight
();
}
break
;
case
"next"
:
if
(
findWordMax
.
value
>
1
)
{
findWordNum
.
value
=
findWordNum
.
value
===
findWordMax
.
value
?
1
:
findWordNum
.
value
+
1
;
updateActiveHighlight
();
}
break
;
case
"close"
:
findWordBox
.
value
=
false
;
findWordTxt
.
value
=
""
;
doUpdateWord
();
break
;
}
}
watch
(
()
=>
props
.
reportData
,
(
val
)
=>
{
originReportData
.
value
=
(
val
||
[]).
map
((
item
)
=>
({
content
:
item
?.
content
||
""
,
contentEn
:
item
?.
contentEn
||
""
,
num
:
item
?.
num
,
}));
setDisplayFromOrigin
();
doUpdateWord
();
},
{
deep
:
true
,
immediate
:
true
},
);
watch
(
isTranslate
,
()
=>
{
doUpdateWord
();
});
</
script
>
<
style
lang=
"scss"
scoped
>
.high-light
{
:deep
(
span
.highlight
)
{
background-color
:
#ffff00
;
}
}
.layout-container
{
width
:
100%
;
height
:
100%
;
background-color
:
#f7f8f9
;
.layout-main
{
height
:
100%
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
.layout-main-center
{
width
:
1600px
;
background-color
:
white
;
padding
:
0
60px
;
height
:
20px
;
flex
:
auto
;
display
:
flex
;
flex-direction
:
column
;
.report-header
{
height
:
80px
;
display
:
flex
;
align-items
:
center
;
border-bottom
:
solid
1px
rgba
(
234
,
236
,
238
,
1
);
margin
:
0
20px
10px
;
position
:
relative
;
.find-word-box
{
position
:
absolute
;
top
:
-50px
;
right
:
0px
;
width
:
430px
;
height
:
60px
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
background-color
:
white
;
border-radius
:
6px
;
display
:
flex
;
align-items
:
center
;
.find-word-input
{
width
:
20px
;
flex
:
auto
;
}
.find-word-limit
{
border-right
:
solid
1px
rgba
(
230
,
231
,
232
,
1
);
color
:
#5f656c
;
padding-right
:
16px
;
}
.find-word-icon
{
padding
:
10px
12px
;
margin
:
0
2px
;
cursor
:
pointer
;
}
}
.report-title
{
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
20px
;
line-height
:
20px
;
font-weight
:
700
;
width
:
20px
;
flex
:
auto
;
}
.btn
{
margin-left
:
10px
;
width
:
88px
;
height
:
32px
;
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
border-radius
:
6px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
cursor
:
pointer
;
.text
{
height
:
24px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
font-family
:
Microsoft
YaHei
;
font-style
:
Regular
;
font-size
:
14px
;
font-weight
:
400
;
line-height
:
24px
;
letter-spacing
:
0px
;
text-align
:
left
;
}
.icon
{
width
:
16px
;
height
:
16px
;
font-size
:
0px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.icon-gap-4
{
margin-right
:
4px
;
}
.icon-gap-6
{
margin-right
:
6px
;
}
}
}
.report-main
{
height
:
20px
;
flex
:
auto
;
box-sizing
:
border-box
;
padding-top
:
10px
;
&
:
:-
webkit-scrollbar
{
display
:
none
;
}
&
:
:-
webkit-scrollbar-thumb
{
background
:
#c1c1c1
;
border-radius
:
4px
;
}
&
:
:-
webkit-scrollbar-track
{
background
:
#f1f1f1
;
}
.noContent
{
height
:
100%
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
Microsoft
YaHei
;
font-style
:
Regular
;
font-size
:
20px
;
font-weight
:
400
;
}
.content-row
{
display
:
flex
;
width
:
100%
;
padding
:
0
20px
;
min-height
:
100px
;
gap
:
80px
;
&
:last-child
{
border-bottom
:
none
;
}
.content-en
,
.content-cn
{
width
:
50%
;
flex
:
auto
;
padding-bottom
:
40px
;
box-sizing
:
border-box
;
font-size
:
16px
;
line-height
:
1
.8
;
color
:
#3b414b
;
font-family
:
Microsoft
YaHei
;
text-align
:
justify
;
white-space
:
pre-wrap
;
}
.translate-cn
{
padding-bottom
:
10px
;
}
}
}
}
}
}
.switch-label
{
margin-left
:
6px
;
}
.switch-label-left
{
margin-right
:
10px
;
}
:deep
(
.el-scrollbar__bar.is-vertical
)
{
right
:
0px
;
width
:
4px
;
background
:
transparent
;
border-radius
:
2px
;
&
>
div
{
background
:
#c5c7c9
;
opacity
:
1
;
}
&
>
div
:hover
{
background
:
#505357
;
}
}
</
style
>
src/components/base/WordCloundChart/wordCloudChart.js
浏览文件 @
08768dcd
import
'echarts-wordcloud'
;
import
{
MUTICHARTCOLORS
}
from
'@/common/constant'
;
const
getWordCloudChart
=
(
data
)
=>
{
const
option
=
{
const
getWordCloudChart
=
data
=>
{
const
option
=
{
grid
:
{
left
:
5
,
top
:
5
,
...
...
@@ -12,9 +12,9 @@ const getWordCloudChart = (data) => {
series
:
[
{
type
:
"wordCloud"
,
shape
:
'circle'
,
width
:
'100%'
,
height
:
'100%'
,
shape
:
"circle"
,
width
:
"100%"
,
height
:
"100%"
,
// 其他形状你可以使用形状路径
// shape: 'circle', // 示例
// 或者自定义路径
...
...
@@ -22,43 +22,29 @@ const getWordCloudChart = (data) => {
sizeRange
:
[
16
,
36
],
// 定义词云中文字大小的范围
rotationRange
:
[
0
,
0
],
rotationStep
:
0
,
drawOutOfBound
:
false
,
// 是否超出画布
shrinkToFit
:
true
,
// 是否自动缩小以适应容器
drawOutOfBound
:
false
,
// 是否超出画布
shrinkToFit
:
true
,
// 是否自动缩小以适应容器
// 字体
textStyle
:
{
// normal: {
// color: function () {
// return 'rgb(' + [
// Math.round(Math.random() * 160),
// Math.round(Math.random() * 160),
// Math.round(Math.random() * 160)
// ].join(',') + ')';
// }
// },
color
:
function
()
{
// let colors = [
// "rgba(189, 33, 33, 1)",
// "rgba(232, 151, 21, 1)",
// "rgba(220, 190, 68, 1)",
// "rgba(96, 58, 186, 1)",
// "rgba(32, 121, 69, 1)",
// "rgba(22, 119, 255, 1)",
// ];
// let colors = MUTICHARTCOLORS
return
MUTICHARTCOLORS
[
parseInt
(
Math
.
random
()
*
MUTICHARTCOLORS
.
length
)];
color
:
function
(
params
)
{
const
colors
=
MUTICHARTCOLORS
||
[];
if
(
!
colors
.
length
)
{
return
"#69B1FF"
;
}
return
colors
[
params
.
dataIndex
%
colors
.
length
];
},
emphasis
:
{
shadowBlur
:
5
,
shadowColor
:
"#333"
,
}
,
shadowColor
:
"#333"
}
},
// 设置词云数据
data
:
data
,
}
,
]
,
data
}
]
};
return
option
}
return
option
;
}
;
export
default
getWordCloudChart
\ No newline at end of file
export
default
getWordCloudChart
;
\ No newline at end of file
src/views/bill/ProcessOverviewDetailDialog.vue
0 → 100644
浏览文件 @
08768dcd
<
template
>
<div
v-if=
"visible"
ref=
"dialogRef"
class=
"dialog-wrapper"
:style=
"position"
>
<div
class=
"dialog-box1"
@
mousedown=
"handleMouseDown"
>
<div
class=
"icon"
>
<img
v-if=
"detailItem.orgName === '参议院'"
:src=
"logoSenate"
alt=
""
/>
<img
v-else
:src=
"logoHouse"
alt=
""
/>
</div>
<div
class=
"title"
>
<div
class=
"date"
>
{{
detailItem
.
actionDate
}}
</div>
<div
class=
"text"
>
{{
detailItem
.
actionTitle
}}
</div>
</div>
<div
class=
"close"
@
click=
"handleClose"
>
<img
:src=
"closeIcon"
alt=
""
/>
</div>
</div>
<div
class=
"dialog-box2"
v-if=
"detailItem.agreeVote !== null || detailItem.disagreeVote !== null"
>
<div
class=
"vote-bar"
>
<div
class=
"agree-bar"
:style=
"
{ flex: detailItem.agreeVote || 1 }">
</div>
<div
class=
"disagree-bar"
:style=
"
{ flex: detailItem.disagreeVote || 1 }">
</div>
</div>
<div
class=
"vote-text"
>
<div
class=
"agree-text"
>
{{
(
detailItem
.
agreeVote
||
0
)
+
"赞成"
}}
</div>
<div
class=
"disagree-text"
>
{{
(
detailItem
.
disagreeVote
||
0
)
+
"反对"
}}
</div>
</div>
</div>
<template
v-if=
"detailItem.fynrList && detailItem.fynrList.length"
>
<div
class=
"dialog-box4"
>
<div
class=
"box4-left"
>
<div
class=
"icon"
>
<img
:src=
"changeIcon"
alt=
""
/>
</div>
<div
class=
"text"
>
{{
"变更条款"
}}
</div>
</div>
</div>
<div
class=
"dialog-box5"
>
<div
class=
"box5-item"
v-for=
"(sub, subIndex) in detailItem.fynrList"
:key=
"subIndex + '-' + sub"
>
<div
class=
"icon"
></div>
<div
class=
"text"
>
{{
sub
}}
</div>
</div>
</div>
</
template
>
</div>
</template>
<
script
setup
>
import
{
ref
}
from
"vue"
;
defineProps
({
visible
:
{
type
:
Boolean
,
default
:
false
,
},
detailItem
:
{
type
:
Object
,
default
:
()
=>
({}),
},
position
:
{
type
:
Object
,
default
:
()
=>
({
left
:
"0px"
,
top
:
"0px"
}),
},
});
const
emit
=
defineEmits
([
"close"
]);
const
logoSenate
=
new
URL
(
"@/views/bill/deepDig/processOverview/assets/images/logo1.png"
,
import
.
meta
.
url
).
href
;
const
logoHouse
=
new
URL
(
"@/views/bill/deepDig/processOverview/assets/images/logo2.png"
,
import
.
meta
.
url
).
href
;
const
closeIcon
=
new
URL
(
"@/views/bill/deepDig/processOverview/assets/images/close.png"
,
import
.
meta
.
url
).
href
;
const
changeIcon
=
new
URL
(
"@/views/bill/deepDig/processOverview/assets/images/dialog-box4-icon.png"
,
import
.
meta
.
url
).
href
;
const
dialogRef
=
ref
(
null
);
const
handleClose
=
()
=>
{
emit
(
"close"
);
};
const
handleMouseDown
=
(
e
)
=>
{
const
dialog
=
dialogRef
.
value
;
if
(
!
dialog
)
return
;
const
startX
=
e
.
clientX
;
const
startY
=
e
.
clientY
;
const
initialLeft
=
dialog
.
offsetLeft
;
const
initialTop
=
dialog
.
offsetTop
;
const
move
=
(
moveEvent
)
=>
{
const
deltaX
=
moveEvent
.
clientX
-
startX
;
const
deltaY
=
moveEvent
.
clientY
-
startY
;
dialog
.
style
.
right
=
"auto"
;
dialog
.
style
.
left
=
`
${
initialLeft
+
deltaX
}
px`
;
dialog
.
style
.
top
=
`
${
initialTop
+
deltaY
}
px`
;
};
const
stop
=
()
=>
{
document
.
removeEventListener
(
"mousemove"
,
move
);
document
.
removeEventListener
(
"mouseup"
,
stop
);
};
document
.
addEventListener
(
"mousemove"
,
move
);
document
.
addEventListener
(
"mouseup"
,
stop
);
};
</
script
>
<
style
lang=
"scss"
scoped
>
.dialog-wrapper
{
position
:
absolute
;
width
:
480px
;
padding-bottom
:
20px
;
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
border-radius
:
4px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
z-index
:
10000
;
box-shadow
:
0px
4px
12px
rgba
(
0
,
0
,
0
,
0
.15
);
.dialog-box1
{
display
:
flex
;
min-height
:
90px
;
height
:
auto
;
padding-bottom
:
15px
;
position
:
relative
;
border-bottom
:
1px
solid
rgba
(
240
,
242
,
244
,
1
);
cursor
:
move
;
.icon
{
width
:
48px
;
height
:
48px
;
margin-left
:
18px
;
margin-top
:
20px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.title
{
margin-top
:
20px
;
margin-left
:
16px
;
width
:
350px
;
.date
{
height
:
22px
;
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
700
;
line-height
:
22px
;
}
.text
{
margin-top
:
4px
;
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
700
;
line-height
:
22px
;
span
{
color
:
rgba
(
95
,
101
,
108
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
14px
;
font-weight
:
400
;
}
}
}
.close
{
position
:
absolute
;
top
:
14px
;
right
:
15px
;
width
:
16px
;
height
:
16px
;
cursor
:
pointer
;
img
{
width
:
100%
;
height
:
100%
;
}
}
}
.dialog-box2
{
height
:
59px
;
padding
:
0
23px
;
.vote-bar
{
display
:
flex
;
height
:
4px
;
width
:
100%
;
margin-top
:
20px
;
border-radius
:
2px
;
overflow
:
hidden
;
.agree-bar
{
background
:
#52c41a
;
margin-right
:
2px
;
}
.disagree-bar
{
background
:
#f5222d
;
}
}
.vote-text
{
display
:
flex
;
justify-content
:
space-between
;
margin-top
:
8px
;
.agree-text
{
color
:
#52c41a
;
font-size
:
14px
;
}
.disagree-text
{
color
:
#f5222d
;
font-size
:
14px
;
}
}
}
.dialog-box4
{
width
:
438px
;
margin-left
:
23px
;
margin-top
:
20px
;
display
:
flex
;
justify-content
:
space-between
;
height
:
30px
;
.box4-left
{
display
:
flex
;
.icon
{
margin-top
:
6px
;
width
:
18px
;
height
:
18px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.text
{
width
:
64px
;
height
:
30px
;
margin-left
:
6px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
700
;
line-height
:
30px
;
}
}
}
.dialog-box5
{
margin-top
:
3px
;
margin-left
:
23px
;
width
:
438px
;
.box5-item
{
min-height
:
30px
;
color
:
rgba
(
132
,
136
,
142
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
400
;
line-height
:
1
.5
;
display
:
flex
;
margin-bottom
:
8px
;
.icon
{
flex-shrink
:
0
;
margin-left
:
15px
;
width
:
6px
;
height
:
6px
;
margin-top
:
9px
;
border-radius
:
3px
;
background
:
#84888e
;
}
.text
{
margin-left
:
10px
;
word-break
:
break-all
;
}
}
}
}
</
style
>
src/views/bill/background/index.vue
浏览文件 @
08768dcd
...
...
@@ -647,7 +647,7 @@ onMounted(() => {
.icon1
{
position
:
absolute
;
left
:
5
px
;
left
:
8
px
;
bottom
:
-8px
;
width
:
16px
;
height
:
16px
;
...
...
@@ -663,7 +663,7 @@ onMounted(() => {
.icon2
{
position
:
absolute
;
right
:
5
px
;
right
:
8
px
;
bottom
:
-8px
;
width
:
16px
;
height
:
16px
;
...
...
src/views/bill/billHome/index.vue
浏览文件 @
08768dcd
...
...
@@ -180,7 +180,8 @@
<OverviewCard
class=
"overview-card--single box9"
title=
"涉华法案关键条款"
:icon=
"box7HeaderIcon"
>
<div
class=
"overview-card-body box9-main"
>
<div
class=
"overview-chart-wrap"
>
<div
id=
"wordCloudChart"
class=
"overview-chart"
></div>
<el-empty
v-if=
"!wordCloudHasData"
description=
"暂无数据"
:image-size=
"100"
/>
<WordCloundChart
v-else
class=
"overview-chart"
width=
"100%"
height=
"100%"
:data=
"wordCloudData"
/>
</div>
<TipTab
class=
"overview-tip"
/>
</div>
...
...
@@ -197,8 +198,8 @@
</template>
<
script
setup
>
import
RiskSignal
from
"@/components/base/
R
iskSignal/index.vue"
;
import
{
onMounted
,
ref
,
onUnmounted
,
nextTick
,
watch
}
from
"vue"
;
import
RiskSignal
from
"@/components/base/
r
iskSignal/index.vue"
;
import
{
onMounted
,
ref
,
onUnmounted
,
nextTick
,
watch
,
computed
}
from
"vue"
;
import
router
from
"@/router/index"
;
import
setChart
from
"@/utils/setChart"
;
import
{
...
...
@@ -220,9 +221,9 @@ import OverviewCard from "./OverviewCard.vue";
import
ResourceLibrarySection
from
"./ResourceLibrarySection.vue"
;
import
{
useContainerScroll
}
from
"@/hooks/useScrollShow"
;
import
TipTab
from
"@/components/base/TipTab/index.vue"
;
import
WordCloundChart
from
"@/components/base/WordCloundChart/index.vue"
;
import
getMultiLineChart
from
"./utils/multiLineChart"
;
import
getWordCloudChart
from
"./utils/worldCloudChart"
;
import
getPieChart
from
"./utils/piechart"
;
import
getDoublePieChart
from
"./utils/doublePieChart"
;
...
...
@@ -637,6 +638,7 @@ const handleToSocialDetail = item => {
};
// 关键条款
const
wordCloudData
=
ref
([]);
const
wordCloudHasData
=
computed
(()
=>
Array
.
isArray
(
wordCloudData
.
value
)
&&
wordCloudData
.
value
.
length
>
0
);
const
handleGetKeyTK
=
async
()
=>
{
try
{
const
res
=
await
getBillOverviewKeyTK
();
...
...
@@ -655,8 +657,6 @@ const handleGetKeyTK = async () => {
};
const
handleBox6
=
async
()
=>
{
await
handleGetKeyTK
();
const
wordCloudChart
=
getWordCloudChart
(
wordCloudData
.
value
);
setChart
(
wordCloudChart
,
"wordCloudChart"
);
};
// 涉华领域分布
...
...
src/views/bill/deepDig/poliContribution/index.vue
浏览文件 @
08768dcd
...
...
@@ -111,48 +111,6 @@
</AnalysisBox>
</div>
<div
class=
"box3"
>
<!-- <div class="box-header">
<div class="icon"></div>
<div class="title">{{ "政治献金领域分布" }}</div>
<div class="header-right">
<div class="right-icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="right-icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="right-icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box3-main">
<div class="box3-main-left" id="chart2"></div>
<div class="box3-main-right">
<el-empty v-if="!areaList.length" description="暂无数据" :image-size="100" />
<div class="box3-main-right-item" v-for="(item, index) in areaList" :key="index">
<div class="id">{{ index + 1 }}</div>
<div class="name">{{ item.name }}</div>
<div class="line">
<div class="inner-line" :style="{ width: (item.num / areaList[0].num) * 100 + '%' }"></div>
</div>
<div class="num">{{ item.numtext }}</div>
<div class="more">{{ `${item.insNum}家机构 >` }}</div>
</div>
</div>
</div>
<div class="box-footer">
<div class="box-footer-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div>
<div class="box-footer-center">
{{ currentPersonName }}的政治资金主要依赖于一个由亿万富翁、特定行业利益集团及通过“超级政治行动委员会”
运作的大额捐款网络。
</div>
<div class="box-footer-right">
<img src="@/assets/icons/box-footer-right-icon.png" alt="" />
</div>
</div> -->
<AnalysisBox
title=
"政治献金领域分布"
>
<div
class=
"box3-main"
:class=
"{ 'box3-main-no-footer': !showHardcodedTips }"
>
<div
class=
"box3-main-left"
id=
"chart2"
></div>
...
...
@@ -196,6 +154,7 @@ import setChart from "@/utils/setChart";
import
getPieChart
from
"./utils/piechart"
;
import
getSankeyChart
from
"./utils/sankey"
;
import
{
MUTICHARTCOLORS
}
from
"@/common/constant"
;
import
Img1
from
"./assets/images/1.png"
;
import
Img2
from
"./assets/images/2.png"
;
...
...
@@ -434,23 +393,9 @@ const topAreaList = computed(() => {
return
areaList
.
value
.
slice
(
0
,
5
);
});
const
chart2ColorList
=
ref
([
"#4096FF"
,
"#FFA39E"
,
"#ADC6FF"
,
"#FFC069"
,
"#B5F5EC"
,
"#B37FEB"
,
"#D6E4FF"
]);
const
sankeyColors
=
[
"#5470c6"
,
"#91cc75"
,
"#fac858"
,
"#ee6666"
,
"#73c0de"
,
"#3ba272"
,
"#fc8452"
,
"#9a60b4"
,
"#ea7ccc"
,
"#a2c0f1"
,
"#f596aa"
,
"#e6b422"
,
"#4b2c20"
];
const
chart2ColorList
=
ref
([...
MUTICHARTCOLORS
]);
const
sankeyColors
=
[...
MUTICHARTCOLORS
];
const
partyContributionList
=
ref
([
{
...
...
src/views/bill/deepDig/processOverview/index.vue
浏览文件 @
08768dcd
...
...
@@ -220,101 +220,12 @@
</div>
-->
</AnalysisBox>
<div
class=
"dialog-wrapper"
v-if=
"isShowDetailDialog"
ref=
"dialogRef"
:style=
"dialogPos"
>
<div
class=
"dialog-box1"
@
mousedown=
"handleMouseDown"
>
<div
class=
"icon"
>
<img
v-if=
"currentDetailItem.orgName === '参议院'"
src=
"./assets/images/logo1.png"
alt=
""
/>
<img
v-else
src=
"./assets/images/logo2.png"
alt=
""
/>
</div>
<div
class=
"title"
>
<div
class=
"date"
>
{{
currentDetailItem
.
actionDate
}}
</div>
<div
class=
"text"
>
{{
currentDetailItem
.
actionTitle
}}
</div>
</div>
<div
class=
"close"
@
click=
"handleClickDetail(false)"
>
<img
src=
"./assets/images/close.png"
alt=
""
/>
</div>
</div>
<div
class=
"dialog-box2"
v-if=
"currentDetailItem.agreeVote !== null || currentDetailItem.disagreeVote !== null"
>
<div
class=
"vote-bar"
>
<div
class=
"agree-bar"
:style=
"
{ flex: currentDetailItem.agreeVote || 1 }">
</div>
<div
class=
"disagree-bar"
:style=
"
{ flex: currentDetailItem.disagreeVote || 1 }">
</div>
</div>
<div
class=
"vote-text"
>
<div
class=
"agree-text"
>
{{
(
currentDetailItem
.
agreeVote
||
0
)
+
"赞成"
}}
</div>
<div
class=
"disagree-text"
>
{{
(
currentDetailItem
.
disagreeVote
||
0
)
+
"反对"
}}
</div>
</div>
</div>
<!--
<div
class=
"dialog-box3"
>
<div
class=
"box3-left"
>
<img
src=
"./assets/icons/right-icon1.png"
alt=
""
/>
</div>
<div
class=
"box3-center"
>
{{
"法案在众议院惊险通过,凸显两党分歧严重。"
}}
</div>
<div
class=
"box3-right"
>
<img
src=
"./assets/icons/arrow-right.png"
alt=
""
/>
</div>
</div>
-->
<template
v-if=
"currentDetailItem.fynrList && currentDetailItem.fynrList.length"
>
<div
class=
"dialog-box4"
>
<div
class=
"box4-left"
>
<div
class=
"icon"
>
<img
src=
"./assets/images/dialog-box4-icon.png"
alt=
""
/>
</div>
<div
class=
"text"
>
{{
"变更条款"
}}
</div>
</div>
<!--
<div
class=
"box4-right"
@
click=
"handleSwitchCompareDialog(true)"
>
{{
"条款对比 >"
}}
</div>
-->
</div>
<div
class=
"dialog-box5"
>
<div
class=
"box5-item"
v-for=
"(sub, subIndex) in currentDetailItem.fynrList"
:key=
"subIndex"
>
<div
class=
"icon"
></div>
<div
class=
"text"
>
{{
sub
}}
</div>
</div>
</div>
</
template
>
<!-- <div class="dialog-box6">
<div class="icon">
<img src="./assets/images/dialog-box6-icon.png" alt="" />
</div>
<div class="text">{{ "支持意见领袖" }}</div>
</div>
<div class="dialog-box7">
<div class="box7-item">
<div class="icon">
<img src="./assets/images/dialog-box7-icon1.png" alt="" />
<div class="inner-icon">
<img src="./assets/images/user-inner-icon.png" alt="" />
</div>
</div>
<div class="right">
<div class="name">{{ "迈克·约翰逊" }}</div>
<div class="content">
{{
"协调党内分歧:修改条款争取支持(如调整SALT扣除上限),推动程序性投票"
}}
</div>
</div>
</div>
<div class="box7-item">
<div class="icon">
<img src="./assets/images/dialog-box7-icon2.png" alt="" />
<div class="inner-icon">
<img src="./assets/images/user-inner-icon.png" alt="" />
</div>
</div>
<div class="right">
<div class="name">{{ "詹姆斯·戴维·万斯" }}</div>
<div class="content">
{{ "框架谈判主导:与约翰逊合作确立调和程序方向" }}
</div>
</div>
</div>
</div> -->
</div>
<ProcessOverviewDetailDialog
:visible=
"isShowDetailDialog"
:detailItem=
"currentDetailItem"
:position=
"dialogPos"
@
close=
"handleClickDetail(false)"
/>
<div
class=
"dialog-wrapper1"
v-if=
"isShowCompareDialog"
>
<div
class=
"dialog-header"
>
<div
class=
"header-left"
>
...
...
@@ -530,6 +441,7 @@
import
{
ref
,
onMounted
,
computed
}
from
"vue"
;
import
{
getBillDyqkSummary
}
from
"@/api/bill"
;
import
CommonPrompt
from
"../../commonPrompt/index.vue"
;
import
ProcessOverviewDetailDialog
from
"../../ProcessOverviewDetailDialog.vue"
;
// 获取法案流程
...
...
@@ -669,39 +581,6 @@ const handleClickDetail = (isShow, item = {}, event = null) => {
}
};
const
dialogRef
=
ref
(
null
);
const
handleMouseDown
=
(
e
)
=>
{
const
dialog
=
dialogRef
.
value
;
if
(
!
dialog
)
return
;
const
startX
=
e
.
clientX
;
const
startY
=
e
.
clientY
;
// 获取当前的 offsetLeft 和 offsetTop
// 注意:如果是第一次拖动,且原本是 right 定位的,offsetLeft 也是可以直接获取到的
const
initialLeft
=
dialog
.
offsetLeft
;
const
initialTop
=
dialog
.
offsetTop
;
const
move
=
(
moveEvent
)
=>
{
const
deltaX
=
moveEvent
.
clientX
-
startX
;
const
deltaY
=
moveEvent
.
clientY
-
startY
;
// 清除 right 定位,改用 left
dialog
.
style
.
right
=
'auto'
;
dialog
.
style
.
left
=
`
${
initialLeft
+
deltaX
}
px`
;
dialog
.
style
.
top
=
`
${
initialTop
+
deltaY
}
px`
;
};
const
stop
=
()
=>
{
document
.
removeEventListener
(
'mousemove'
,
move
);
document
.
removeEventListener
(
'mouseup'
,
stop
);
};
document
.
addEventListener
(
'mousemove'
,
move
);
document
.
addEventListener
(
'mouseup'
,
stop
);
};
// 挂载阶段调用
onMounted
(()
=>
{
getBillDyqkSummaryList
();
...
...
@@ -1382,337 +1261,6 @@ onMounted(() => {
}
}
.dialog-wrapper
{
position
:
absolute
;
width
:
480px
;
// height: 692px; // 去掉固定高度,由内容撑开
padding-bottom
:
20px
;
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
border-radius
:
4px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
z-index
:
10000
;
// 提高层级,超过节点的 9999
box-shadow
:
0px
4px
12px
rgba
(
0
,
0
,
0
,
0
.15
);
// 加个阴影更好看
.dialog-box1
{
display
:
flex
;
min-height
:
90px
;
// 改为最小高度,允许撑开
height
:
auto
;
// 高度自动
padding-bottom
:
15px
;
// 底部留白
position
:
relative
;
border-bottom
:
1px
solid
rgba
(
240
,
242
,
244
,
1
);
cursor
:
move
;
// 鼠标变为移动手势
.icon
{
width
:
48px
;
height
:
48px
;
margin-left
:
18px
;
margin-top
:
20px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.title
{
margin-top
:
20px
;
margin-left
:
16px
;
width
:
350px
;
.date
{
height
:
22px
;
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
700
;
line-height
:
22px
;
}
.text
{
margin-top
:
4px
;
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
700
;
line-height
:
22px
;
span
{
color
:
rgba
(
95
,
101
,
108
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
14px
;
font-weight
:
400
;
}
}
}
.close
{
position
:
absolute
;
top
:
14px
;
right
:
15px
;
width
:
16px
;
height
:
16px
;
cursor
:
pointer
;
img
{
width
:
100%
;
height
:
100%
;
}
}
}
.dialog-box2
{
height
:
59px
;
padding
:
0
23px
;
// border-bottom: 1px solid rgba(240, 242, 244, 1);
.vote-bar
{
display
:
flex
;
height
:
4px
;
width
:
100%
;
margin-top
:
20px
;
border-radius
:
2px
;
overflow
:
hidden
;
.agree-bar
{
background
:
#52C41A
;
margin-right
:
2px
;
}
.disagree-bar
{
background
:
#F5222D
;
}
}
.vote-text
{
display
:
flex
;
justify-content
:
space-between
;
margin-top
:
8px
;
.agree-text
{
color
:
#52C41A
;
font-size
:
14px
;
}
.disagree-text
{
color
:
#F5222D
;
font-size
:
14px
;
}
}
}
.dialog-box3
{
width
:
438px
;
height
:
46px
;
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
231
,
241
,
255
,
1
);
border-radius
:
4px
;
background
:
rgba
(
246
,
251
,
255
,
1
);
margin-left
:
23px
;
margin-top
:
15px
;
.box3-left
{
width
:
19px
;
height
:
20px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.box3-center
{
width
:
345px
;
margin-left
:
25px
;
color
:
var
(
--
color-main-active
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
400
;
line-height
:
46px
;
}
.box3-right
{
width
:
24px
;
height
:
24px
;
background
:
rgba
(
231
,
241
,
255
,
1
);
border-radius
:
12px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
}
.dialog-box4
{
width
:
438px
;
margin-left
:
23px
;
margin-top
:
20px
;
display
:
flex
;
justify-content
:
space-between
;
height
:
30px
;
.box4-left
{
display
:
flex
;
.icon
{
margin-top
:
6px
;
width
:
18px
;
height
:
18px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.text
{
width
:
64px
;
height
:
30px
;
margin-left
:
6px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
700
;
line-height
:
30px
;
}
}
.box4-right
{
height
:
30px
;
line-height
:
30px
;
color
:
rgba
(
20
,
89
,
187
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
14px
;
font-weight
:
400
;
cursor
:
pointer
;
}
}
.dialog-box5
{
margin-top
:
3px
;
margin-left
:
23px
;
width
:
438px
;
.box5-item
{
min-height
:
30px
;
color
:
rgba
(
132
,
136
,
142
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
400
;
line-height
:
1
.5
;
display
:
flex
;
margin-bottom
:
8px
;
.icon
{
flex-shrink
:
0
;
margin-left
:
15px
;
width
:
6px
;
height
:
6px
;
margin-top
:
9px
;
border-radius
:
3px
;
background
:
#84888e
;
}
.text
{
margin-left
:
10px
;
word-break
:
break-all
;
}
}
}
.dialog-box6
{
margin-top
:
30px
;
margin-left
:
20px
;
display
:
flex
;
height
:
30px
;
.icon
{
margin-top
:
6px
;
width
:
18px
;
height
:
18px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.text
{
color
:
rgba
(
95
,
101
,
108
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
700
;
line-height
:
30px
;
margin-left
:
6px
;
}
}
.dialog-box7
{
margin-left
:
21px
;
margin-top
:
3px
;
height
:
190px
;
width
:
440px
;
overflow
:
hidden
;
.box7-item
{
display
:
flex
;
.icon
{
width
:
42px
;
height
:
42px
;
position
:
relative
;
img
{
width
:
42px
;
height
:
42px
;
}
.inner-icon
{
position
:
absolute
;
top
:
0
;
right
:
-8px
;
width
:
24px
;
height
:
24px
;
box-sizing
:
border-box
;
padding
:
2px
;
border-radius
:
12px
;
background
:
rgba
(
255
,
255
,
255
,
0
.8
);
img
{
width
:
20px
;
height
:
20px
;
}
}
}
.right
{
margin-left
:
30px
;
.name
{
height
:
30px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
700
;
line-height
:
30px
;
}
.content
{
color
:
rgba
(
132
,
136
,
142
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
400
;
line-height
:
30px
;
}
}
}
}
}
.dialog-wrapper1
{
position
:
absolute
;
z-index
:
9999
;
...
...
src/views/bill/template/index.vue
浏览文件 @
08768dcd
...
...
@@ -100,11 +100,11 @@
<div
class=
"term-main"
>
<div
class=
"term-row term-row-cn"
>
<div
class=
"term-no-cn"
>
第{{ term.tkxh }}条.
</div>
<div
class=
"term-content-cn"
>
{{ term.fynr }}
</div>
<div
class=
"term-content-cn"
v-html=
"getTermContentHtml(term, 'cn')"
>
</div>
</div>
<div
class=
"term-row term-row-en"
v-if=
"termsShowOriginal"
>
<div
class=
"term-no-en"
>
Sec.{{ term.tkxh }}
</div>
<div
class=
"term-content-en"
>
{{ term.ywnr }}
</div>
<div
class=
"term-content-en"
v-html=
"getTermContentHtml(term, 'en')"
>
</div>
</div>
</div>
</div>
...
...
@@ -136,6 +136,8 @@ import * as echarts from "echarts";
import
{
Search
}
from
"@element-plus/icons-vue"
;
import
getPieChart
from
"./utils/piechart"
;
import
{
getBillContentId
,
getBillContentTk
,
getBillContentXzfs
,
getBillHyly
}
from
"@/api/bill"
;
import
{
MUTICHARTCOLORS
}
from
"@/common/constant"
;
import
{
extractTextEntity
}
from
"@/api/intelligent/index"
;
const
route
=
useRoute
();
...
...
@@ -161,6 +163,134 @@ const domainLoading = ref(false);
const
termsHighlight
=
ref
(
true
);
const
termsShowOriginal
=
ref
(
true
);
const
entityRequestToken
=
ref
(
0
);
const
termEntityCache
=
ref
(
new
Map
());
const
escapeHtml
=
value
=>
{
const
str
=
String
(
value
??
""
);
return
str
.
replace
(
/&/g
,
"&"
)
.
replace
(
/</g
,
"<"
)
.
replace
(
/>/g
,
">"
)
.
replace
(
/"/g
,
"""
)
.
replace
(
/'/g
,
"'"
);
};
const
normalizeEntities
=
entities
=>
{
const
list
=
Array
.
isArray
(
entities
)
?
entities
:
[];
return
list
.
map
(
item
=>
{
return
{
text_span
:
String
(
item
?.
text_span
??
""
).
trim
(),
type
:
String
(
item
?.
type
??
""
).
trim
()
};
})
.
filter
(
item
=>
item
.
text_span
);
};
const
getEntityRanges
=
(
text
,
entities
)
=>
{
const
ranges
=
[];
const
rawText
=
String
(
text
??
""
);
if
(
!
rawText
)
return
ranges
;
const
list
=
normalizeEntities
(
entities
).
sort
((
a
,
b
)
=>
b
.
text_span
.
length
-
a
.
text_span
.
length
);
for
(
const
ent
of
list
)
{
let
startIndex
=
0
;
while
(
startIndex
<
rawText
.
length
)
{
const
idx
=
rawText
.
indexOf
(
ent
.
text_span
,
startIndex
);
if
(
idx
===
-
1
)
break
;
ranges
.
push
({
start
:
idx
,
end
:
idx
+
ent
.
text_span
.
length
,
ent
});
startIndex
=
idx
+
ent
.
text_span
.
length
;
}
}
ranges
.
sort
((
a
,
b
)
=>
a
.
start
-
b
.
start
||
b
.
end
-
a
.
end
);
const
merged
=
[];
let
lastEnd
=
0
;
for
(
const
r
of
ranges
)
{
if
(
r
.
start
<
lastEnd
)
continue
;
merged
.
push
(
r
);
lastEnd
=
r
.
end
;
}
return
merged
;
};
const
buildHighlightedHtml
=
(
text
,
entities
,
enableHighlight
)
=>
{
const
rawText
=
String
(
text
??
""
);
if
(
!
rawText
)
return
""
;
const
safeText
=
escapeHtml
(
rawText
).
replace
(
/
\n
/g
,
"<br />"
);
if
(
!
enableHighlight
)
return
safeText
;
const
ranges
=
getEntityRanges
(
rawText
,
entities
);
if
(
!
ranges
.
length
)
return
safeText
;
let
html
=
""
;
let
cursor
=
0
;
for
(
const
r
of
ranges
)
{
if
(
cursor
<
r
.
start
)
{
html
+=
escapeHtml
(
rawText
.
slice
(
cursor
,
r
.
start
));
}
const
spanText
=
rawText
.
slice
(
r
.
start
,
r
.
end
);
const
type
=
escapeHtml
(
r
.
ent
?.
type
??
""
);
html
+=
`<span class="term-entity" data-entity-type="
${
type
}
">
${
escapeHtml
(
spanText
)}
</span>`
;
cursor
=
r
.
end
;
}
if
(
cursor
<
rawText
.
length
)
{
html
+=
escapeHtml
(
rawText
.
slice
(
cursor
));
}
return
html
.
replace
(
/
\n
/g
,
"<br />"
);
};
const
getTermEntityKey
=
(
term
,
lang
)
=>
{
const
baseKey
=
getTermKey
(
term
,
-
1
);
return
`
${
baseKey
}
__
${
lang
}
`
;
};
const
ensureEntitiesForTerms
=
async
terms
=>
{
if
(
!
termsHighlight
.
value
)
return
;
const
list
=
Array
.
isArray
(
terms
)
?
terms
:
[];
if
(
!
list
.
length
)
return
;
const
currentToken
=
++
entityRequestToken
.
value
;
const
tasks
=
[];
for
(
const
term
of
list
)
{
const
cnKey
=
getTermEntityKey
(
term
,
"cn"
);
const
enKey
=
getTermEntityKey
(
term
,
"en"
);
if
(
!
termEntityCache
.
value
.
has
(
cnKey
)
&&
String
(
term
?.
fynr
??
""
).
trim
())
{
tasks
.
push
({
key
:
cnKey
,
text
:
term
.
fynr
});
}
if
(
!
termEntityCache
.
value
.
has
(
enKey
)
&&
String
(
term
?.
ywnr
??
""
).
trim
())
{
tasks
.
push
({
key
:
enKey
,
text
:
term
.
ywnr
});
}
}
if
(
!
tasks
.
length
)
return
;
try
{
const
results
=
await
Promise
.
all
(
tasks
.
map
(
async
item
=>
{
const
res
=
await
extractTextEntity
(
item
.
text
);
const
entities
=
normalizeEntities
(
res
?.
result
??
res
?.
data
?.
result
??
res
?.
data
??
res
);
return
{
key
:
item
.
key
,
entities
};
})
);
if
(
currentToken
!==
entityRequestToken
.
value
)
return
;
for
(
const
r
of
results
)
{
termEntityCache
.
value
.
set
(
r
.
key
,
r
.
entities
);
}
}
catch
(
error
)
{
if
(
currentToken
!==
entityRequestToken
.
value
)
return
;
}
};
const
getTermContentHtml
=
(
term
,
lang
)
=>
{
const
raw
=
lang
===
"en"
?
term
?.
ywnr
:
term
?.
fynr
;
const
key
=
getTermEntityKey
(
term
,
lang
);
const
entities
=
termEntityCache
.
value
.
get
(
key
)
||
[];
return
buildHighlightedHtml
(
raw
,
entities
,
termsHighlight
.
value
);
};
const
tkRequestToken
=
ref
(
0
);
const
xzfsRequestToken
=
ref
(
0
);
const
hylyRequestToken
=
ref
(
0
);
...
...
@@ -182,9 +312,9 @@ const getTermSerial = index => {
};
const
chart1Data
=
ref
([]);
const
chart1ColorList
=
ref
([
"#4096ff"
,
"#b37feb"
,
"#ff7875"
,
"#85a5ff"
,
"#69b1ff"
,
"#ffc069"
,
"#87e8de"
]);
const
chart1ColorList
=
ref
([
...
MUTICHARTCOLORS
]);
const
chart2ColorList
=
ref
([
"#ff7875"
,
"#85a5ff"
,
"#95de64"
,
"#ffc069"
,
"#85e5db"
]);
const
chart2ColorList
=
ref
([
...
MUTICHARTCOLORS
]);
const
chart2Data
=
ref
([]);
...
...
@@ -218,6 +348,14 @@ watch([selectedDomain, selectedLimit], () => {
handleGetBillContentTk
(
checkedValue
.
value
?
"Y"
:
"N"
);
});
watch
(
[
termsHighlight
,
termsShowOriginal
],
()
=>
{
ensureEntitiesForTerms
(
displayTermsList
.
value
);
},
{
immediate
:
true
}
);
const
handleSearchSubmit
=
()
=>
{
searchKeyword
.
value
=
searchValue
.
value
;
currentPage
.
value
=
1
;
...
...
@@ -362,6 +500,7 @@ const handleGetBillContentTk = async cRelated => {
return item;
});
total.value = res.data.totalElements || 0;
ensureEntitiesForTerms(mainTermsList.value);
} else {
mainTermsList.value = [];
total.value = 0;
...
...
@@ -805,6 +944,14 @@ onMounted(async () => {
font-weight
:
700
;
line-height
:
24px
;
color
:
var
(
--
text-primary-80-color
);
:deep
(
.term-entity
)
{
display
:
inline
;
padding
:
0
2px
;
border-radius
:
4px
;
background
:
rgba
(
255
,
213
,
79
,
0
.35
);
box-shadow
:
inset
0
0
0
1px
rgba
(
255
,
193
,
7
,
0
.25
);
}
}
.term-content-en
{
...
...
@@ -813,6 +960,14 @@ onMounted(async () => {
font-weight
:
400
;
line-height
:
24px
;
color
:
var
(
--
text-primary-65-color
);
:deep
(
.term-entity
)
{
display
:
inline
;
padding
:
0
2px
;
border-radius
:
4px
;
background
:
rgba
(
255
,
213
,
79
,
0
.28
);
box-shadow
:
inset
0
0
0
1px
rgba
(
255
,
193
,
7
,
0
.2
);
}
}
.open
{
...
...
src/views/decree/decreeOriginal/index.vue
浏览文件 @
08768dcd
<
template
>
<div
class=
"layout-container"
>
<!-- 导航菜单 -->
<div
class=
"layout-main"
>
<div
class=
"header-main"
>
<div
class=
"layout-main-header"
>
...
...
@@ -25,76 +24,23 @@
</div>
</div>
</div>
<div
class=
"layout-main-center"
>
<div
class=
"report-header"
>
<div
class=
"report-title"
>
政令原文
</div>
<el-switch
v-model=
"isHighlight"
/>
<div
style=
"margin-left: 6px; margin-right: 10px;"
>
高亮实体
</div>
<el-switch
v-model=
"isTranslate"
/>
<div
style=
"margin-left: 6px;"
>
原文显示
</div>
<div
class=
"btn"
@
click=
"handleDownload"
>
<div
class=
"icon"
style=
"margin-right: 4px;"
>
<img
:src=
"download"
alt=
""
>
</div>
<div
class=
"text"
>
下载
</div>
</div>
<div
class=
"btn"
@
click=
"handleFindWord('open')"
>
<div
class=
"icon"
style=
"margin-right: 6px;"
>
<img
:src=
"search"
alt=
""
>
</div>
<div
class=
"text"
>
查找
</div>
</div>
<div
class=
"find-word-box"
v-if=
"findWordBox"
>
<div
class=
"find-word-input"
>
<el-input
v-model=
"findWordTxt"
placeholder=
"查找原文内容"
@
input=
"handleUpdateWord()"
/>
</div>
<div
class=
"find-word-limit"
>
{{
findWordNum
}}
/
{{
findWordMax
}}
</div>
<div
class=
"find-word-icon"
@
click=
"handleFindWord('last')"
>
<el-icon><ArrowUp
/></el-icon>
</div>
<div
class=
"find-word-icon"
@
click=
"handleFindWord('next')"
>
<el-icon><ArrowDown
/></el-icon>
</div>
<div
class=
"find-word-icon"
@
click=
"handleFindWord('close')"
>
<el-icon><Close
/></el-icon>
</div>
</div>
</div>
<div
class=
"report-main"
>
<div
v-if=
"!reportData.length"
class=
"noContent"
>
{{
"暂无数据"
}}
</div>
<el-scrollbar
height=
"100%"
v-else
>
<div
v-for=
"(item, index) in reportData"
:key=
"index"
:class=
"['content-row',
{'high-light':isHighlight}]">
<!-- 右侧:中文 -->
<div
:class=
"['content-cn',
{'translate-cn':!isTranslate}]" v-html="item.content">
</div>
<!-- 左侧:英文 -->
<div
class=
"content-en"
v-html=
"item.contentEn"
v-if=
"isTranslate"
></div>
</div>
</el-scrollbar>
</div>
<BaseDecreeOriginal
:report-data=
"reportData"
@
download=
"handleDownload"
/>
</div>
</div>
</div>
</
template
>
<
script
setup
>
import
{
ref
,
onMounted
,
nextTick
}
from
"vue"
;
import
{
ref
,
onMounted
}
from
"vue"
;
import
{
useRoute
}
from
"vue-router"
;
import
{
ElMessage
}
from
"element-plus"
;
import
{
debounce
}
from
"lodash"
;
import
{
getDecreeSummary
}
from
"@/api/decree/introduction"
;
import
{
getDecreeReport
}
from
"@/api/decree/introduction"
;
import
download
from
"./assets/icons/download.png"
;
import
search
from
"./assets/icons/search.png"
;
import
BaseDecreeOriginal
from
"@/components/base/DecreeOriginal/index.vue"
;
const
route
=
useRoute
();
// 政令原文操作
const
isHighlight
=
ref
(
false
);
const
isTranslate
=
ref
(
true
);
const
findWordTxt
=
ref
(
""
)
const
findWordBox
=
ref
(
false
);
const
findWordNum
=
ref
(
0
);
const
findWordMax
=
ref
(
0
);
const
handleDownload
=
async
()
=>
{
if
(
summaryInfo
.
value
?.
url
)
{
try
{
...
...
@@ -134,76 +80,6 @@ const handleDownload = async () => {
ElMessage
.
warning
(
"暂无下载链接!"
);
}
}
const
handleHighlight
=
()
=>
{
let
spans
=
document
.
querySelectorAll
(
`span.highlight`
);
spans
.
forEach
((
span
,
index
)
=>
{
if
(
index
+
1
===
findWordNum
.
value
)
{
// 平滑滚动 behavior: 'smooth'
span
.
scrollIntoView
({})
span
.
style
.
backgroundColor
=
"#ff9632"
;
}
else
{
span
.
style
.
backgroundColor
=
"#ffff00"
;
}
})
}
const
handleFindWord
=
(
event
)
=>
{
switch
(
event
)
{
case
"open"
:
findWordBox
.
value
=
true
;
break
;
case
"last"
:
if
(
findWordMax
.
value
>
1
)
{
findWordNum
.
value
=
findWordNum
.
value
==
1
?
findWordMax
.
value
:
findWordNum
.
value
-
1
;
handleHighlight
()
}
break
;
case
"next"
:
if
(
findWordMax
.
value
>
1
)
{
findWordNum
.
value
=
findWordNum
.
value
==
findWordMax
.
value
?
1
:
findWordNum
.
value
+
1
;
handleHighlight
()
}
break
;
case
"close"
:
findWordBox
.
value
=
false
;
findWordTxt
.
value
=
""
;
handleUpdateWord
()
break
;
}
}
const
handleUpdateWord
=
debounce
(()
=>
{
console
.
log
(
"更新查找词"
,
findWordTxt
.
value
);
findWordNum
.
value
=
0
;
findWordMax
.
value
=
0
;
if
(
findWordTxt
.
value
)
{
originData
.
forEach
((
item
,
index
)
=>
{
if
(
item
.
content
)
{
reportData
.
value
[
index
].
content
=
highlightText
(
item
.
content
,
findWordTxt
.
value
);
}
if
(
isTranslate
.
value
&&
item
.
contentEn
)
{
reportData
.
value
[
index
].
contentEn
=
highlightText
(
item
.
contentEn
,
findWordTxt
.
value
);
}
});
if
(
findWordMax
.
value
>
0
)
{
nextTick
(()
=>
{
findWordNum
.
value
=
findWordNum
.
value
==
findWordMax
.
value
?
1
:
findWordNum
.
value
+
1
;
handleHighlight
()
})
}
}
else
{
originData
.
forEach
((
item
,
index
)
=>
{
reportData
.
value
[
index
].
content
=
item
.
content
;
reportData
.
value
[
index
].
contentEn
=
item
.
contentEn
;
});
}
},
300
)
const
highlightText
=
(
text
,
searchTerm
)
=>
{
const
escapedTerm
=
searchTerm
.
replace
(
/
[
.*+?^${}()|[
\]\\]
/g
,
'
\\
$&'
);
return
text
.
replace
(
new
RegExp
(
escapedTerm
,
'g'
),
(
match
)
=>
{
findWordMax
.
value
++
;
return
`<span class="highlight">
${
match
}
</span>`
;
});
}
// 获取全局信息
const
summaryInfo
=
ref
({});
...
...
@@ -222,13 +98,12 @@ const handleGetSummary = async () => {
// 获取报告原文 - 修改为获取分段数组
const
reportData
=
ref
([]);
let
originData
=
[];
const
handleGetReport
=
async
()
=>
{
try
{
const
res
=
await
getDecreeReport
({
id
:
route
.
query
.
id
});
console
.
log
(
"报告原文"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
originData
=
[];
const
originData
=
[];
let
num
=
Math
.
max
(
res
.
data
.
content
.
length
,
res
.
data
.
contentEn
.
length
)
for
(
let
i
=
0
;
i
<
num
;
i
++
)
{
let
obj
=
{
...
...
@@ -250,16 +125,10 @@ onMounted(() => {
</
script
>
<
style
lang=
"scss"
scoped
>
.high-light
{
:deep
(
span
.highlight
)
{
// color: #055FC2;
background-color
:
#ffff00
;
}
}
.layout-container
{
width
:
100%
;
height
:
100%
;
background-color
:
#
F7F8F
9
;
background-color
:
#
f7f8f
9
;
.layout-main
{
height
:
100%
;
...
...
@@ -273,6 +142,7 @@ onMounted(() => {
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0
.1
);
background
:
rgba
(
255
,
255
,
255
,
1
);
}
.layout-main-header
{
width
:
1600px
;
display
:
flex
;
...
...
@@ -295,6 +165,7 @@ onMounted(() => {
margin-right
:
40px
;
width
:
20px
;
flex
:
auto
;
.info-box1
{
width
:
100%
;
color
:
rgba
(
59
,
65
,
75
,
1
);
...
...
@@ -324,6 +195,7 @@ onMounted(() => {
white-space
:
nowrap
;
padding
:
0
10px
;
}
.info-box2-item
:first-child
{
padding-left
:
0px
;
}
...
...
@@ -333,6 +205,7 @@ onMounted(() => {
.layout-main-header-right-box
{
.right-box-top
{
white-space
:
nowrap
;
.time
{
height
:
24px
;
line-height
:
24px
;
...
...
@@ -368,160 +241,7 @@ onMounted(() => {
flex
:
auto
;
display
:
flex
;
flex-direction
:
column
;
.report-header
{
height
:
80px
;
display
:
flex
;
align-items
:
center
;
border-bottom
:
solid
1px
rgba
(
234
,
236
,
238
,
1
);
margin
:
0
20px
10px
;
position
:
relative
;
.find-word-box
{
position
:
absolute
;
top
:
-50px
;
right
:
0px
;
width
:
430px
;
height
:
60px
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
background-color
:
white
;
border-radius
:
6px
;
display
:
flex
;
align-items
:
center
;
.find-word-input
{
width
:
20px
;
flex
:
auto
;
}
.find-word-limit
{
border-right
:
solid
1px
rgba
(
230
,
231
,
232
,
1
);
color
:
#5F656C
;
padding-right
:
16px
;
}
.find-word-icon
{
padding
:
10px
12px
;
margin
:
0
2px
;
cursor
:
pointer
;
}
}
.report-title
{
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
20px
;
line-height
:
20px
;
font-weight
:
700
;
width
:
20px
;
flex
:
auto
;
}
.btn
{
margin-left
:
10px
;
width
:
88px
;
height
:
32px
;
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
border-radius
:
6px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
cursor
:
pointer
;
.text
{
height
:
24px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
font-family
:
Microsoft
YaHei
;
font-style
:
Regular
;
font-size
:
14px
;
font-weight
:
400
;
line-height
:
24px
;
letter-spacing
:
0px
;
text-align
:
left
;
}
.icon
{
width
:
16px
;
height
:
16px
;
font-size
:
0px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
}
}
.report-main
{
height
:
20px
;
flex
:
auto
;
box-sizing
:
border-box
;
padding-top
:
10px
;
// 滚动条样式
&
:
:-
webkit-scrollbar
{
display
:
none
;
}
&
:
:-
webkit-scrollbar-thumb
{
background
:
#c1c1c1
;
border-radius
:
4px
;
}
&
:
:-
webkit-scrollbar-track
{
background
:
#f1f1f1
;
}
.noContent
{
height
:
100%
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
Microsoft
YaHei
;
font-style
:
Regular
;
font-size
:
20px
;
font-weight
:
400
;
}
.content-row
{
display
:
flex
;
width
:
100%
;
padding
:
0
20px
;
min-height
:
100px
;
gap
:
80px
;
&
:last-child
{
border-bottom
:
none
;
}
.content-en
,
.content-cn
{
width
:
50%
;
flex
:
auto
;
padding-bottom
:
40px
;
box-sizing
:
border-box
;
font-size
:
16px
;
line-height
:
1
.8
;
color
:
#3B414B
;
font-family
:
Microsoft
YaHei
;
text-align
:
justify
;
white-space
:
pre-wrap
;
// 保留换行格式
}
.translate-cn
{
padding-bottom
:
10px
;
}
}
}
}
}
}
// 修改element-plus滚动条样式
:deep
(
.el-scrollbar__bar.is-vertical
)
{
right
:
0px
;
width
:
4px
;
background
:
transparent
;
border-radius
:
2px
;
&
>
div
{
background
:
#c5c7c9
;
opacity
:
1
;
}
&
>
div
:hover
{
background
:
#505357
;
}
}
</
style
>
\ No newline at end of file
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论