Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
a3bb0583
提交
a3bb0583
authored
3月 17, 2026
作者:
yanpeng
浏览文件
操作
浏览文件
下载
差异文件
Merge branch 'master' into yp-dev
上级
d9c0373b
6db7dd8e
全部展开
显示空白字符变更
内嵌
并排
正在显示
14 个修改的文件
包含
880 行增加
和
196 行删除
+880
-196
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
+391
-0
wordCloudChart.js
src/components/base/WordCloundChart/wordCloudChart.js
+19
-33
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/billOriginalText/index.vue
+7
-92
index.vue
src/views/bill/deepDig/poliContribution/index.vue
+4
-59
index.vue
src/views/bill/deepDig/processOverview/index.vue
+0
-0
index.vue
src/views/bill/template/index.vue
+159
-4
download.png
src/views/decree/decreeOriginal/assets/icons/download.png
+0
-0
search.png
src/views/decree/decreeOriginal/assets/icons/search.png
+0
-0
index.vue
src/views/decree/decreeOriginal/index.vue
+0
-0
没有找到文件。
src/components/base/DecreeOriginal/assets/icons/download.png
0 → 100644
浏览文件 @
a3bb0583
350 Bytes
src/components/base/DecreeOriginal/assets/icons/search.png
0 → 100644
浏览文件 @
a3bb0583
399 Bytes
src/components/base/DecreeOriginal/index.vue
0 → 100644
浏览文件 @
a3bb0583
<
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
v-for=
"action in headerActions"
:key=
"action.key"
class=
"btn"
@
click=
"action.onClick"
>
<div
:class=
"['icon', action.iconGapClass]"
>
<img
:src=
"action.icon"
alt=
""
/>
</div>
<div
class=
"text"
>
{{
action
.
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=
"no-content"
>
暂无数据
</div>
<el-scrollbar
v-else
height=
"100%"
>
<div
v-for=
"item in displayReportData"
:key=
"item.num"
class=
"content-row"
:class=
"
{ 'high-light': isHighlight }"
>
<div
class=
"content-cn"
:class=
"
{ 'translate-cn': !isTranslate }" v-html="item.content" />
<div
v-if=
"isTranslate"
class=
"content-en"
v-html=
"item.contentEn"
/>
</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
([]);
const
headerActions
=
[
{
key
:
"download"
,
text
:
"下载"
,
icon
:
defaultDownloadIcon
,
iconGapClass
:
"icon-gap-4"
,
onClick
:
()
=>
emits
(
"download"
),
},
{
key
:
"search"
,
text
:
"查找"
,
icon
:
defaultSearchIcon
,
iconGapClass
:
"icon-gap-6"
,
onClick
:
()
=>
handleFindWord
(
"open"
),
},
];
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
;
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
{
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
;
flex
:
1
;
}
.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
{
flex
:
auto
;
box-sizing
:
border-box
;
padding-top
:
10px
;
.no-content
{
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
;
.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
浏览文件 @
a3bb0583
import
'echarts-wordcloud'
;
import
{
MUTICHARTCOLORS
}
from
'@/common/constant'
;
const
getWordCloudChart
=
(
data
)
=>
{
const
getWordCloudChart
=
data
=>
{
const
option
=
{
grid
:
{
left
:
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', // 示例
// 或者自定义路径
...
...
@@ -26,39 +26,25 @@ const getWordCloudChart = (data) => {
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
浏览文件 @
a3bb0583
<
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
浏览文件 @
a3bb0583
...
...
@@ -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
浏览文件 @
a3bb0583
...
...
@@ -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/billOriginalText/index.vue
浏览文件 @
a3bb0583
<
template
>
<div
class=
"bill-original-text-page"
>
<div
class=
"page-header"
>
<div
class=
"page-title"
>
法案原文
</div>
<div
class=
"page-actions"
>
<div
class=
"action-btn"
@
click=
"handleBack"
>
返回
</div>
</div>
</div>
<div
class=
"page-content"
>
<iframe
v-if=
"billFullText"
:src=
"billFullText"
width=
"100%"
height=
"100%"
frameborder=
"0"
></iframe>
<div
v-else
class=
"empty-state"
>
暂无原文
</div>
</div>
<DecreeOriginal
:report-data=
"reportData"
@
download=
"handleDownload"
/>
</div>
</
template
>
<
script
setup
>
import
{
onMounted
,
ref
}
from
"vue"
;
import
{
useRoute
,
useRouter
}
from
"vue-router"
;
import
{
getBillFullText
}
from
"@/api/bill"
;
const
route
=
useRoute
();
const
router
=
useRouter
();
import
{
ref
}
from
"vue"
;
import
DecreeOriginal
from
"@/components/base/DecreeOriginal/index.vue"
;
const
billFullText
=
ref
(
""
);
// 旧“法案原文”功能/按钮全部废弃:页面仅承载通用原文组件,后续接入新接口再赋值。
const
reportData
=
ref
([]);
const
getBillFullTextFn
=
async
()
=>
{
const
res
=
await
getBillFullText
({
id
:
route
.
query
.
billId
});
if
(
res
.
code
===
200
&&
res
.
data
)
{
billFullText
.
value
=
typeof
res
.
data
===
"string"
?
res
.
data
.
trim
()
:
res
.
data
;
}
const
handleDownload
=
()
=>
{
// 后续接入新接口/下载逻辑
};
const
handleBack
=
()
=>
{
router
.
back
();
};
onMounted
(()
=>
{
getBillFullTextFn
();
});
</
script
>
<
style
lang=
"scss"
scoped
>
.bill-original-text-page
{
width
:
100%
;
box-sizing
:
border-box
;
background
:
rgba
(
248
,
249
,
250
,
1
);
padding
:
0
0
20px
;
.page-header
{
width
:
100%
;
height
:
64px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
.page-title
{
color
:
rgba
(
59
,
65
,
75
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
18px
;
font-weight
:
700
;
}
.page-actions
{
display
:
flex
;
justify-content
:
flex-end
;
.action-btn
{
cursor
:
pointer
;
height
:
32px
;
line-height
:
32px
;
padding
:
0
12px
;
border-radius
:
6px
;
border
:
1px
solid
rgba
(
230
,
231
,
232
,
1
);
background
:
rgba
(
255
,
255
,
255
,
1
);
color
:
rgba
(
95
,
101
,
108
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
14px
;
font-weight
:
400
;
}
}
}
.page-content
{
width
:
100%
;
height
:
calc
(
100vh
-
320px
);
min-height
:
600px
;
background
:
#fff
;
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0
.1
);
overflow
:
hidden
;
iframe
{
display
:
block
;
}
.empty-state
{
height
:
100%
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
color
:
rgba
(
132
,
136
,
142
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
14px
;
}
}
}
</
style
>
src/views/bill/deepDig/poliContribution/index.vue
浏览文件 @
a3bb0583
...
...
@@ -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
浏览文件 @
a3bb0583
差异被折叠。
点击展开。
src/views/bill/template/index.vue
浏览文件 @
a3bb0583
...
...
@@ -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/assets/icons/download.png
0 → 100644
浏览文件 @
a3bb0583
350 Bytes
src/views/decree/decreeOriginal/assets/icons/search.png
0 → 100644
浏览文件 @
a3bb0583
399 Bytes
src/views/decree/decreeOriginal/index.vue
浏览文件 @
a3bb0583
差异被折叠。
点击展开。
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论