Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
78d38246
提交
78d38246
authored
3月 17, 2026
作者:
coderBryanFu
浏览文件
操作
浏览文件
下载
差异文件
feat:新增数据资源库模块,新增时间选择框通用组件
上级
010d5cc4
6db7dd8e
隐藏空白字符变更
内嵌
并排
正在显示
17 个修改的文件
包含
1041 行增加
和
1235 行删除
+1041
-1235
countryCoordMap.js
src/assets/json/countryCoordMap.js
+15
-0
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
+20
-34
index-back.vue
...omponents/fourSuppress/components/allUnion/index-back.vue
+36
-241
index.vue
...iew/components/fourSuppress/components/allUnion/index.vue
+81
-53
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
+8
-93
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
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
+20
-284
没有找到文件。
src/assets/json/countryCoordMap.js
浏览文件 @
78d38246
...
...
@@ -213,3 +213,18 @@ export const countryCoordMap = {
法属波利尼西亚
:
[
-
149.5986
,
-
17.6797
],
// 法属波利尼西亚帕皮提
"新喀里多尼亚(法)"
:
[
166.4572
,
-
21.5547
]
// 新喀里多尼亚努美阿
};
export
function
convertAsiaCenterCoord
(
coord
)
{
const
[
lng
,
lat
]
=
coord
;
// 将以本初子午线为基准的坐标转换为以亚洲为中心的坐标
// world-asia-center.json 是将标准坐标的经度减去了 180 度
let
newLng
=
lng
-
180
;
// 规范化到 [-180, 180] 范围
if
(
newLng
<
-
180
)
{
newLng
+=
360
;
}
return
[
newLng
,
lat
];
}
src/components/base/DecreeOriginal/assets/icons/download.png
0 → 100644
浏览文件 @
78d38246
350 Bytes
src/components/base/DecreeOriginal/assets/icons/search.png
0 → 100644
浏览文件 @
78d38246
399 Bytes
src/components/base/DecreeOriginal/index.vue
0 → 100644
浏览文件 @
78d38246
<
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
浏览文件 @
78d38246
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', // 示例
// 或者自定义路径
...
...
@@ -27,39 +27,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/ZMOverView/components/fourSuppress/components/allUnion/index-back.vue
浏览文件 @
78d38246
...
...
@@ -588,22 +588,18 @@ function handleUnionItemClick(item) {
createUnionChart
();
}
);
}
// ... existing code ...
// ... existing code ...
function
createChart
()
{
// 如果没有数据,直接返回
if
(
!
countryTotalList
.
value
||
countryTotalList
.
value
.
length
===
0
)
{
console
.
error
(
"No country data available"
);
return
;
}
// 找到最大值用于颜色计算
const
maxValue
=
Math
.
max
(...
countryTotalList
.
value
.
map
(
item
=>
item
.
value
));
console
.
log
(
"全部国家数据 countryTotalList =>"
,
countryTotalList
.
value
);
// 为每个数据项计算颜色
const
processedData
=
countryTotalList
.
value
.
map
(
item
=>
{
const
color
=
getColorByValueRandom
(
item
.
value
,
maxValue
);
return
{
...
...
@@ -621,12 +617,12 @@ function createChart() {
}
;
}
);
// 构建基础地图配置
const
option
=
{
geo
:
{
map
:
"world"
,
roam
:
true
,
zoom
:
1.2
,
// center: [104.1954, 35.8617], // 设置地图中心点为中国
label
:
{
show
:
false
}
,
...
...
@@ -687,7 +683,6 @@ function createChart() {
]
}
;
// 如果有选中的国家,添加关系线
if
(
currentSelectedCountry
.
value
&&
currentSelectedCountry
.
value
.
memberRelation
&&
...
...
@@ -698,8 +693,19 @@ function createChart() {
currentSelectedCountry
.
value
.
zhName
||
nameMap
[
currentSelectedCountry
.
value
.
name
]
||
currentSelectedCountry
.
value
.
name
;
const
sourceCoord
=
countryCoordMap
[
sourceCountryName
];
if
(
!
sourceCoord
)
{
console
.
warn
(
`无法找到源国家 ${sourceCountryName
}
的坐标`
);
return
;
}
console
.
log
(
"=== 源国家信息 ==="
);
console
.
log
(
"源国家名称:"
,
sourceCountryName
);
console
.
log
(
"源国家坐标:"
,
sourceCoord
);
console
.
log
(
"=================="
);
const
validRelations
=
relations
.
filter
(
relation
=>
{
return
relation
.
tagetMemberName
&&
relation
.
tagetMemberCount
;
}
);
...
...
@@ -711,10 +717,12 @@ function createChart() {
const
targetPoints
=
[];
validRelations
.
forEach
(
relation
=>
{
const
targetCountry
=
relation
.
tagetMemberName
;
const
targetCoord
=
countryCoordMap
[
targetCountry
];
const
targetCountry
ZhName
=
relation
.
tagetMemberName
;
const
targetCoord
=
countryCoordMap
[
targetCountry
ZhName
];
if
(
targetCoord
)
{
console
.
log
(
`目标国家:${targetCountryZhName
}
, 坐标:`
,
targetCoord
);
const
ratio
=
relation
.
tagetMemberCount
/
maxRelationCount
;
const
r
=
Math
.
round
(
5
+
(
255
-
5
)
*
ratio
);
const
g
=
Math
.
round
(
95
+
(
77
-
95
)
*
ratio
);
...
...
@@ -724,9 +732,9 @@ function createChart() {
const
lineWidth
=
1
+
(
relation
.
tagetMemberCount
/
maxRelationCount
)
*
7
;
linesData
.
push
({
name
:
`${sourceCountryName
}
- ${targetCountry
}
`
,
name
:
`${sourceCountryName
}
- ${targetCountry
ZhName
}
`
,
sourceName
:
sourceCountryName
,
targetName
:
targetCountry
,
targetName
:
targetCountry
ZhName
,
coords
:
[
sourceCoord
,
targetCoord
],
value
:
relation
.
tagetMemberCount
,
lineStyle
:
{
...
...
@@ -737,13 +745,15 @@ function createChart() {
}
);
targetPoints
.
push
({
name
:
targetCountry
,
name
:
targetCountry
ZhName
,
value
:
targetCoord
,
symbolSize
:
8
+
(
relation
.
tagetMemberCount
/
maxRelationCount
)
*
7
,
itemStyle
:
{
color
:
lineColor
}
}
);
}
else
{
console
.
warn
(
`无法找到目标国家 ${targetCountryZhName
}
的坐标`
);
}
}
);
...
...
@@ -795,244 +805,29 @@ function createChart() {
// ... existing code ...
// ... existing code ...
// 处理国家列表项点击事件
// 在 handleCountryClick 中添加调试代码
const
handleCountryClick
=
country
=>
{
currentSelectedCountry
.
value
=
country
;
console
.
log
(
"国家之间的关系 =>"
,
country
);
console
.
log
(
"=== 点击的国家信息 ==="
);
console
.
log
(
"国家中文名:"
,
country
.
zhName
);
console
.
log
(
"国家英文名:"
,
country
.
name
);
// 检查坐标映射
const
coordFromZhName
=
countryCoordMap
[
country
.
zhName
];
const
coordFromEnName
=
countryCoordMap
[
country
.
name
];
const
coordFromMappedName
=
countryCoordMap
[
nameMap
[
country
.
name
]];
console
.
log
(
"通过中文名获取坐标:"
,
coordFromZhName
);
console
.
log
(
"通过英文名获取坐标:"
,
coordFromEnName
);
console
.
log
(
"通过映射名获取坐标:"
,
coordFromMappedName
);
console
.
log
(
"====================="
);
nextTick
(()
=>
{
createChart
();
}
);
}
;
const
countryMock
=
{
name
:
"Australia"
,
ename
:
"Commonwealth of Australia"
,
image
:
"http://8.140.26.4:10010/kjb-files/images/cr_flag/AUS.jpg"
,
count
:
7
,
memberRelation
:
[
{
tagetMemberName
:
"美国"
,
tagetMemberCount
:
7
}
,
{
tagetMemberName
:
"英国"
,
tagetMemberCount
:
6
}
,
{
tagetMemberName
:
"日本"
,
tagetMemberCount
:
5
}
,
{
tagetMemberName
:
"韩国"
,
tagetMemberCount
:
4
}
,
{
tagetMemberName
:
"印度"
,
tagetMemberCount
:
4
}
,
{
tagetMemberName
:
"新西兰"
,
tagetMemberCount
:
3
}
,
{
tagetMemberName
:
"加拿大"
,
tagetMemberCount
:
3
}
,
{
tagetMemberName
:
"挪威"
,
tagetMemberCount
:
2
}
,
{
tagetMemberName
:
"德国"
,
tagetMemberCount
:
2
}
,
{
tagetMemberName
:
"法国"
,
tagetMemberCount
:
2
}
,
{
tagetMemberName
:
"意大利"
,
tagetMemberCount
:
2
}
,
{
tagetMemberName
:
"荷兰"
,
tagetMemberCount
:
2
}
,
{
tagetMemberName
:
"芬兰"
,
tagetMemberCount
:
2
}
,
{
tagetMemberName
:
"瑞典"
,
tagetMemberCount
:
2
}
,
{
tagetMemberName
:
"爱沙尼亚"
,
tagetMemberCount
:
2
}
,
{
tagetMemberName
:
"新加坡"
,
tagetMemberCount
:
2
}
,
{
tagetMemberName
:
"泰国"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"文莱"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"丹麦"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"克罗地亚"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"卢森堡"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"欧盟"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"阿根廷"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"越南"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"罗马尼亚"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"立陶宛"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"以色列"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"阿联酋"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"斐济"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"墨西哥"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"希腊"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"捷克"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"保加利亚"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"马耳他"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"匈牙利"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"波兰"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"瑞士"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"奥地利"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"俄罗斯"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"斯洛文尼亚"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"土耳其"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"西班牙"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"斯洛伐克"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"比利时"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"乌克兰"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"南非"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"拉脱维亚"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"马来西亚"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"爱尔兰"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"葡萄牙"
,
tagetMemberCount
:
1
}
,
{
tagetMemberName
:
"菲律宾"
,
tagetMemberCount
:
1
}
],
value
:
7
,
zhName
:
"澳大利亚"
}
;
// function initMap()
{
// chartDom.value = document.getElementById("echartsMap");
// if (!chartDom.value) return;
// if (myChart.value) myChart.value.dispose();
// myChart.value = echarts.init(chartDom.value);
// myChart.value.showLoading();
// echarts.registerMap("world", mapJson);
// createChart();
// myChart.value.hideLoading();
//
}
function
initMap
()
{
chartDom
.
value
=
document
.
getElementById
(
"echartsMap"
);
unionChartDom
.
value
=
document
.
getElementById
(
"echartsUnionMap"
);
...
...
src/views/ZMOverView/components/fourSuppress/components/allUnion/index.vue
浏览文件 @
78d38246
...
...
@@ -69,7 +69,13 @@
<div
class=
"right-num"
>
参与排华联盟
</div>
</div>
</div>
<div
class=
"item"
v-for=
"(item, index) in countList"
:key=
"index"
@
click=
"handleCountryClick(item)"
>
<div
class=
"item"
v-for=
"(item, index) in countList"
:key=
"index"
@
click=
"handleCountryClick(item)"
:class=
"
{ 'selected-country': currentSelectedCountry
&&
currentSelectedCountry.name === item.name }"
>
<div
class=
"item-left"
>
<img
:src=
"item.image"
alt
/>
<el-tooltip
...
...
@@ -298,7 +304,7 @@ import { link } from "d3";
import
{
get
,
union
,
update
}
from
"lodash"
;
import
ButtonList
from
"@/components/buttonList/buttonList.vue"
;
import
{
fieldOptions
,
COLORS
,
countryNameMap
,
nameMap
}
from
"@/views/ZMOverView/public.js"
;
import
{
countryCoordMap
}
from
"@/assets/json/countryCoordMap.js"
;
import
{
countryCoordMap
,
convertAsiaCenterCoord
}
from
"@/assets/json/countryCoordMap.js"
;
const
buttonList
=
ref
([
{
...
...
@@ -589,8 +595,6 @@ function handleUnionItemClick(item) {
}
);
}
// ... existing code ...
function
createChart
()
{
if
(
!
countryTotalList
.
value
||
countryTotalList
.
value
.
length
===
0
)
{
console
.
error
(
"No country data available"
);
...
...
@@ -622,7 +626,6 @@ function createChart() {
map
:
"world"
,
roam
:
true
,
zoom
:
1.2
,
// center: [104.1954, 35.8617], // 设置地图中心点为中国
label
:
{
show
:
false
}
,
...
...
@@ -672,6 +675,8 @@ function createChart() {
type
:
"scatter"
,
coordinateSystem
:
"geo"
,
geoIndex
:
0
,
nameMap
:
nameMap
,
data
:
processedData
,
symbolSize
:
function
(
val
)
{
return
Math
.
max
(
val
.
value
/
5
,
5
);
...
...
@@ -689,20 +694,36 @@ function createChart() {
Array
.
isArray
(
currentSelectedCountry
.
value
.
memberRelation
)
)
{
const
relations
=
currentSelectedCountry
.
value
.
memberRelation
;
const
sourceCountryName
=
currentSelectedCountry
.
value
.
zhName
||
nameMap
[
currentSelectedCountry
.
value
.
name
]
||
currentSelectedCountry
.
value
.
name
;
const
sourceCoord
=
countryCoordMap
[
sourceCountryName
];
const
sourceCountryZhName
=
currentSelectedCountry
.
value
.
zhName
;
const
sourceCountryEnName
=
currentSelectedCountry
.
value
.
name
;
let
sourceCoord
=
null
;
let
finalSourceName
=
""
;
if
(
sourceCountryZhName
&&
countryCoordMap
[
sourceCountryZhName
])
{
const
convertedCoord
=
convertAsiaCenterCoord
(
countryCoordMap
[
sourceCountryZhName
]);
sourceCoord
=
convertedCoord
;
finalSourceName
=
sourceCountryZhName
;
}
else
if
(
sourceCountryEnName
)
{
const
mappedName
=
nameMap
[
sourceCountryEnName
]
||
countryNameMap
[
sourceCountryEnName
];
const
zhName
=
mappedName
||
sourceCountryEnName
;
if
(
countryCoordMap
[
zhName
])
{
const
convertedCoord
=
convertAsiaCenterCoord
(
countryCoordMap
[
zhName
]);
sourceCoord
=
convertedCoord
;
finalSourceName
=
zhName
;
}
}
if
(
!
sourceCoord
)
{
console
.
warn
(
`无法找到源国家 ${sourceCountryName
}
的坐标`
);
console
.
warn
(
`无法找到源国家 ${sourceCountry
ZhName || sourceCountryEn
Name
}
的坐标`
);
return
;
}
console
.
log
(
"=== 源国家信息 ==="
);
console
.
log
(
"源国家名称:"
,
sourceCountryName
);
console
.
log
(
"源国家中文名:"
,
sourceCountryZhName
);
console
.
log
(
"源国家英文名:"
,
sourceCountryEnName
);
console
.
log
(
"最终使用的国家名称:"
,
finalSourceName
);
console
.
log
(
"源国家坐标:"
,
sourceCoord
);
console
.
log
(
"=================="
);
...
...
@@ -718,23 +739,46 @@ function createChart() {
validRelations
.
forEach
(
relation
=>
{
const
targetCountryZhName
=
relation
.
tagetMemberName
;
const
targetCoord
=
countryCoordMap
[
targetCountryZhName
];
let
targetCoord
=
null
;
let
finalTargetName
=
""
;
if
(
countryCoordMap
[
targetCountryZhName
])
{
const
convertedCoord
=
convertAsiaCenterCoord
(
countryCoordMap
[
targetCountryZhName
]);
targetCoord
=
convertedCoord
;
finalTargetName
=
targetCountryZhName
;
}
else
{
const
possibleNames
=
[
targetCountryZhName
,
nameMap
[
targetCountryZhName
],
countryNameMap
[
targetCountryZhName
]
];
for
(
const
name
of
possibleNames
)
{
if
(
name
&&
countryCoordMap
[
name
])
{
const
convertedCoord
=
convertAsiaCenterCoord
(
countryCoordMap
[
name
]);
targetCoord
=
convertedCoord
;
finalTargetName
=
name
;
break
;
}
}
}
if
(
targetCoord
)
{
console
.
log
(
`目标国家:${targetCountryZhName
}
, 坐标:`
,
targetCoord
);
console
.
log
(
`目标国家:${targetCountryZhName
}
,
最终名称:${finalTargetName
}
,
坐标:`
,
targetCoord
);
const
ratio
=
relation
.
tagetMemberCount
/
maxRelationCount
;
const
r
=
Math
.
round
(
5
+
(
255
-
5
)
*
ratio
);
const
g
=
Math
.
round
(
95
+
(
77
-
95
)
*
ratio
);
const
b
=
Math
.
round
(
194
+
(
79
-
194
)
*
ratio
);
const
b
=
Math
.
round
(
194
+
(
194
-
194
)
*
ratio
);
const
lineColor
=
`rgb(${r
}
, ${g
}
, ${b
}
)`
;
const
lineWidth
=
1
+
(
relation
.
tagetMemberCount
/
maxRelationCount
)
*
7
;
linesData
.
push
({
name
:
`${
sourceCountryName
}
- ${targetCountryZh
Name
}
`
,
sourceName
:
sourceCountry
Name
,
targetName
:
targetCountryZh
Name
,
name
:
`${
finalSourceName
}
- ${finalTarget
Name
}
`
,
sourceName
:
finalSource
Name
,
targetName
:
finalTarget
Name
,
coords
:
[
sourceCoord
,
targetCoord
],
value
:
relation
.
tagetMemberCount
,
lineStyle
:
{
...
...
@@ -745,7 +789,7 @@ function createChart() {
}
);
targetPoints
.
push
({
name
:
targetCountryZh
Name
,
name
:
finalTarget
Name
,
value
:
targetCoord
,
symbolSize
:
8
+
(
relation
.
tagetMemberCount
/
maxRelationCount
)
*
7
,
itemStyle
:
{
...
...
@@ -753,7 +797,7 @@ function createChart() {
}
}
);
}
else
{
console
.
warn
(
`无法找到目标国家 ${targetCountryZhName
}
的坐标`
);
console
.
warn
(
`无法找到目标国家 ${targetCountryZhName
}
的坐标
,尝试的名称都不匹配
`
);
}
}
);
...
...
@@ -776,7 +820,7 @@ function createChart() {
}
);
targetPoints
.
unshift
({
name
:
sourceCountry
Name
,
name
:
finalSource
Name
,
value
:
sourceCoord
,
symbolSize
:
15
,
itemStyle
:
{
...
...
@@ -803,44 +847,24 @@ function createChart() {
}
}
// ... existing code ...
// 处理国家列表项点击事件
// 在 handleCountryClick 中添加调试代码
const
handleCountryClick
=
country
=>
{
currentSelectedCountry
.
value
=
country
;
if
(
currentSelectedCountry
.
value
?.
name
===
country
.
name
)
{
// currentSelectedCountry.value = null;
return
;
}
else
{
currentSelectedCountry
.
value
=
country
;
}
console
.
log
(
"=== 点击的国家信息 ==="
);
console
.
log
(
"国家中文名:"
,
country
.
zhName
);
console
.
log
(
"国家英文名:"
,
country
.
name
);
// 检查坐标映射
const
coordFromZhName
=
countryCoordMap
[
country
.
zhName
];
const
coordFromEnName
=
countryCoordMap
[
country
.
name
];
const
coordFromMappedName
=
countryCoordMap
[
nameMap
[
country
.
name
]];
console
.
log
(
"通过中文名获取坐标:"
,
coordFromZhName
);
console
.
log
(
"通过英文名获取坐标:"
,
coordFromEnName
);
console
.
log
(
"通过映射名获取坐标:"
,
coordFromMappedName
);
console
.
log
(
"====================="
);
console
.
log
(
"联盟关系数据:"
,
country
.
memberRelation
);
nextTick
(()
=>
{
createChart
();
}
);
}
;
// function initMap()
{
// chartDom.value = document.getElementById("echartsMap");
// if (!chartDom.value) return;
// if (myChart.value) myChart.value.dispose();
// myChart.value = echarts.init(chartDom.value);
// myChart.value.showLoading();
// echarts.registerMap("world", mapJson);
// createChart();
// myChart.value.hideLoading();
//
}
function
initMap
()
{
chartDom
.
value
=
document
.
getElementById
(
"echartsMap"
);
unionChartDom
.
value
=
document
.
getElementById
(
"echartsUnionMap"
);
...
...
@@ -1489,22 +1513,21 @@ const getPredictionList = async () => {
}
;
// 获取排华联盟数量
const
getUnionCountList
=
async
()
=>
{
try
{
const
res
=
await
getUnionCount
({
page
:
1
,
pageSize
:
100
,
domainId
:
selectedFieldForLatest
.
value
}
);
if
(
res
&&
res
.
code
===
200
)
{
console
.
log
(
"----getUnionCountList"
,
res
.
data
);
// 处理一下数据
countryTotalList
.
value
=
res
.
data
.
content
.
sort
((
a
,
b
)
=>
b
.
count
-
a
.
count
)
.
map
(
item
=>
{
item
.
value
=
item
.
count
;
item
.
zhName
=
item
.
name
;
// 1. 尝试直接从映射表获取
let
mappedName
=
countryNameMap
[
item
.
ename
];
// 2. 如果映射表没有,尝试简单的模糊匹配
if
(
!
mappedName
&&
item
.
ename
)
{
let
tempName
=
item
.
ename
.
replace
(
/Republic of /i
,
""
)
...
...
@@ -1517,7 +1540,8 @@ const getUnionCountList = async () => {
mappedName
=
tempName
;
}
item
.
name
=
mappedName
||
item
.
ename
;
item
.
name
=
nameMap
[
item
.
ename
]
||
mappedName
||
item
.
ename
;
item
.
originalEnName
=
item
.
ename
;
return
item
;
}
);
...
...
@@ -1878,11 +1902,15 @@ watch(activeDate, async () => {
padding
:
4
px
12
px
4
px
12
px
;
box
-
sizing
:
border
-
box
;
// border: 1px solid rgba(234, 236, 238, 1);
border
-
radius
:
50
px
;
//
border-radius: 50px;
/* 业务系统/模态背景模糊 */
backdrop
-
filter
:
blur
(
30
px
);
background
:
rgba
(
255
,
255
,
255
,
0.65
);
&
.
selected
-
country
{
background
-
color
:
rgb
(
246
,
250
,
255
)
!
important
;
}
.
item
-
left
{
display
:
flex
;
align
-
items
:
center
;
...
...
src/views/bill/ProcessOverviewDetailDialog.vue
0 → 100644
浏览文件 @
78d38246
<
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
浏览文件 @
78d38246
...
...
@@ -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
浏览文件 @
78d38246
...
...
@@ -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
浏览文件 @
78d38246
<
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
();
const
billFullText
=
ref
(
""
);
import
{
ref
}
from
"vue"
;
import
DecreeOriginal
from
"@/components/base/DecreeOriginal/index.vue"
;
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
reportData
=
ref
([]);
const
handle
Back
=
()
=>
{
router
.
back
();
const
handle
Download
=
()
=>
{
// 后续接入新接口/下载逻辑
};
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
;
}
}
height
:
100%
;
}
</
style
>
src/views/bill/deepDig/poliContribution/index.vue
浏览文件 @
78d38246
...
...
@@ -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
浏览文件 @
78d38246
...
...
@@ -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
浏览文件 @
78d38246
...
...
@@ -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
浏览文件 @
78d38246
350 Bytes
src/views/decree/decreeOriginal/assets/icons/search.png
0 → 100644
浏览文件 @
78d38246
399 Bytes
src/views/decree/decreeOriginal/index.vue
浏览文件 @
78d38246
<
template
>
<div
class=
"layout-container"
>
<!-- 导航菜单 -->
<div
class=
"layout-main"
>
<div
class=
"
layout-main-box
"
>
<div
class=
"
header-main
"
>
<div
class=
"layout-main-header"
>
<div
class=
"icon"
>
<img
:src=
"summaryInfo.imageUrl"
alt=
""
/>
</div>
<div
class=
"info"
>
<div
class=
"info-box1 one-line-ellipsis"
>
{{
summaryInfo
.
name
}}
</div>
<div
class=
"info-box1 one-line-ellipsis"
>
{{
summaryInfo
.
name
||
"--"
}}
</div>
<div
class=
"info-box2"
>
<div
class=
"info-box2-item"
>
{{
summaryInfo
.
postDate
}}
</div>
<div
class=
"info-box2-item"
>
{{
summaryInfo
.
postDate
||
"--"
}}
</div>
|
<div
class=
"info-box2-item"
>
{{
summaryInfo
.
orgName
}}
</div>
<div
class=
"info-box2-item"
>
{{
summaryInfo
.
orgName
||
"--"
}}
</div>
|
<div
class=
"info-box2-item one-line-ellipsis"
>
{{
summaryInfo
.
ename
}}
</div>
<div
class=
"info-box2-item one-line-ellipsis"
>
{{
summaryInfo
.
ename
||
"--"
}}
</div>
</div>
</div>
<div
class=
"layout-main-header-right-box"
>
<div
class=
"right-box-top"
>
<div
class=
"time"
>
{{
summaryInfo
.
postDate
}}
</div>
<div
class=
"name"
>
{{
summaryInfo
.
orgName
}}
</div>
<div
class=
"time"
>
{{
summaryInfo
.
postDate
||
"--"
}}
</div>
<div
class=
"name"
>
{{
summaryInfo
.
orgName
||
"--"
}}
</div>
</div>
</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"
>
<el-icon><Document
/></el-icon>
<div
class=
"text"
>
下载
</div>
</div>
-->
<div
class=
"btn"
@
click=
"handleFindWord('open')"
>
<el-icon><Search
/></el-icon>
<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
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
{
...
...
@@ -128,73 +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
(()
=>
{
handleFindWord
(
'next'
)
})
}
}
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
({});
...
...
@@ -213,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
=
{
...
...
@@ -241,30 +125,25 @@ 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%
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
.layout-main-box
{
padding
:
16px
0
;
.header-main
{
padding
:
17px
0
;
width
:
100%
;
border-bottom
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
border-top
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
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
;
...
...
@@ -287,6 +166,7 @@ onMounted(() => {
margin-right
:
40px
;
width
:
20px
;
flex
:
auto
;
.info-box1
{
width
:
100%
;
color
:
rgba
(
59
,
65
,
75
,
1
);
...
...
@@ -316,6 +196,7 @@ onMounted(() => {
white-space
:
nowrap
;
padding
:
0
10px
;
}
.info-box2-item
:first-child
{
padding-left
:
0px
;
}
...
...
@@ -325,6 +206,7 @@ onMounted(() => {
.layout-main-header-right-box
{
.right-box-top
{
white-space
:
nowrap
;
.time
{
height
:
24px
;
line-height
:
24px
;
...
...
@@ -360,152 +242,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
;
gap
:
8px
;
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
;
}
}
}
.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
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论