Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
e4e9a0d4
提交
e4e9a0d4
authored
4月 22, 2026
作者:
coderBryanFu
浏览文件
操作
浏览文件
下载
差异文件
Merge branch 'pre' of
http://8.140.26.4:10003/caijian/risk-monitor
into fk-dev
上级
1bef077e
bb55d9a0
流水线
#598
已通过 于阶段
in 1 分 33 秒
变更
39
流水线
1
隐藏空白字符变更
内嵌
并排
正在显示
39 个修改的文件
包含
1390 行增加
和
723 行删除
+1390
-723
index.vue
src/components/base/RiskSignalOverviewDetailDialog/index.vue
+28
-43
index.js
src/router/index.js
+1
-1
comTitle.vue
src/views/coopRestriction/common/comTitle.vue
+21
-14
index.vue
src/views/coopRestriction/components/dataNew/index.vue
+23
-15
index.vue
src/views/coopRestriction/components/dataSub/index.vue
+2
-2
index.vue
src/views/coopRestriction/components/resLib/index.vue
+18
-34
index.vue
src/views/coopRestriction/detail/index.vue
+4
-5
index.vue
src/views/coopRestriction/index.vue
+2
-2
ChartAiAnalysis.vue
src/views/exportControl/components/ChartAiAnalysis.vue
+128
-0
title.vue
src/views/exportControl/components/title.vue
+2
-2
index.vue
src/views/exportControl/index.vue
+357
-183
index.vue
...ol/v2.0SingleSanction/components/dataStatistics/index.vue
+234
-130
index.vue
...v2.0SingleSanction/components/sanctionsOverview/index.vue
+71
-34
index.vue
...ews/exportControl/v2.0SingleSanction/originPage/index.vue
+4
-4
title.vue
src/views/finance/components/title.vue
+2
-2
index.vue
src/views/finance/index.vue
+169
-31
index.vue
...nce/singleSanction/components/sanctionsOverview/index.vue
+78
-36
index.vue
src/views/finance/singleSanction/index.vue
+1
-1
index.vue
src/views/finance/singleSanction/originPage/index.vue
+4
-4
index.vue
src/views/thinkTank/CongressHearingView/index.vue
+25
-6
index.vue
src/views/thinkTank/ReportDetail/index.vue
+0
-3
index.vue
src/views/thinkTank/ReportDetail/reportAnalysis/index.vue
+25
-3
index.vue
src/views/thinkTank/SurveyProjectView/index.vue
+1
-4
index.vue
src/views/thinkTank/ThinkTankDetail/PolicyTracking/index.vue
+22
-29
index.vue
src/views/thinkTank/ThinkTankDetail/index.vue
+0
-3
index.vue
src/views/thinkTank/ThinkTankDetail/thinkDynamics/index.vue
+30
-40
index.vue
src/views/thinkTank/ThinkTankDetail/thinkInfo/index.vue
+2
-2
index.vue
src/views/thinkTank/TipTab/index.vue
+1
-1
ThinkTankPolicyAdviceOverview.vue
...ws/thinkTank/components/ThinkTankPolicyAdviceOverview.vue
+3
-2
index.vue
src/views/thinkTank/index.vue
+23
-26
index.vue
src/views/thinkTank/reportOriginal/index.vue
+2
-5
pdf.vue
src/views/thinkTank/reportOriginal/pdf.vue
+82
-50
risk-icon-blue.png
src/views/viewRiskSignal/assets/images/risk-icon-blue.png
+0
-0
risk-icon-green.png
src/views/viewRiskSignal/assets/images/risk-icon-green.png
+0
-0
risk-icon-orange.png
src/views/viewRiskSignal/assets/images/risk-icon-orange.png
+0
-0
risk-icon-red.png
src/views/viewRiskSignal/assets/images/risk-icon-red.png
+0
-0
risk-icon-yellow.png
src/views/viewRiskSignal/assets/images/risk-icon-yellow.png
+0
-0
warning-red.svg
src/views/viewRiskSignal/assets/images/warning-red.svg
+0
-0
index.vue
src/views/viewRiskSignal/index.vue
+25
-6
没有找到文件。
src/components/base/RiskSignalOverviewDetailDialog/index.vue
浏览文件 @
e4e9a0d4
<
template
>
<el-dialog
v-model=
"visible"
class=
"risk-signal-detail-dialog"
modal-class=
"risk-signal-detail-modal"
width=
"1280px"
align-center
:z-index=
"zIndex"
:show-close=
"true"
destroy-on-close
@
closed=
"handleClosed"
>
<el-dialog
v-model=
"visible"
class=
"risk-signal-detail-dialog"
modal-class=
"risk-signal-detail-modal"
width=
"1280px"
align-center
:z-index=
"zIndex"
:show-close=
"true"
destroy-on-close
@
closed=
"handleClosed"
>
<template
#
header
>
<img
class=
"header-icon"
src=
"@/views/viewRiskSignal/assets/images/risk-icon.png"
alt=
""
/>
<span
v-if=
"listLevelText"
class=
"risk-signal-detail-dialog__level"
:class=
"listLevelModifierClass"
>
{{
listLevelText
}}
</span>
<img
class=
"header-icon"
:src=
"headerIconSrc"
alt=
""
/>
<span
v-if=
"listLevelText"
class=
"risk-signal-detail-dialog__level"
:class=
"listLevelModifierClass"
>
{{
listLevelText
}}
</span>
<div
v-if=
"bodyFromApi"
class=
"risk-signal-detail-dialog__read-indicator"
>
<el-icon
v-if=
"riskDetailStatus === false"
class=
"risk-signal-detail-dialog__header-badge-close"
>
<Close
/>
</el-icon>
<img
v-else-if=
"riskDetailStatus === true"
class=
"risk-signal-detail-dialog__header-badge-read"
:src=
"greenRightImg"
alt=
""
/>
<img
v-else-if=
"riskDetailStatus === true"
class=
"risk-signal-detail-dialog__header-badge-read"
:src=
"greenRightImg"
alt=
""
/>
<span
v-if=
"riskDetailStatus != null"
class=
"read"
>
{{
riskDetailStatus
?
"已读"
:
"未读"
}}
</span>
</div>
</
template
>
...
...
@@ -35,28 +19,19 @@
<div
v-if=
"riskDetailItem.title"
class=
"risk-signal-detail-dialog__body"
>
<span
class=
"risk-signal-detail-dialog__title"
>
{{ riskDetailItem.title }}
</span>
<div
v-if=
"riskDetailItem.directionLabels.length"
class=
"risk-signal-detail-dialog__directions"
>
<div
v-for=
"(dirLabel, dirIndex) in riskDetailItem.directionLabels"
<div
v-for=
"(dirLabel, dirIndex) in riskDetailItem.directionLabels"
:key=
"'overview-risk-detail-direction-' + dirIndex + '-' + dirLabel"
class=
"risk-signal-detail-dialog__origin"
>
{{ dirLabel }}
</div>
class=
"risk-signal-detail-dialog__origin"
>
{{ dirLabel }}
</div>
</div>
<div
class=
"risk-signal-detail-dialog__meta"
>
<span>
{{ metaLine }}
</span>
<div
v-if=
"riskDetailItem.tag.length"
class=
"risk-signal-detail-dialog__tags"
>
<AreaTag
v-for=
"(tag, index) in riskDetailItem.tag"
:key=
"'overview-risk-detail-tag-' + index + '-' + tag"
:tag-name=
"tag"
>
{{ tag }}
</AreaTag>
<AreaTag
v-for=
"(tag, index) in riskDetailItem.tag"
:key=
"'overview-risk-detail-tag-' + index + '-' + tag"
:tag-name=
"tag"
>
{{ tag }}
</AreaTag>
</div>
</div>
</div>
<div
v-if=
"showRelationBar"
class=
"risk-signal-detail-dialog_relation"
@
click=
"handleRelationClick"
>
<div
v-if=
"showRelationBar"
class=
"risk-signal-detail-dialog_relation"
@
click=
"handleRelationClick"
>
<div
class=
"relation"
>
<div
class=
"logo"
>
<img
src=
"@/views/viewRiskSignal/assets/images/logo.png"
alt=
""
/>
...
...
@@ -72,12 +47,8 @@
</div>
</div>
<
template
#
footer
>
<el-button
type=
"primary"
class=
"risk-signal-detail-dialog__action-btn"
:loading=
"confirmLoading"
@
click=
"handleConfirm"
>
<el-button
type=
"primary"
class=
"risk-signal-detail-dialog__action-btn"
:loading=
"confirmLoading"
@
click=
"handleConfirm"
>
确定
</el-button>
</
template
>
...
...
@@ -91,6 +62,11 @@ import AreaTag from "@/components/base/AreaTag/index.vue";
import
{
ElMessage
}
from
"element-plus"
;
import
{
Close
}
from
"@element-plus/icons-vue"
;
import
greenRightImg
from
"@/views/viewRiskSignal/assets/images/green-right.png"
;
import
riskIconRed
from
"@/views/viewRiskSignal/assets/images/risk-icon-red.png"
;
import
riskIconOrange
from
"@/views/viewRiskSignal/assets/images/risk-icon-orange.png"
;
import
riskIconYellow
from
"@/views/viewRiskSignal/assets/images/risk-icon-yellow.png"
;
import
riskIconGreen
from
"@/views/viewRiskSignal/assets/images/risk-icon-green.png"
;
import
riskIconBlue
from
"@/views/viewRiskSignal/assets/images/risk-icon-blue.png"
;
import
{
getRiskSignalInfoById
,
updateRiskSignalStatus
}
from
"@/api/riskSignal/index.js"
;
import
{
buildListRowFallbackFromRawRow
,
...
...
@@ -180,6 +156,15 @@ const listLevelModifierClass = computed(
()
=>
`risk-signal-detail-dialog__level--
${
getRiskDetailLevelModifier
(
listLevelText
.
value
)}
`
);
const
headerIconSrc
=
computed
(()
=>
{
const
lv
=
getRiskDetailLevelModifier
(
listLevelText
.
value
);
if
(
lv
===
"lv1"
)
return
riskIconRed
;
if
(
lv
===
"lv2"
)
return
riskIconOrange
;
if
(
lv
===
"lv3"
)
return
riskIconYellow
;
if
(
lv
===
"lv4"
)
return
riskIconGreen
;
return
riskIconBlue
;
});
const
fieldMap
=
computed
(()
=>
({
nameField
:
props
.
nameField
,
postDateField
:
props
.
postDateField
,
...
...
src/router/index.js
浏览文件 @
e4e9a0d4
...
...
@@ -147,7 +147,7 @@ const router = createRouter({
// 2)登录成功回跳带 ?token=:先 setToken 并同步 bootId,再去掉 URL 中的 token(须先于 clearTokenIfNewDevBoot,避免误清刚写入的登录态)
// 3)已有本地 token:正常走前端路由
router
.
beforeEach
((
to
,
from
,
next
)
=>
{
//
【新增】
在每次路由跳转开始前,取消上一个页面所有未完成的请求
// 在每次路由跳转开始前,取消上一个页面所有未完成的请求
// 这能防止旧页面的数据回来覆盖新页面,也能减少服务器压力
cancelAllRequests
();
if
(
import
.
meta
.
env
.
DEV
)
{
...
...
src/views/coopRestriction/common/comTitle.vue
浏览文件 @
e4e9a0d4
<
template
>
<div
class=
"com-title"
>
<div
class=
"cl1"
></div>
<div
class=
"com-title"
>
<div
class=
"cl1"
></div>
<div
class=
"cl2"
></div>
<div
class=
"title"
>
{{
title
}}
</div>
<div
class=
"cl3"
></div>
</div>
</div>
</
template
>
<
script
setup
>
import
{
ref
}
from
"vue"
;
// 传入的title数据
const
props
=
defineProps
({
title
:
{
type
:
String
,
default
:
""
}
title
:
{
type
:
String
,
default
:
""
}
});
</
script
>
<
style
scoped
lang=
"scss"
>
*
{
margin
:
0
;
padding
:
0
;
margin
:
0
;
padding
:
0
;
}
.com-title
{
width
:
1575
px
;
height
:
42px
;
width
:
1600
px
;
height
:
42px
;
display
:
flex
;
align-items
:
center
;
.cl1
{
width
:
24px
;
height
:
30px
;
background-color
:
rgba
(
174
,
214
,
255
,
1
);
margin-right
:
8px
;
}
.cl2
{
width
:
8px
;
height
:
30px
;
background-color
:
rgba
(
174
,
214
,
255
,
1
);
margin-right
:
8px
;
}
.title
{
width
:
152px
;
height
:
42px
;
text-align
:
center
;
font-size
:
32px
;
...
...
@@ -50,13 +54,16 @@ const props = defineProps({
font-family
:
'Microsoft YaHei'
;
line-height
:
42px
;
margin-right
:
8px
;
width
:
fit-content
;
/* 核心:强制不换行 */
white-space
:
nowrap
;
}
.cl3
{
width
:
1
367px
;
width
:
1
00%
;
height
:
1px
;
background-color
:
rgba
(
174
,
214
,
255
,
1
);
box-sizing
:
border-box
;
}
}
</
style
>
src/views/coopRestriction/components/dataNew/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -33,12 +33,9 @@
</li>
<li>
<span
class=
"ul-title"
>
涉及领域:
</span>
<div
class=
"ul-tags"
v-if=
"item.AREA"
>
<span
v-for=
"(field, fIndex) in typeof item.AREA === 'string'
? item.AREA.split(',')
: item.AREA"
:key=
"fIndex"
class=
"ul-pie"
:class=
"'cl' + ((fIndex % 3) + 1)"
>
{{
field
}}
</span>
<div
class=
"ul-tags"
v-if=
"getAreaTagList(item).length"
>
<AreaTag
v-for=
"(field, fIndex) in getAreaTagList(item)"
:key=
"`$
{field}-${fIndex}`"
:tagName="field" />
</div>
<span
v-else
class=
"ul-content"
>
未知
</span>
</li>
...
...
@@ -113,13 +110,8 @@
<RiskSignal
:list=
"riskSignals"
@
more-click=
"handleToMoreRiskSignal"
postDate=
"time"
name=
"content"
riskLevel=
"title"
@
item-click=
"handleRiskSignalItemToManage"
/>
<RiskSignalOverviewDetailDialog
v-model=
"isRiskOverviewDetailOpen"
:row=
"riskOverviewDetailRow"
name-field=
"content"
post-date-field=
"time"
risk-level-field=
"title"
/>
<RiskSignalOverviewDetailDialog
v-model=
"isRiskOverviewDetailOpen"
:row=
"riskOverviewDetailRow"
name-field=
"content"
post-date-field=
"time"
risk-level-field=
"title"
/>
</div>
</
template
>
...
...
@@ -132,6 +124,7 @@ import { navigateToViewRiskSignal } from "@/utils/riskSignalOverviewNavigate";
import
{
getCoopRestrictionTrends
,
getCoopRestrictionSignals
}
from
"@/api/coopRestriction/coopRestriction.js"
;
import
defaultImg
from
"./assets/usImg.png"
;
import
CommonPrompt
from
"../../commonPrompt/index.vue"
;
import
AreaTag
from
"@/components/base/AreaTag/index.vue"
;
// 合作限制-查询风险信号数据
const
getCoopRestrictionSignalsData
=
async
()
=>
{
...
...
@@ -168,6 +161,20 @@ const getCoopRestrictionTrendsData = async () => {
}
};
const
getAreaTagList
=
(
item
)
=>
{
const
raw
=
item
?.
AREA
;
if
(
Array
.
isArray
(
raw
))
{
return
raw
.
map
(
v
=>
String
(
v
||
""
).
trim
()).
filter
(
Boolean
);
}
if
(
typeof
raw
===
"string"
)
{
return
raw
.
split
(
","
)
.
map
(
v
=>
String
(
v
||
""
).
trim
())
.
filter
(
Boolean
);
}
return
[];
};
// 轮播图手动切换
const
handlePrev
=
()
=>
{
if
(
carouselRef
.
value
)
{
...
...
@@ -350,7 +357,7 @@ onMounted(() => {
display
:
flex
;
.left-center-main
{
width
:
439px
;
width
:
1000dvb
;
height
:
175px
;
position
:
relative
;
...
...
@@ -364,7 +371,7 @@ onMounted(() => {
}
.left-center-main-ul
{
width
:
439
px
;
width
:
1000
px
;
height
:
132px
;
ul
{
...
...
@@ -410,6 +417,7 @@ onMounted(() => {
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
8px
;
width
:
600px
;
}
.ul-pie
{
...
...
src/views/coopRestriction/components/dataSub/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -74,8 +74,8 @@ import TipTab from "@/views/thinkTank/TipTab/index.vue";
import
AiButton
from
"@/components/base/Ai/AiButton/index.vue"
;
import
AiPane
from
"@/components/base/Ai/AiPane/index.vue"
;
const
COOP_LEFT_TIP_TEXT
=
"
各类型合作限制政策对比,
数据来源:美对华科技合作限制信息平台"
;
const
COOP_RIGHT_TIP_TEXT
=
"
各领域规则分布情况,
数据来源:美对华科技合作限制信息平台"
;
const
COOP_LEFT_TIP_TEXT
=
"数据来源:美对华科技合作限制信息平台"
;
const
COOP_RIGHT_TIP_TEXT
=
"数据来源:美对华科技合作限制信息平台"
;
// 临时展示 mock(不改样式):右侧“各领域规则分布情况”
// 用完把这个开关改回 false 即可恢复走接口
...
...
src/views/coopRestriction/components/resLib/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -2,21 +2,14 @@
<div
class=
"reslib-page"
ref=
"reslibContainer"
>
<div
class=
"nav"
>
<div
v-for=
"item in navList"
:key=
"item.id"
class=
"nav-item"
:class=
"
{ active: item.id === activeItem }"
@click="
activeItem = item.id
">
@click="
handleNavItemClick(item.id)
">
{{
item
.
name
}}
</div>
</div>
<el-select
v-model=
"sortModel"
placeholder=
"发布时间"
class=
"select"
popper-class=
"coop-select-dropdown"
:teleported=
"true"
placement=
"bottom-start"
:popper-options=
"sortPopperOptions"
@
change=
"handleSortChange"
>
<template
#
prefix
>
<img
v-if=
"sortModel !== true"
src=
"@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image down.png"
class=
"select-prefix-img"
alt=
""
@
click
.
stop=
"toggleSortPrefix"
/>
<img
v-else
src=
"@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image up.png"
class=
"select-prefix-img"
alt=
""
@
click
.
stop=
"toggleSortPrefix"
/>
</
template
>
<el-option
:key=
"true"
label=
"正序"
:value=
"true"
/>
<el-option
:key=
"false"
label=
"倒序"
:value=
"false"
/>
</el-select>
<div
class=
"select"
>
<TimeSortSelectBox
:key=
"`coop-reslib-sort-$
{activeItem}`" :sort-demension="1"
@handle-px-change="handleCoopReslibPxChange" />
</div>
<div
class=
"main"
>
<div
class=
"left"
>
<div
class=
"left-ti1"
></div>
...
...
@@ -82,6 +75,7 @@
import
{
ref
,
onMounted
,
watch
,
computed
}
from
"vue"
;
import
{
useRouter
}
from
"vue-router"
;
import
{
getCoopRestrictionList
}
from
"@/api/coopRestriction/coopRestriction"
;
import
TimeSortSelectBox
from
"@/components/base/TimeSortSelectBox/index.vue"
;
import
defaultImg
from
"../../assets/images/default-icon2.png"
;
...
...
@@ -172,21 +166,15 @@ const activeItem = ref("0");
/** null:占位「发布时间」且默认倒序;true 正序;false 倒序(显式),与智库概览一致 */
const
sort
=
ref
(
null
);
const
sortPopperOptions
=
{
modifiers
:
[
{
name
:
"preventOverflow"
,
options
:
{
mainAxis
:
false
,
altAxis
:
false
}
},
{
name
:
"flip"
,
enabled
:
false
}
]
};
const
sortModel
=
computed
({
get
()
{
return
sort
.
value
;
},
set
(
v
)
{
sort
.
value
=
v
;
const
handleNavItemClick
=
(
id
)
=>
{
if
(
activeItem
.
value
===
id
)
{
return
;
}
});
// 切换 tab:默认回到倒序,并从第一页开始
sort
.
value
=
null
;
currentPage
.
value
=
1
;
activeItem
.
value
=
id
;
};
const
handleSortChange
=
()
=>
{
// 改变排序后从第一页开始
...
...
@@ -197,14 +185,10 @@ const handleSortChange = () => {
}
};
const
toggleSortPrefix
=
()
=>
{
sort
.
value
=
sort
.
value
===
true
?
false
:
true
;
// 切换排序后从第一页开始
if
(
currentPage
.
value
===
1
)
{
getMainDataList
();
}
else
{
currentPage
.
value
=
1
;
}
/** 合作限制数据库排序公共组件回调:1=时间倒序,2=时间正序(映射到现有 sort(true/false/null)) */
const
handleCoopReslibPxChange
=
(
val
)
=>
{
sort
.
value
=
Number
(
val
)
===
2
?
true
:
false
;
handleSortChange
();
};
const
dataList
=
ref
([
{
...
...
src/views/coopRestriction/detail/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -432,9 +432,6 @@ const dataList3 = ref([
padding
:
19px
0
20px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
box-shadow
:
0px
0px
20px
0px
rgba
(
25
,
69
,
130
,
0
.1
);
position
:
sticky
;
top
:
0
;
z-index
:
9
;
.nav-main
{
width
:
1600px
;
...
...
@@ -442,6 +439,7 @@ const dataList3 = ref([
margin
:
0
auto
;
display
:
flex
;
align-items
:
center
;
position
:
relative
;
img
{
width
:
72px
;
...
...
@@ -488,8 +486,9 @@ const dataList3 = ref([
display
:
flex
;
justify-content
:
right
;
position
:
absolute
;
bottom
:
0
;
margin-left
:
1224px
;
right
:
0
;
bottom
:
-20px
;
margin-left
:
0
;
.btn1
{
...
...
src/views/coopRestriction/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -34,14 +34,14 @@
</div>
<!-- 数据总览 -->
<div
class=
"datasub"
id=
"position3"
>
<com-title
title=
"
数据总
览"
/>
<com-title
title=
"
全景概
览"
/>
<div
class=
"datasub-main"
>
<dataSub
/>
</div>
</div>
<!-- 资源库 -->
<div
class=
"reslib"
id=
"position4"
>
<com-title
title=
"
资源
库"
/>
<com-title
title=
"
合作限制数据
库"
/>
<div
class=
"reslib-main"
>
<resLib
/>
</div>
...
...
src/views/exportControl/components/ChartAiAnalysis.vue
0 → 100644
浏览文件 @
e4e9a0d4
<
template
>
<div
class=
"chart-ai-analysis"
@
mouseenter=
"handleMouseEnter"
@
mouseleave=
"handleMouseLeave"
>
<!-- 触发按钮 -->
<div
class=
"ai-button-wrapper"
>
<AiButton
/>
</div>
<!-- AI 内容面板 -->
<transition
name=
"ai-fade"
>
<div
v-if=
"isVisible"
class=
"ai-pane-wrapper"
>
<div
class=
"ai-pane-content"
>
<!-- 1. Loading 状态 -->
<div
v-if=
"hookInstance.loading"
class=
"ai-loading-text"
>
智能总结生成中...
</div>
<!-- 2. 错误状态 -->
<div
v-else-if=
"hookInstance.error"
class=
"ai-error-text"
>
{{
hookInstance
.
error
}}
</div>
<!-- 3. 成功状态 -->
<div
v-else-if=
"hookInstance.interpretation"
class=
"ai-success-content"
>
<AiPane
:aiContent=
"hookInstance.interpretation"
/>
</div>
<!-- 4. 初始空状态 (可选,防止闪烁) -->
<div
v-else
class=
"ai-empty-text"
>
暂无数据
</div>
</div>
</div>
</transition>
</div>
</
template
>
<
script
setup
>
import
{
ref
}
from
"vue"
;
import
AiButton
from
"@/components/base/Ai/AiButton/index.vue"
;
import
AiPane
from
"@/components/base/Ai/AiPane/index.vue"
;
const
props
=
defineProps
({
// 接收由 useChartInterpretation() 创建的实例对象
hookInstance
:
{
type
:
Object
,
required
:
true
},
// 图表数据 Payload,用于首次请求
payload
:
{
type
:
Object
,
required
:
true
}
});
const
isVisible
=
ref
(
false
);
const
hasRequested
=
ref
(
false
);
const
handleMouseEnter
=
()
=>
{
isVisible
.
value
=
true
;
// 如果还没有请求过数据,则发起请求
if
(
!
hasRequested
.
value
&&
!
props
.
hookInstance
.
loading
)
{
hasRequested
.
value
=
true
;
// 调用 Hook 中的 interpret 方法
props
.
hookInstance
.
interpret
(
props
.
payload
);
}
};
const
handleMouseLeave
=
()
=>
{
isVisible
.
value
=
false
;
};
</
script
>
<
style
lang=
"scss"
scoped
>
.chart-ai-analysis
{
position
:
absolute
;
right
:
0px
;
bottom
:
15px
;
z-index
:
999
;
width
:
auto
;
height
:
auto
;
.ai-button-wrapper
{
display
:
flex
;
justify-content
:
flex-end
;
}
.ai-pane-wrapper
{
position
:
absolute
;
bottom
:
0
;
right
:
0
;
width
:
100%
;
// 确保面板出现在按钮上方或覆盖区域,根据原有 CSS 调整
// 原有 .ai-pane:hover 逻辑是宽度变宽,这里我们直接显示完整面板
background
:
#fff
;
border-radius
:
4px
;
box-shadow
:
0
-2px
12px
rgba
(
0
,
0
,
0
,
0
.1
);
padding
:
10px
;
box-sizing
:
border-box
;
.ai-pane-content
{
min-height
:
40px
;
.ai-loading-text
,
.ai-error-text
,
.ai-empty-text
{
font-size
:
14px
;
color
:
var
(
--
text-primary-50-color
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
min-height
:
40px
;
}
.ai-error-text
{
color
:
var
(
--
color-red-100
);
}
}
}
}
// 复用原有的淡入淡出动画
.ai-fade-enter-active
,
.ai-fade-leave-active
{
transition
:
opacity
0
.3s
ease
;
}
.ai-fade-enter-from
,
.ai-fade-leave-to
{
opacity
:
0
;
}
</
style
>
src/views/exportControl/components/title.vue
浏览文件 @
e4e9a0d4
...
...
@@ -43,8 +43,8 @@ defineProps({
.title-text
{
color
:
rgba
(
10
,
18
,
30
,
1
);
font-size
:
32px
;
font-family
:
$base-font-family
;
font-weight
:
bold
;
font-family
:
"Microsoft YaHei"
;
font-weight
:
700
;
margin-left
:
20px
;
white-space
:
nowrap
;
}
...
...
src/views/exportControl/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -3,12 +3,25 @@
<div
class=
"home-main"
ref=
"homeMainRef"
>
<div
class=
"home-top-bg"
></div>
<div
class=
"home-main-header"
>
<SearchContainer
style=
"margin-bottom: 0; margin-top: 48px; height: fit-content"
v-if=
"homeMainRef"
placeholder=
"搜索出口管制"
:containerRef=
"homeMainRef"
areaName=
"实体清单"
/>
<SearchContainer
style=
"margin-bottom: 0; margin-top: 48px; height: fit-content"
v-if=
"homeMainRef"
placeholder=
"搜索出口管制"
:containerRef=
"homeMainRef"
areaName=
"实体清单"
/>
<div
class=
"home-main-header-footer-info"
>
<InfoCard
v-for=
"(item, index) in infoList"
:key=
"item.id"
:title=
"item.nameZh"
:subtitle=
"item.nameAbbr"
:description=
"item.description"
:quantity=
"item.postCount"
:unit=
"item.unit"
:color=
"infoListColor[index]"
@
click=
"handleToEntityListNoId(item)"
/>
<InfoCard
v-for=
"(item, index) in infoList"
:key=
"item.id"
:title=
"item.nameZh"
:subtitle=
"item.nameAbbr"
:description=
"item.description"
:quantity=
"item.postCount"
:unit=
"item.unit"
:color=
"infoListColor[index]"
@
click=
"handleToEntityListNoId(item)"
/>
</div>
</div>
...
...
@@ -71,10 +84,18 @@
<div
class=
"box1-bottom-sanTypeId"
v-if=
"item.sanEntities?.length"
>
<div
class=
"box1-bottom-title"
>
· 涉及主要实体:
</div>
<div
class=
"box1-bottom-content"
>
<div
class=
"box1-bottom-content-item"
v-for=
"(ett, index) in item.sanEntities"
:key=
"index"
@
click=
"handleEntityClick(ett)"
>
<el-image
v-if=
"ett.img"
class=
"box1-bottom-content-item-img"
:src=
"ett.img"
alt=
""
></el-image>
<div
class=
"box1-bottom-content-item"
v-for=
"(ett, index) in item.sanEntities"
:key=
"index"
@
click=
"handleEntityClick(ett)"
>
<el-image
v-if=
"ett.img"
class=
"box1-bottom-content-item-img"
:src=
"ett.img"
alt=
""
></el-image>
<div
v-else
class=
"box1-bottom-content-item-imgUndefined"
>
{{
(
ett
.
orgName
||
ett
.
orgNameZh
)?.
match
(
...
...
@@ -91,8 +112,12 @@
<div
class=
"box1-bottom-sanTypeId"
v-if=
"item.sanItems?.length > 0"
>
<div
class=
"box1-bottom-title"
>
· 涉及管制物项:
</div>
<div
class=
"box1-bottom-content__wx"
>
<div
class=
"box1-bottom-content__wx-item"
v-for=
"(ett, index) in item.sanItems"
:key=
"index"
@
click=
"handleWxClick(item)"
>
<div
class=
"box1-bottom-content__wx-item"
v-for=
"(ett, index) in item.sanItems"
:key=
"index"
@
click=
"handleWxClick(item)"
>
<div
class=
"box1-bottom-content__wx-item-id"
>
{{
ett
.
id
}}
</div>
...
...
@@ -124,86 +149,35 @@
</custom-container>
</el-col>
<el-col
:span=
"8"
style=
"padding-right: 0px"
>
<!-- <custom-container
titleType="danger"
title="风险信号"
:headerNum="warningList.length"
:titleIcon="dangerIcon"
height="450px"
>
<template #default>
<div class="box2-main">
<div
class="box2-main-item"
v-for="(item, index) in warningList"
:key="index"
@click="handleToRiskSignalDetail"
>
<div
class="item-left"
:class="{
itemLeftStatus1: item.status === '一般风险',
itemLeftStatus2: item.status === '重大风险'
}"
>
{{ item.status }}
</div>
<div class="item-right">
<div class="text">
<CommonPrompt :content="item.title">
{{ item.title }}
</CommonPrompt>
</div>
<div class="time">{{ item.time }}</div>
</div>
</div>
<div class="box2-footer" @click="handleToMoreRiskSignal">
<div class="icon">
<img src="./assets/images/box2-footer-icon.png" alt="" />
</div>
<div class="text">{{ "查看更多" }}</div>
</div>
</div>
</template>
</custom-container> -->
<RiskSignal
:list=
"warningList"
@
item-click=
"handleToRiskSignalDetail"
@
more-click=
"handleToMoreRiskSignal"
riskLevel=
"signalLevel"
postDate=
"signalTime"
name=
"signalTitle"
/>
<RiskSignal
:list=
"warningList"
@
item-click=
"handleToRiskSignalDetail"
@
more-click=
"handleToMoreRiskSignal"
riskLevel=
"signalLevel"
postDate=
"signalTime"
name=
"signalTitle"
/>
</el-col>
</el-row>
<el-row
:gutter=
"16"
style=
"width: 1600px; margin: 0 auto; height: 50px; margin-top: 64px"
>
<CustomTitle
id=
"position2"
title=
"资讯要闻"
/>
</el-row>
<!-- <el-col :span="12">
<custom-container title="新闻资讯" :titleIcon="newsIcon" height="450px">
<template #header-right>
<el-button type="primary" link @click="handleToMoreNews">
{{ "更多 +" }}
</el-button>
</template>
<template #default>
<div class="news-list">
<NewsList :list-data="newsList" @item-click="item => handleNewsInfoClick(item)" />
</div>
</template>
</custom-container>
</el-col> -->
<div
class=
"center-center"
>
<NewsList
:newsList=
"newsList"
@
item-click=
"handleNewsInfoClick"
@
more-click=
"handleToMoreNews"
content=
"newsContent"
/>
<MessageBubble
:messageList=
"socialMediaList"
@
person-click=
"handlePerClick"
imageUrl=
"avatar"
@
more-click=
"handleToSocialDetail"
/>
<!-- <custom-container title="社交媒体" :titleIcon="dialogIcon" height="450px">
<template #default>
<div class="dialog-list">
<MessageBubble v-for="(item, index) in socialMediaList" @click="handlePerClick(item)"
@info-click="handleMediaClick(item)" :key="index" :avatar="item.avatar" :name="item.name"
:time="item.time" :source="item.source" :content="item.content" />
</div>
</template>
</custom-container> -->
<NewsList
:newsList=
"newsList"
@
item-click=
"handleNewsInfoClick"
@
more-click=
"handleToMoreNews"
content=
"newsContent"
/>
<MessageBubble
:messageList=
"socialMediaList"
@
person-click=
"handlePerClick"
imageUrl=
"avatar"
@
more-click=
"handleToSocialDetail"
/>
</div>
<el-row
:gutter=
"16"
style=
"width: 1600px; margin: 0 auto; height: 510px; margin-top: 64px"
>
...
...
@@ -214,24 +188,31 @@
<div
class=
"box3"
>
<div
class=
"box3-content"
>
<div
class=
"box3-content-title"
>
实体清单发布频次统计
</div>
<el-table
:data=
"entityListReleaseFreq"
stripe
style=
"width: 100%"
@
row-click=
"handleEntityRowClick"
>
<el-table
:data=
"entityListReleaseFreq"
stripe
style=
"width: 100%"
@
row-click=
"handleEntityRowClick"
>
<el-table-column
prop=
"year"
label=
"年份"
width=
"200"
/>
<el-table-column
label=
"发布次数"
width=
"300"
>
<template
#
default=
"scope"
>
<div
style=
"display: flex; align-items: center"
>
<span
style=
"margin-right: 10px; width: 40px"
>
{{
scope
.
row
.
num
}}
次
</span>
<el-progress
:percentage=
"scope.row.percent * 100"
:show-text=
"false"
:status=
"getStatus(scope.row.percent)"
/>
<el-progress
:percentage=
"scope.row.percent * 100"
:show-text=
"false"
:status=
"getStatus(scope.row.percent)"
/>
</div>
</
template
>
</el-table-column>
<el-table-column
label=
"重点领域"
width=
"220"
align=
"center"
>
<
template
#
default=
"scope"
>
<div
style=
"display: flex; justify-content: center; align-items: center; gap: 5px"
>
<div
style=
"display: flex; justify-content: center; align-items: center; gap: 5px"
>
<AreaTag
v-for=
"tag in scope.row.tags"
:key=
"tag"
:tagName=
"tag"
/>
<!--
<el-tag
v-for=
"tag in scope.row.tags"
:key=
"tag"
:type=
"getTagType(tag)"
>
{{
tag
}}
</el-tag>
-->
</div>
</
template
>
</el-table-column>
...
...
@@ -240,34 +221,44 @@
<div
class=
"data-origin-icon"
>
<img
:src=
"tipsIcon"
alt=
""
/>
</div>
<div
class=
"data-origin-text"
>
美国商务部发布实体清单的频次,
数据来源:美国商务部官网
</div>
<div
class=
"data-origin-text"
>
数据来源:美国商务部官网
</div>
</div>
<div
class=
"ai-pane"
>
<AiButton
/>
<AiPane
:aiContent=
"entityListReleaseFreqChart.interpretation"
/>
<AiButton
@
mouseenter=
"handleShowAiPane('entityListReleaseFreqChart')"
/>
<AiPane
v-if=
"aiPaneVisible?.entityListReleaseFreqChart"
:aiContent=
"overviewAiContent.entityListReleaseFreqChart"
@
mouseleave=
"handleHideAiPane('entityListReleaseFreqChart')"
/>
</div>
</div>
<div
class=
"box3-content"
>
<div
class=
"box3-content-title"
>
商业管制清单发布频次统计
</div>
<el-table
:data=
"commerceControlListReleaseFreq"
stripe
style=
"width: 100%"
@
row-click=
"handleCommercialRowClick"
>
<el-table
:data=
"commerceControlListReleaseFreq"
stripe
style=
"width: 100%"
@
row-click=
"handleCommercialRowClick"
>
<el-table-column
prop=
"year"
label=
"年份"
width=
"200"
/>
<el-table-column
label=
"发布次数"
width=
"300"
>
<
template
#
default=
"scope"
>
<div
style=
"display: flex; align-items: center"
>
<span
style=
"margin-right: 10px; width: 40px"
>
{{
scope
.
row
.
num
}}
次
</span>
<el-progress
:percentage=
"scope.row.percent * 100"
:show-text=
"false"
:status=
"getStatus(scope.row.percent)"
/>
<el-progress
:percentage=
"scope.row.percent * 100"
:show-text=
"false"
:status=
"getStatus(scope.row.percent)"
/>
</div>
</
template
>
</el-table-column>
<el-table-column
label=
"重点领域"
width=
"220"
align=
"center"
>
<
template
#
default=
"scope"
>
<div
style=
"display: flex; justify-content: center; align-items: center; gap: 5px"
>
<div
style=
"display: flex; justify-content: center; align-items: center; gap: 5px"
>
<AreaTag
v-for=
"tag in scope.row.tags"
:key=
"tag"
:tagName=
"tag"
/>
<!--
<el-tag
v-for=
"tag in scope.row.tags"
:key=
"tag"
:type=
"getTagType(tag)"
>
{{
tag
}}
</el-tag>
-->
</div>
</
template
>
</el-table-column>
...
...
@@ -276,13 +267,15 @@
<div
class=
"data-origin-icon"
>
<img
:src=
"tipsIcon"
alt=
""
/>
</div>
<div
class=
"data-origin-text"
>
美国商务部发布商业管制清单的频次,数据来源:美国商务部官网
</div>
<div
class=
"data-origin-text"
>
数据来源:美国商务部官网
</div>
</div>
<div
class=
"ai-pane"
>
<AiButton
/>
<AiPane
:aiContent=
"commerceControlListReleaseFreqChart.interpretation"
/>
<AiButton
@
mouseenter=
"handleShowAiPane('commerceControlListReleaseFreqChart')"
/>
<AiPane
v-if=
"aiPaneVisible?.commerceControlListReleaseFreqChart"
:aiContent=
"overviewAiContent.commerceControlListReleaseFreqChart"
@
mouseleave=
"handleHideAiPane('commerceControlListReleaseFreqChart')"
/>
</div>
</div>
<div
class=
"box3-content"
style=
"display: none"
>
...
...
@@ -293,8 +286,11 @@
<
template
#
default=
"scope"
>
<div
style=
"display: flex; align-items: center"
>
<span
style=
"margin-right: 10px; width: 40px"
>
{{
scope
.
row
.
num
}}
次
</span>
<el-progress
:percentage=
"scope.row.percent * 100"
:show-text=
"false"
:status=
"getStatus(scope.row.percent)"
/>
<el-progress
:percentage=
"scope.row.percent * 100"
:show-text=
"false"
:status=
"getStatus(scope.row.percent)"
/>
</div>
</
template
>
</el-table-column>
...
...
@@ -322,17 +318,21 @@
<el-checkbox
v-model=
"domainChecked"
label=
"50%规则"
size=
"large"
/>
</
template
>
<
template
#
default
>
<EChart
:option=
"radarOption"
autoresize
:style=
"
{ height: '420px' }"
@chart-click="handleRadarChartClick" />
<EChart
:option=
"radarOption"
autoresize
:style=
"
{ height: '420px' }"
@chart-click="handleRadarChartClick"
/>
<div
class=
"data-origin-box"
>
<div
class=
"data-origin-icon"
>
<img
:src=
"tipsIcon"
alt=
""
/>
</div>
<div
class=
"data-origin-text"
>
进入实体清单的中国实体领域分布情况,
数据来源:美国商务部官网
</div>
<div
class=
"data-origin-text"
>
数据来源:美国商务部官网
</div>
</div>
<div
class=
"ai-pane"
>
<AiButton
/>
<AiPane
:aiContent=
"
radarChart.interpretation
"
/>
<AiButton
@
mouseenter=
"handleShowAiPane('radarChart')"
/>
<AiPane
:aiContent=
"
overviewAiContent.radarChart"
@
mouseleave=
"handleHideAiPane('radarChart')
"
/>
</div>
</
template
>
</custom-container>
...
...
@@ -341,24 +341,37 @@
<custom-container
title=
"制裁清单数量增长趋势"
:titleIcon=
"qushiIcon"
height=
"540px"
>
<
template
#
header-right
>
<div
style=
"display: flex; align-items: center; gap: 16px"
>
<el-checkbox
v-if=
"selectedEntityId != '13'"
v-model=
"trendChecked"
label=
"50%规则"
size=
"large"
/>
<el-checkbox
v-if=
"selectedEntityId != '13'"
v-model=
"trendChecked"
label=
"50%规则"
size=
"large"
/>
<el-select
v-model=
"selectedEntityId"
placeholder=
"请选择清单类型"
style=
"width: 160px"
>
<el-option
v-for=
"item in infoList"
:key=
"item.id"
:label=
"item.nameZh"
:value=
"item.id"
/>
</el-select>
</div>
</
template
>
<
template
#
default
>
<EChart
:option=
"trendOption"
autoresize
:style=
"
{ height: '420px' }"
@chart-click="handleMultiBarChartClick" />
<EChart
:option=
"trendOption"
autoresize
:style=
"
{ height: '420px' }"
@chart-click="handleMultiBarChartClick"
/>
<div
class=
"data-origin-box"
>
<div
class=
"data-origin-icon"
>
<img
:src=
"tipsIcon"
alt=
""
/>
</div>
<div
class=
"data-origin-text"
>
进入实体清单的中国实体数量变化趋势,
数据来源:美国商务部官网
</div>
<div
class=
"data-origin-text"
>
数据来源:美国商务部官网
</div>
</div>
<div
class=
"ai-pane"
>
<AiButton
/>
<AiPane
:aiContent=
"trendChart.interpretation"
/>
<AiButton
@
mouseenter=
"handleShowAiPane('trendChart')"
/>
<AiPane
v-if=
"aiPaneVisible?.trendChart"
:aiContent=
"overviewAiContent.trendChart"
@
mouseleave=
"handleHideAiPane('trendChart')"
/>
</div>
</
template
>
</custom-container>
...
...
@@ -368,9 +381,13 @@
<el-row
:gutter=
"16"
style=
"width: 1600px; margin: 0 auto; margin-top: 39px; padding-bottom: 60px"
>
<CustomTitle
id=
"position4"
title=
"资源库"
style=
"margin-top: 0px"
/>
<div
class=
"resource-tabs"
>
<div
v-for=
"tab in resourceTabs"
:key=
"tab.value"
class=
"resource-tab-item"
<div
v-for=
"tab in resourceTabs"
:key=
"tab.value"
class=
"resource-tab-item"
:class=
"{ active: activeResourceTab == tab.value, disabled: tab.disabled }"
@
click=
"handleResourceTabClick(tab)"
>
@
click=
"handleResourceTabClick(tab)"
>
{{ tab.label }}
</div>
</div>
...
...
@@ -383,15 +400,25 @@
<div
class=
"box4-item"
v-for=
"(item, idx) in sanctionProcessList"
:key=
"item.title"
>
<div
class=
"box4-item-left"
>
<el-image
:src=
"dotIcon"
alt=
"图片"
class=
"box4-item-left-icon"
/>
<div
class=
"box4-item-left-line"
v-if=
"idx + 1 != sanctionProcessList.length"
></div>
<div
class=
"box4-item-left-line"
v-if=
"idx + 1 != sanctionProcessList.length"
></div>
</div>
<div
class=
"box4-item-right"
>
<div
class=
"box4-item-right-header"
@
click=
"handleSanc(item)"
>
<span
class=
"box4-item-right-header-title"
>
{{
item
.
postDate
}}
—
{{
item
.
title
}}
</span>
<span
class=
"box4-item-right-header-title"
>
{{
item
.
postDate
}}
—
{{
item
.
title
}}
</span
>
<span
class=
"box4-item-right-header-desc"
>
{{
item
.
desc
}}
</span>
</div>
<el-tooltip
effect=
"dark"
:content=
"item.content"
popper-class=
"common-prompt-popper"
placement=
"top"
:show-after=
"500"
>
<el-tooltip
effect=
"dark"
:content=
"item.content"
popper-class=
"common-prompt-popper"
placement=
"top"
:show-after=
"500"
>
<div
class=
"box4-item-right-content"
>
{{
item
.
content
}}
</div>
...
...
@@ -399,8 +426,12 @@
</div>
</div>
</div>
<div
class=
"box4-footer"
:style=
"
{ marginTop: sanctionProcessList.length > 0 ? '0px' : 'auto' }">
<el-button
type=
"primary"
link
@
click=
"handleGetMore"
>
查看更多
<div
class=
"box4-footer"
:style=
"
{ marginTop: sanctionProcessList.length > 0 ? '0px' : 'auto' }"
>
<el-button
type=
"primary"
link
@
click=
"handleGetMore"
>
查看更多
<el-icon>
<DArrowRight
/>
</el-icon>
...
...
@@ -417,13 +448,24 @@
</
template
>
<
template
#
default
>
<div
class=
"box5"
>
<el-table
:data=
"entitiesList"
class=
"sanction-table"
stripe
empty-text=
"暂无数据"
height=
"700px"
header-row-class-name=
"table-header"
row-class-name=
"table-row"
>
<el-table
:data=
"entitiesList"
class=
"sanction-table"
stripe
empty-text=
"暂无数据"
height=
"700px"
header-row-class-name=
"table-header"
row-class-name=
"table-row"
>
<el-table-column
prop=
"name"
label=
"实体名称"
min-width=
"200"
>
<template
#
default=
"scope"
>
<div
class=
"tableName"
@
click=
"handleCompClick(scope.row)"
>
<el-image
v-if=
"scope.row.img"
class=
"box1-bottom-content-item-img"
:src=
"scope.row.img"
alt=
""
></el-image>
<el-image
v-if=
"scope.row.img"
class=
"box1-bottom-content-item-img"
:src=
"scope.row.img"
alt=
""
></el-image>
<div
v-else
class=
"box1-bottom-content-item-imgUndefined"
>
{{
(
scope
.
row
.
name
||
scope
.
row
.
enName
)?.
match
(
...
...
@@ -455,35 +497,22 @@
</
template
>
</el-table-column>
<!-- <el-table-column prop="strength" label="制裁强度" width="120" align="center">
<template #default="scope">
<div class="sanction-strength">
<div :class="['strength-bar', `strength-${scope.row.strength}`]"></div>
<span>{{ strengthLabels[scope.row.strength] }}</span>
</div>
</template>
</el-table-column> -->
<!-- <el-table-column prop="revenue" label="营收(亿元)" width="140" align="right">
<template #default="scope">
<span
:class="['revenue-cell', scope.row.revenue === '无营收数据' ? 'no-revenue' : '']"
>
{{ scope.row.revenue }}
</span>
</template>
</el-table-column> -->
<el-table-column
prop=
"revenue"
label=
"50%规则子企业"
width=
"280"
align=
"right"
>
<
template
#
default=
"scope"
>
<div
class=
"num-item"
v-if=
"scope.row.ruleOrgCount > 0"
>
<div
class=
"name-item"
:class=
"[
'revenue-cell',
scope.row.revenue === '无营收数据' ? 'no-revenue' : ''
]"
>
<div
class=
"name-item"
:class=
"[
'revenue-cell',
scope.row.revenue === '无营收数据' ? 'no-revenue' : ''
]"
>
{{
scope
.
row
.
ruleOrgList
[
0
].
orgName
}}
...等
</div>
<div
style=
"width: 50px; color: #409eff; cursor: pointer"
@
click=
"handleOrgClick(scope.row)"
>
<div
style=
"width: 50px; color: #409eff; cursor: pointer"
@
click=
"handleOrgClick(scope.row)"
>
{{
scope
.
row
.
ruleOrgCount
}}
家>
</div>
</div>
...
...
@@ -495,8 +524,15 @@
<!-- <div class="pagination-info">
第{{ currentPage }}页,共{{ totalPages }}页
</div> -->
<el-pagination
v-model:current-page=
"currentPage"
:page-size=
"pageSize"
:total=
"total"
:pager-count=
"5"
layout=
"prev, pager, next"
background
@
current-change=
"handlePageChange"
/>
<el-pagination
v-model:current-page=
"currentPage"
:page-size=
"pageSize"
:total=
"total"
:pager-count=
"5"
layout=
"prev, pager, next"
background
@
current-change=
"handlePageChange"
/>
</div>
</div>
</template>
...
...
@@ -562,8 +598,14 @@
</div>
<div
class=
"right-footer"
>
<div
class=
"total-count"
>
共
{{
totalAll
}}
项
</div>
<el-pagination
v-model:current-page=
"currentPageAll"
:page-size=
"pageSizeAll"
:total=
"totalAll"
layout=
"prev, pager, next"
background
@
current-change=
"handlePageChangeAll"
/>
<el-pagination
v-model:current-page=
"currentPageAll"
:page-size=
"pageSizeAll"
:total=
"totalAll"
layout=
"prev, pager, next"
background
@
current-change=
"handlePageChangeAll"
/>
</div>
</div>
</div>
...
...
@@ -576,8 +618,12 @@
</
template
>
</el-row>
</div>
<RuleSubsidiaryDialog
v-model=
"dialogVisible"
:company-name=
"currentRuleCompany"
:total-count=
"currentRuleCount"
:data-list=
"currentOrgList"
/>
<RuleSubsidiaryDialog
v-model=
"dialogVisible"
:company-name=
"currentRuleCompany"
:total-count=
"currentRuleCount"
:data-list=
"currentOrgList"
/>
</div>
<el-dialog
v-model=
"mediaVisible"
title=
"社交媒体信息"
width=
"500"
:before-close=
"handleMediaClose"
>
<div
class=
"dialog-content"
>
...
...
@@ -590,32 +636,34 @@
</div>
</
template
>
</el-dialog>
<RiskSignalOverviewDetailDialog
v-model=
"isRiskOverviewDetailOpen"
:row=
"riskOverviewDetailRow"
name-field=
"signalTitle"
post-date-field=
"signalTime"
risk-level-field=
"signalLevel"
/>
<RiskSignalOverviewDetailDialog
v-model=
"isRiskOverviewDetailOpen"
:row=
"riskOverviewDetailRow"
name-field=
"signalTitle"
post-date-field=
"signalTime"
risk-level-field=
"signalLevel"
/>
</template>
<
script
setup
>
//这是一个备注
import
NewsList
from
"@/components/base/newsList/index.vue"
;
import
RiskSignal
from
"@/components/base/riskSignal/index.vue"
;
import
{
getChartAnalysis
}
from
"@/api/aiAnalysis/index"
;
import
RiskSignalOverviewDetailDialog
from
"@/components/base/RiskSignalOverviewDetailDialog/index.vue"
;
import
{
onMounted
,
ref
,
computed
,
reactive
,
shallowRef
,
watch
,
nextTick
}
from
"vue"
;
import
{
useContainerScroll
}
from
"@/hooks/useScrollShow"
;
const
homeMainRef
=
ref
(
null
);
const
{
isShow
}
=
useContainerScroll
(
homeMainRef
);
import
*
as
echarts
from
"echarts"
;
import
setChart
from
"@/utils/setChart"
;
import
{
ElMessage
,
ElMessageBox
}
from
"element-plus"
;
import
listPage
from
"./v2.0CommercialControlList/components/sanctionsOverview/components/listPage/index.vue"
;
import
{
DArrowRight
,
Warning
,
Search
,
Message
}
from
"@element-plus/icons-vue"
;
import
EChart
from
"@/components/Chart/index.vue"
;
import
tipsIcon
from
"./assets/icons/info-icon.png"
;
import
AiButton
from
"@/components/base/Ai/AiButton/index.vue"
;
import
AiPane
from
"@/components/base/Ai/AiPane/index.vue"
;
import
AreaTag
from
"@/components/base/AreaTag/index.vue"
;
import
{
useChartInterpretation
}
from
"@/views/exportControl/utils/common"
;
const
sanctionCountChart
=
useChartInterpretation
();
import
{
TAGTYPE
}
from
"@/public/constant"
;
import
{
useGotoCompanyPages
}
from
"@/router/modules/company"
;
import
{
useGotoNewsDetail
}
from
"@/router/modules/news"
;
...
...
@@ -886,11 +934,12 @@ onMounted(async () => {
tags
:
item
.
domain
};
});
entityListReleaseFreqChart
.
interpret
({
type
:
"柱状图"
,
name
:
"美国商务部发布实体清单的频次"
,
data
:
entityListReleaseFreq
.
value
});
entityListReleaseFreqChartData
.
value
=
entityListReleaseFreq
.
value
;
// entityListReleaseFreqChart.interpret({
// type: "柱状图",
// name: "美国商务部发布实体清单的频次",
// data: entityListReleaseFreq.value
// });
commerceControlListReleaseFreq
.
value
=
_
.
map
(
cclList1
,
item
=>
{
return
{
year
:
item
.
year
,
...
...
@@ -899,11 +948,12 @@ onMounted(async () => {
tags
:
item
.
domain
};
});
commerceControlListReleaseFreqChart
.
interpret
({
type
:
"柱状图"
,
name
:
"美国商务部发布商业管制清单的频次"
,
data
:
commerceControlListReleaseFreq
.
value
});
commerceControlListReleaseFreqChartData
.
value
=
commerceControlListReleaseFreq
.
value
;
// commerceControlListReleaseFreqChart.interpret({
// type: "柱状图",
// name: "美国商务部发布商业管制清单的频次",
// data: commerceControlListReleaseFreq.value
// });
// 获取趋势图数据
await
fetchTrendData
();
...
...
@@ -944,13 +994,136 @@ const fetchTrendData = async () => {
});
if
(
res
&&
res
[
0
]
&&
res
[
0
].
yearDomainCount
)
{
trendOption
.
value
=
processYearDomainCountData
(
res
[
0
].
yearDomainCount
);
trendChart
.
interpret
({
type
:
"柱状图"
,
name
:
"制裁清单数量增长趋势"
,
data
:
res
[
0
].
yearDomainCount
});
trendChartData
.
value
=
res
[
0
].
yearDomainCount
;
// trendChart.interpret({ type: "柱状图", name: "制裁清单数量增长趋势", data: res[0].yearDomainCount });
}
}
catch
(
error
)
{
console
.
error
(
"获取趋势图数据失败:"
,
error
);
}
};
const
requestAiPaneContent
=
async
key
=>
{
if
(
!
key
||
aiPaneLoading
.
value
[
key
]
||
aiPaneFetched
.
value
[
key
])
return
;
aiPaneLoading
.
value
=
{
...
aiPaneLoading
.
value
,
[
key
]:
true
};
overviewAiContent
.
value
=
{
...
overviewAiContent
.
value
,
[
key
]:
"智能总结生成中..."
};
try
{
const
payload
=
buildAiChartPayload
(
key
);
const
res
=
await
getChartAnalysis
(
{
text
:
JSON
.
stringify
(
payload
)
},
{
onChunk
:
chunk
=>
{
const
current
=
overviewAiContent
.
value
[
key
];
const
base
=
current
===
"智能总结生成中..."
?
""
:
current
;
overviewAiContent
.
value
=
{
...
overviewAiContent
.
value
,
[
key
]:
base
+
chunk
};
}
}
);
const
list
=
res
?.
data
;
const
first
=
Array
.
isArray
(
list
)
?
list
[
0
]
:
null
;
const
interpretation
=
first
?.
解读
||
first
?.[
"解读"
];
// 流式已渲染过内容,最终用解析出的解读覆盖(保证显示格式统一)
if
(
interpretation
)
{
overviewAiContent
.
value
=
{
...
overviewAiContent
.
value
,
[
key
]:
interpretation
};
}
aiPaneFetched
.
value
=
{
...
aiPaneFetched
.
value
,
[
key
]:
true
};
}
catch
(
error
)
{
console
.
error
(
"获取图表解读失败"
,
error
);
overviewAiContent
.
value
=
{
...
overviewAiContent
.
value
,
[
key
]:
"智能总结生成失败"
};
}
finally
{
aiPaneLoading
.
value
=
{
...
aiPaneLoading
.
value
,
[
key
]:
false
};
}
};
const
trendChartData
=
ref
([]);
const
radarChartData
=
ref
([]);
const
entityListReleaseFreqChartData
=
ref
([]);
const
commerceControlListReleaseFreqChartData
=
ref
([]);
const
aiPaneVisible
=
ref
({
trendChart
:
false
,
radarChart
:
false
,
entityListReleaseFreqChart
:
false
,
commerceControlListReleaseFreqChart
:
false
});
const
overviewAiContent
=
ref
({
trendChart
:
"智能总结生成中..."
,
radarChart
:
"智能总结生成中..."
,
entityListReleaseFreqChart
:
"智能总结生成中..."
,
commerceControlListReleaseFreqChart
:
"智能总结生成中..."
});
const
aiPaneFetched
=
ref
({
trendChart
:
false
,
radarChart
:
false
,
entityListReleaseFreqChart
:
false
,
commerceControlListReleaseFreqChart
:
false
});
const
aiPaneLoading
=
ref
({
trendChart
:
false
,
radarChart
:
false
,
entityListReleaseFreqChart
:
false
,
commerceControlListReleaseFreqChart
:
false
});
const
chartLoading
=
ref
({
trendChart
:
false
,
radarChart
:
false
,
entityListReleaseFreqChart
:
false
,
commerceControlListReleaseFreqChart
:
false
});
const
buildAiChartPayload
=
key
=>
{
if
(
key
===
"trendChart"
)
{
return
{
type
:
"柱状图"
,
name
:
"制裁清单数量增长趋势"
,
data
:
trendChartData
.
value
};
}
if
(
key
===
"radarChart"
)
{
return
{
type
:
"雷达图"
,
name
:
"实体清单领域分布情况"
,
data
:
radarChartData
.
value
};
}
if
(
key
===
"entityListReleaseFreqChart"
)
{
return
{
type
:
"柱状图"
,
name
:
"美国商务部发布实体清单的频次"
,
data
:
entityListReleaseFreqChartData
.
value
};
}
if
(
key
===
"commerceControlListReleaseFreqChart"
)
{
return
{
type
:
"柱状图"
,
name
:
"美国商务部发布商业管制清单的频次"
,
data
:
commerceControlListReleaseFreqChartData
.
value
};
}
return
{
type
:
""
,
name
:
""
,
data
:
[]
};
};
const
handleShowAiPane
=
key
=>
{
aiPaneVisible
.
value
=
{
...
aiPaneVisible
.
value
,
[
key
]:
true
};
requestAiPaneContent
(
key
);
};
const
handleHideAiPane
=
key
=>
{
aiPaneVisible
.
value
=
{
...
aiPaneVisible
.
value
,
[
key
]:
false
};
};
watch
(
()
=>
[
trendChecked
.
value
,
selectedEntityId
.
value
],
()
=>
{
...
...
@@ -965,6 +1138,7 @@ const processYearDomainCountData = yearDomainCountData => {
// 提取所有领域名称
const
allDomains
=
[...
new
Set
(
yearDomainCountData
.
flatMap
(
item
=>
item
.
domainCountInfo
.
map
(
domain
=>
domain
.
name
)))];
console
.
log
(
"不同领域的数据 =>"
,
allDomains
);
// 构造 getMultipleBarChart_m 所需的数据结构
const
chartData
=
{
...
...
@@ -991,7 +1165,7 @@ const processYearDomainCountData = yearDomainCountData => {
};
})
};
console
.
log
(
"不同领域的数据 chartData"
,
chartData
);
// 使用 getMultipleBarChart_m 生成图表配置
return
getMultipleBarChart_m
(
chartData
);
};
...
...
@@ -1218,7 +1392,8 @@ const fetchRadarData = async checked => {
}
};
});
radarChart
.
interpret
({
type
:
"雷达图"
,
name
:
"实体清单领域分布情况"
,
data
:
data
});
radarChartData
.
value
=
data
;
// radarChart.interpret({ type: "雷达图", name: "实体清单领域分布情况", data: data });
}
}
catch
(
error
)
{
console
.
error
(
"获取雷达图数据失败:"
,
error
);
...
...
@@ -1318,7 +1493,7 @@ const fetchSanctionList = async () => {
});
totalAll
.
value
=
res
.
totalElements
;
}
}
catch
(
error
)
{
}
}
catch
(
error
)
{}
};
const
handlePageChangeAll
=
val
=>
{
...
...
@@ -1612,7 +1787,7 @@ const handleGetHylyList = async () => {
hylymc
:
"全部分类"
};
categoryList
.
value
=
[
obj
,
...
categoryList
.
value
];
}
catch
(
error
)
{
}
}
catch
(
error
)
{}
};
const
chart1Data
=
ref
({
...
...
@@ -2218,7 +2393,6 @@ const handleMediaClick = item => {
}
.box3-content
{
// flex: 1;
.el-progress--line
{
width
:
82px
;
...
...
src/views/exportControl/v2.0SingleSanction/components/dataStatistics/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -26,7 +26,7 @@
</div>
</div>
</div>
<div
class=
"nav-item"
@
click=
"handleToDataLibrary2"
>
<div
class=
"nav-item"
@
click=
"handleToDataLibrary2"
>
<div
class=
"item-position"
></div>
<div
class=
"content"
>
<div
class=
"info"
>
...
...
@@ -54,107 +54,94 @@
</div>
</div>
</div>
-->
<EChart
:option=
"domainChartOption"
autoresize
:style=
"
{ height: '300px', padding: '0 20px' }"
@chart-click="handleToDataLibrary3" />
<EChart
:option=
"domainChartOption"
autoresize
:style=
"
{ height: '300px', padding: '0 20px' }"
@chart-click="handleToDataLibrary3"
/>
<div
class=
"data-origin-box"
>
<div
class=
"data-origin-icon"
>
<img
:src=
"tipsIcon"
alt=
""
/>
</div>
<div
class=
"data-origin-text"
>
进入本次实体清单的中国实体领域分布情况,
数据来源:美国商务部官网
</div>
<div
class=
"data-origin-text"
>
数据来源:美国商务部官网
</div>
</div>
<div
class=
"ai-pane"
>
<AiButton
/>
<AiPane
:aiContent=
"domainChart.interpretation"
/>
<!--
<AiButton
/>
<AiPane
:aiContent=
"domainChart.interpretation"
/>
-->
<AiButton
@
mouseenter=
"handleShowAiPane('domainChart')"
/>
<AiPane
v-if=
"aiPaneVisible?.domainChart"
:aiContent=
"overviewAiContent.domainChart"
@
mouseleave=
"handleHideAiPane('domainChart')"
/>
</div>
</AnalysisBox>
</div>
<div
class=
"main-item"
>
<!--
<div
class=
"title-com"
>
<div
class=
"box"
></div>
<div
class=
"text"
>
制裁实体类型分布情况
</div>
<div
class=
"right-group"
>
<div
class=
"btn"
>
<img
src=
"../../assets/数据库按钮.png"
alt=
""
/>
<img
src=
"../../assets/下载按钮.png"
alt=
""
/>
<img
src=
"../../assets/收藏按钮.png"
alt=
""
/>
</div>
</div>
</div>
<div
class=
"echarts"
ref=
"typeChartRef"
></div>
<div
class=
"bottom"
>
<div
class=
"ai"
>
<div
class=
"left"
>
<img
:src=
"ai"
alt=
""
class=
"icon1"
/>
<div
class=
"text"
>
我国被制裁实体以企业、科研院所和高校为主。
</div>
</div>
<div
class=
"right"
>
<img
:src=
"right"
alt=
""
class=
"icon2"
/>
</div>
</div>
</div>
-->
<AnalysisBox
title=
"制裁实体类型分布情况"
>
<!--
<div
class=
"echarts"
ref=
"typeChartRef"
></div>
<div
class=
"bottom"
>
<div
class=
"ai"
>
<div
class=
"left"
>
<img
:src=
"ai"
alt=
""
class=
"icon1"
/>
<div
class=
"text"
>
我国被制裁实体以企业、科研院所和高校为主。
</div>
</div>
<div
class=
"right"
>
<img
:src=
"right"
alt=
""
class=
"icon2"
/>
</div>
</div>
</div>
-->
<EChart
:option=
"typeChartOption"
autoresize
:style=
"
{ height: '300px', padding: '0 20px' }"
@chart-click="handleToDataLibrary4" />
<EChart
:option=
"typeChartOption"
autoresize
:style=
"
{ height: '300px', padding: '0 20px' }"
@chart-click="handleToDataLibrary4"
/>
<div
class=
"data-origin-box"
>
<div
class=
"data-origin-icon"
>
<img
:src=
"tipsIcon"
alt=
""
/>
</div>
<div
class=
"data-origin-text"
>
进入本次实体清单的中国实体类型分布情况,
数据来源:美国商务部官网
</div>
<div
class=
"data-origin-text"
>
数据来源:美国商务部官网
</div>
</div>
<div
class=
"ai-pane"
>
<AiButton
/>
<AiPane
:aiContent=
"typeChart.interpretation"
/>
<!--
<AiButton
/>
<AiPane
:aiContent=
"typeChart.interpretation"
/>
-->
<AiButton
@
mouseenter=
"handleShowAiPane('typeChart')"
/>
<AiPane
v-if=
"aiPaneVisible?.typeChart"
:aiContent=
"overviewAiContent.typeChart"
@
mouseleave=
"handleHideAiPane('typeChart')"
/>
</div>
</AnalysisBox>
</div>
<div
class=
"main-item"
>
<AnalysisBox
title=
"制裁实体国家地区分布情况"
>
<div
class=
"country-list"
>
<div
class=
"list-item"
v-for=
"(item, index) in countryDistribution"
:key=
"index"
@
click=
"handleToDataLibrary5(item)"
>
<div
class=
"list-item"
v-for=
"(item, index) in countryDistribution"
:key=
"index"
@
click=
"handleToDataLibrary5(item)"
>
<img
:src=
"flag"
alt=
""
class=
"flag"
/>
<div
class=
"country-name"
>
{{
item
.
name
}}
</div>
<div
class=
"progress-bar-container"
>
<div
class=
"progress-bar"
:style=
"
{
width: item.width,
background: item.gradient
}">
</div>
<div
class=
"progress-bar"
:style=
"
{
width: item.width,
background: item.gradient
}"
>
</div>
</div>
<div
class=
"count"
:class=
"
{ highlight: index === 0 }">
{{
item
.
count
}}
家
</div>
</div>
</div>
<!--
<div
class=
"bottom"
>
<div
class=
"ai"
>
<div
class=
"left"
>
<img
:src=
"ai"
alt=
""
class=
"icon1"
/>
<div
class=
"text"
>
美国对中国的制裁近年来呈现显著增长趋势。
</div>
</div>
<div
class=
"right"
>
<img
:src=
"right"
alt=
""
class=
"icon2"
/>
</div>
</div>
</div>
-->
<div
class=
"data-origin-box"
>
<div
class=
"data-origin-icon"
>
<img
:src=
"tipsIcon"
alt=
""
/>
</div>
<div
class=
"data-origin-text"
>
进入本次实体清单的实体国家地区分布情况,
数据来源:美国商务部官网
</div>
<div
class=
"data-origin-text"
>
数据来源:美国商务部官网
</div>
</div>
<div
class=
"ai-pane"
>
<AiButton
/>
<AiPane
:aiContent=
"countryDistributionChart.interpretation"
/>
<!--
<AiButton
/>
<AiPane
:aiContent=
"countryDistributionChart.interpretation"
/>
-->
<AiButton
@
mouseenter=
"handleShowAiPane('countryDistributionChart')"
/>
<AiPane
v-if=
"aiPaneVisible?.countryDistributionChart"
:aiContent=
"overviewAiContent.countryDistributionChart"
@
mouseleave=
"handleHideAiPane('countryDistributionChart')"
/>
</div>
</AnalysisBox>
</div>
...
...
@@ -163,40 +150,42 @@
<div
class=
"map-wrapper"
>
<div
class=
"map-chart"
ref=
"mapChartRef"
></div>
<div
class=
"rank-list"
>
<div
class=
"rank-item"
v-for=
"(item, index) in regionDistribution"
:key=
"index"
@
click=
"handleToDataLibrary6(item)"
>
<div
class=
"rank-item"
v-for=
"(item, index) in regionDistribution"
:key=
"index"
@
click=
"handleToDataLibrary6(item)"
>
<div
class=
"rank-index"
:class=
"'rank-' + (index + 1)"
>
{{
index
+
1
}}
</div>
<div
class=
"rank-name"
>
{{
item
.
name
}}
</div>
<div
class=
"rank-bar-bg"
>
<div
class=
"rank-bar-fill"
:style=
"
{
width: (maxRegionCount > 0 ? (item.count / maxRegionCount) * 100 : 0) + '%',
background: getBarColor(index)
}">
</div>
<div
class=
"rank-bar-fill"
:style=
"
{
width: (maxRegionCount > 0 ? (item.count / maxRegionCount) * 100 : 0) + '%',
background: getBarColor(index)
}"
>
</div>
</div>
<div
class=
"rank-value"
>
{{
item
.
count
}}
家
</div>
</div>
</div>
</div>
<!--
<div
class=
"bottom"
>
<div
class=
"ai"
>
<div
class=
"left"
>
<img
:src=
"ai"
alt=
""
class=
"icon1"
/>
<div
class=
"text"
>
我国被制裁实体多分布于沿海经济活跃省份。
</div>
</div>
<div
class=
"right"
>
<img
:src=
"right"
alt=
""
class=
"icon2"
/>
</div>
</div>
</div>
-->
<div
class=
"data-origin-box"
>
<div
class=
"data-origin-icon"
>
<img
:src=
"tipsIcon"
alt=
""
/>
</div>
<div
class=
"data-origin-text"
>
进入本次实体清单的中国实体各省分布情况,
数据来源:美国商务部官网
</div>
<div
class=
"data-origin-text"
>
数据来源:美国商务部官网
</div>
</div>
<div
class=
"ai-pane"
>
<AiButton
/>
<AiPane
:aiContent=
"regionDistributionChart.interpretation"
/>
<!--
<AiButton
/>
<AiPane
:aiContent=
"regionDistributionChart.interpretation"
/>
-->
<AiButton
@
mouseenter=
"handleShowAiPane('regionDistributionChart')"
/>
<AiPane
v-if=
"aiPaneVisible?.regionDistributionChart"
:aiContent=
"overviewAiContent.regionDistributionChart"
@
mouseleave=
"handleHideAiPane('regionDistributionChart')"
/>
</div>
</AnalysisBox>
</div>
...
...
@@ -223,9 +212,9 @@ import {
getSingleSanctionEntityCountryCount
,
getSingleSanctionEntityRegionCount
}
from
"@/api/exportControlV2.0"
;
import
{
getChartAnalysis
}
from
"@/api/aiAnalysis/index"
;
import
{
useChartInterpretation
}
from
"@/views/exportControl/utils/common"
;
const
sanctionCountChart
=
useChartInterpretation
();
const
domainChart
=
useChartInterpretation
();
const
typeChart
=
useChartInterpretation
();
const
countryDistributionChart
=
useChartInterpretation
();
...
...
@@ -248,8 +237,9 @@ const getRegionData = async () => {
if
(
res
.
code
===
200
)
{
regionDistribution
.
value
=
res
.
data
||
[];
maxRegionCount
.
value
=
Math
.
max
(...
regionDistribution
.
value
.
map
(
item
=>
item
.
count
),
0
);
regionDistributionChartData
.
value
=
res
.
data
||
[];
initMapChart
();
regionDistributionChart
.
interpret
({
type
:
"柱状图"
,
name
:
"进入本次实体清单的中国实体各省分布情况"
,
data
:
res
.
data
});
//
regionDistributionChart.interpret({ type: "柱状图", name: "进入本次实体清单的中国实体各省分布情况", data: res.data });
}
}
catch
(
error
)
{
console
.
log
(
error
);
...
...
@@ -289,11 +279,12 @@ const getCountryCount = async () => {
gradient
};
});
countryDistributionChart
.
interpret
({
type
:
"柱状图"
,
name
:
"进入本次实体清单的实体国家地区分布情况"
,
data
:
res
.
data
});
countryDistributionChartData
.
value
=
res
.
data
||
[];
// countryDistributionChart.interpret({
// type: "柱状图",
// name: "进入本次实体清单的实体国家地区分布情况",
// data: res.data
// });
}
}
catch
(
error
)
{
console
.
log
(
error
);
...
...
@@ -314,7 +305,7 @@ const getEntityTypeCount = async () => {
const
res
=
await
getSingleSanctionEntityTypeCount
(
params
);
if
(
res
.
code
===
200
)
{
entityTypeCount
.
value
=
res
.
data
||
[];
typeChart
.
interpret
({
type
:
"饼图"
,
name
:
"进入本次实体清单的中国实体类型分布情况"
,
data
:
entityTypeCount
.
value
});
//
typeChart.interpret({ type: "饼图", name: "进入本次实体清单的中国实体类型分布情况", data: entityTypeCount.value });
initTypeChart
();
}
}
catch
(
error
)
{
...
...
@@ -337,7 +328,7 @@ const getDomainCount = async () => {
if
(
res
.
code
===
200
)
{
domainCount
.
value
=
res
.
data
||
[];
initDomainChart
();
domainChart
.
interpret
({
type
:
"饼图"
,
name
:
"进入本次实体清单的中国实体领域分布情况"
,
data
:
domainCount
.
value
});
//
domainChart.interpret({ type: "饼图", name: "进入本次实体清单的中国实体领域分布情况", data: domainCount.value });
}
}
catch
(
error
)
{
console
.
log
(
error
);
...
...
@@ -351,7 +342,7 @@ const getTotalCount = async () => {
if
(
!
sanRecordId
.
value
)
return
;
try
{
const
res
=
await
getSingleSanctionTotalCount
(
route
.
query
.
sanTypeId
,
sanRecordId
.
value
);
console
.
log
(
'统计'
,
res
);
console
.
log
(
"统计"
,
res
);
if
(
res
.
code
===
200
)
{
totalCount
.
value
=
res
.
data
||
{};
...
...
@@ -389,14 +380,6 @@ for (let i = 2025; i >= 2000; i--) {
timeOptions
.
push
({
label
:
`
${
i
}
年`
,
value
:
`
${
i
}
`
});
}
// const countryDistribution = [
// { name: "中国", count: 24, width: "80%", gradient: "linear-gradient(90deg, rgba(205, 66, 70, 0) 0%, rgba(205, 66, 70, 1) 100%)" },
// { name: "沙特阿拉伯", count: 2, width: "60%", gradient: "linear-gradient(90deg, rgba(255, 172, 77, 0) 0%, rgba(255, 172, 77, 1) 100%)" },
// { name: "伊朗", count: 2, width: "60%", gradient: "linear-gradient(90deg, rgba(255, 172, 77, 0) 0%, rgba(255, 172, 77, 1) 100%)" },
// { name: "俄罗斯", count: 2, width: "55%", gradient: "linear-gradient(90deg, rgba(255, 172, 77, 0) 0%, rgba(255, 172, 77, 1) 100%)" },
// { name: "中国香港", count: 1, width: "40%", gradient: "linear-gradient(90deg, rgba(5, 95, 194, 0) 0%, rgba(5, 95, 194, 1) 100%)" }
// ];
const
mapChartRef
=
ref
(
null
);
const
domainChartRef
=
ref
(
null
);
const
typeChartRef
=
ref
(
null
);
...
...
@@ -474,13 +457,13 @@ const initMapChart = () => {
chart
.
setOption
(
option
);
chart
.
on
(
'click'
,
function
(
params
)
{
chart
.
on
(
"click"
,
function
(
params
)
{
const
param
=
{
selectedProvince
:
params
.
name
,
selectedDate
:
JSON
.
stringify
([
route
.
query
.
date
,
route
.
query
.
date
])
}
}
;
const
curRoute
=
router
.
resolve
({
path
:
'/dataLibrary/dataEntityList'
,
path
:
"/dataLibrary/dataEntityList"
,
query
:
param
});
window
.
open
(
curRoute
.
href
,
"_blank"
);
...
...
@@ -855,67 +838,67 @@ const initTypeChart = () => {
const
sanTypeId
=
ref
(
""
);
// 制裁实体领域分布情况
const
handleToDataLibrary3
=
(
val
)
=>
{
const
handleToDataLibrary3
=
val
=>
{
// console.log('val', val);
const
params
=
{
domains
:
val
.
name
,
isCnEntityOnly
:
true
,
selectedDate
:
JSON
.
stringify
([
route
.
query
.
date
,
route
.
query
.
date
])
}
}
;
const
curRoute
=
router
.
resolve
({
path
:
'/dataLibrary/dataEntityList'
,
path
:
"/dataLibrary/dataEntityList"
,
query
:
params
});
window
.
open
(
curRoute
.
href
,
"_blank"
);
}
}
;
// 制裁实体类型分布情况
const
handleToDataLibrary4
=
(
val
)
=>
{
const
handleToDataLibrary4
=
val
=>
{
// console.log('val', val);
const
params
=
{
selectedEntityType
:
val
.
name
,
isCnEntityOnly
:
true
,
selectedDate
:
JSON
.
stringify
([
route
.
query
.
date
,
route
.
query
.
date
])
}
}
;
const
curRoute
=
router
.
resolve
({
path
:
'/dataLibrary/dataEntityList'
,
path
:
"/dataLibrary/dataEntityList"
,
query
:
params
});
window
.
open
(
curRoute
.
href
,
"_blank"
);
}
}
;
// 制裁实体国家地区分布情况
const
handleToDataLibrary5
=
(
item
)
=>
{
const
handleToDataLibrary5
=
item
=>
{
const
params
=
{
selectedCountryId
:
item
.
id
,
isCnEntityOnly
:
true
,
selectedDate
:
JSON
.
stringify
([
route
.
query
.
date
,
route
.
query
.
date
])
}
}
;
const
curRoute
=
router
.
resolve
({
path
:
'/dataLibrary/dataEntityList'
,
path
:
"/dataLibrary/dataEntityList"
,
query
:
params
});
window
.
open
(
curRoute
.
href
,
"_blank"
);
}
}
;
// 制裁实体各省分布情况
const
handleToDataLibrary6
=
(
item
)
=>
{
console
.
log
(
'item'
,
item
);
const
handleToDataLibrary6
=
item
=>
{
console
.
log
(
"item"
,
item
);
const
params
=
{
selectedProvince
:
item
.
name
,
isCnEntityOnly
:
true
,
selectedDate
:
JSON
.
stringify
([
route
.
query
.
date
,
route
.
query
.
date
])
}
}
;
const
curRoute
=
router
.
resolve
({
path
:
'/dataLibrary/dataEntityList'
,
path
:
"/dataLibrary/dataEntityList"
,
query
:
params
});
window
.
open
(
curRoute
.
href
,
"_blank"
);
}
}
;
// 跳转到数据资源库
const
handleToDataLibrary
=
()
=>
{
const
dateStr
=
route
.
query
.
date
?
route
.
query
.
date
:
''
const
dateStr
=
route
.
query
.
date
?
route
.
query
.
date
:
""
;
const
curRoute
=
router
.
resolve
({
path
:
"/dataLibrary/dataEntityList"
,
query
:
{
...
...
@@ -924,10 +907,10 @@ const handleToDataLibrary = () => {
}
});
window
.
open
(
curRoute
.
href
,
"_blank"
);
}
}
;
const
handleToDataLibrary1
=
()
=>
{
const
dateStr
=
route
.
query
.
date
?
route
.
query
.
date
:
''
const
dateStr
=
route
.
query
.
date
?
route
.
query
.
date
:
""
;
const
curRoute
=
router
.
resolve
({
path
:
"/dataLibrary/dataEntityList"
,
query
:
{
...
...
@@ -937,10 +920,10 @@ const handleToDataLibrary1 = () => {
}
});
window
.
open
(
curRoute
.
href
,
"_blank"
);
}
}
;
const
handleToDataLibrary2
=
()
=>
{
const
dateStr
=
route
.
query
.
date
?
route
.
query
.
date
:
''
const
dateStr
=
route
.
query
.
date
?
route
.
query
.
date
:
""
;
const
curRoute
=
router
.
resolve
({
path
:
"/dataLibrary/dataEntityList"
,
query
:
{
...
...
@@ -949,8 +932,129 @@ const handleToDataLibrary2 = () => {
}
});
window
.
open
(
curRoute
.
href
,
"_blank"
);
}
};
const
requestAiPaneContent
=
async
key
=>
{
if
(
!
key
||
aiPaneLoading
.
value
[
key
]
||
aiPaneFetched
.
value
[
key
])
return
;
aiPaneLoading
.
value
=
{
...
aiPaneLoading
.
value
,
[
key
]:
true
};
overviewAiContent
.
value
=
{
...
overviewAiContent
.
value
,
[
key
]:
"智能总结生成中..."
};
try
{
const
payload
=
buildAiChartPayload
(
key
);
const
res
=
await
getChartAnalysis
(
{
text
:
JSON
.
stringify
(
payload
)
},
{
onChunk
:
chunk
=>
{
const
current
=
overviewAiContent
.
value
[
key
];
const
base
=
current
===
"智能总结生成中..."
?
""
:
current
;
overviewAiContent
.
value
=
{
...
overviewAiContent
.
value
,
[
key
]:
base
+
chunk
};
}
}
);
const
list
=
res
?.
data
;
const
first
=
Array
.
isArray
(
list
)
?
list
[
0
]
:
null
;
const
interpretation
=
first
?.
解读
||
first
?.[
"解读"
];
// 流式已渲染过内容,最终用解析出的解读覆盖(保证显示格式统一)
if
(
interpretation
)
{
overviewAiContent
.
value
=
{
...
overviewAiContent
.
value
,
[
key
]:
interpretation
};
}
aiPaneFetched
.
value
=
{
...
aiPaneFetched
.
value
,
[
key
]:
true
};
}
catch
(
error
)
{
console
.
error
(
"获取图表解读失败"
,
error
);
overviewAiContent
.
value
=
{
...
overviewAiContent
.
value
,
[
key
]:
"智能总结生成失败"
};
}
finally
{
aiPaneLoading
.
value
=
{
...
aiPaneLoading
.
value
,
[
key
]:
false
};
}
};
const
domainChartData
=
ref
([]);
const
typeChartData
=
ref
([]);
const
countryDistributionChartData
=
ref
([]);
const
regionDistributionChartData
=
ref
([]);
const
aiPaneVisible
=
ref
({
domainChart
:
false
,
typeChart
:
false
,
countryDistributionChart
:
false
,
regionDistributionChart
:
false
});
const
overviewAiContent
=
ref
({
domainChart
:
"智能总结生成中..."
,
typeChart
:
"智能总结生成中..."
,
countryDistributionChart
:
"智能总结生成中..."
,
regionDistributionChart
:
"智能总结生成中..."
});
const
aiPaneFetched
=
ref
({
domainChart
:
false
,
typeChart
:
false
,
countryDistributionChart
:
false
,
regionDistributionChart
:
false
});
const
aiPaneLoading
=
ref
({
domainChart
:
false
,
typeChart
:
false
,
countryDistributionChart
:
false
,
regionDistributionChart
:
false
});
const
chartLoading
=
ref
({
domainChart
:
false
,
typeChart
:
false
,
countryDistributionChart
:
false
,
regionDistributionChart
:
false
});
const
buildAiChartPayload
=
key
=>
{
if
(
key
===
"domainChart"
)
{
return
{
type
:
"饼图"
,
name
:
"进入本次实体清单的中国实体领域分布情况"
,
data
:
domainCount
.
value
};
}
if
(
key
===
"typeChart"
)
{
return
{
type
:
"饼图"
,
name
:
"进入本次实体清单的中国实体类型分布情况"
,
data
:
entityTypeCount
.
value
};
}
if
(
key
===
"countryDistributionChart"
)
{
return
{
type
:
"柱状图"
,
name
:
"进入本次实体清单的实体国家地区分布情况"
,
data
:
countryDistributionChartData
.
value
};
}
if
(
key
===
"regionDistributionChart"
)
{
return
{
type
:
"柱状图"
,
name
:
"进入本次实体清单的中国实体各省分布情况"
,
data
:
regionDistributionChartData
.
value
};
}
return
{
type
:
""
,
name
:
""
,
data
:
[]
};
};
const
handleShowAiPane
=
key
=>
{
aiPaneVisible
.
value
=
{
...
aiPaneVisible
.
value
,
[
key
]:
true
};
requestAiPaneContent
(
key
);
};
const
handleHideAiPane
=
key
=>
{
aiPaneVisible
.
value
=
{
...
aiPaneVisible
.
value
,
[
key
]:
false
};
};
onMounted
(()
=>
{
// 获取路由参数id
...
...
src/views/exportControl/v2.0SingleSanction/components/sanctionsOverview/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -8,7 +8,7 @@
<div
class=
"info-row"
>
<div
class=
"label"
>
发布机构:
</div>
<div
class=
"value link"
>
<img
:src=
"title"
alt=
""
class=
"icon"
/>
<img
:src=
"
formattedData.postOrgLogoUrl ||
title"
alt=
""
class=
"icon"
/>
<span
@
click=
"handleClickDp"
>
{{
formattedData
.
postOrgName
}}
>
</span>
</div>
</div>
...
...
@@ -45,15 +45,22 @@
<div
class=
"left-top-content"
>
<div
class=
"content-title"
>
制裁实体分布:
</div>
<div
class=
"distribution-list"
>
<div
class=
"list-item"
v-for=
"(item, index) in entityDistribution"
:key=
"index"
@
click=
"handleToDataLibrary(item)"
>
<div
class=
"list-item"
v-for=
"(item, index) in entityDistribution"
:key=
"index"
@
click=
"handleToDataLibrary(item)"
>
<img
:src=
"item.imageUrl || flag"
alt=
""
class=
"flag"
/>
<div
class=
"country-name"
>
{{
item
.
name
}}
</div>
<div
class=
"progress-bar-container"
>
<div
class=
"progress-bar"
:style=
"
{
width: item.width,
background: item.gradient
}">
</div>
<div
class=
"progress-bar"
:style=
"
{
width: item.width,
background: item.gradient
}"
>
</div>
</div>
<div
class=
"count"
:class=
"
{ highlight: index === 0 }">
{{
item
.
count
}}
家
</div>
</div>
...
...
@@ -96,13 +103,25 @@
</div>
<div
class=
"filter-right"
>
<el-checkbox
v-model=
"onlyChina"
label=
"只看中国实体"
/>
<el-select
v-model=
"filterField"
placeholder=
"选择领域"
style=
"width: 150px; margin: 0 12px 0 16px"
>
<el-select
v-model=
"filterField"
placeholder=
"选择领域"
style=
"width: 150px; margin: 0 12px 0 16px"
>
<!--
<el-option
label=
"全部领域"
value=
""
/>
-->
<el-option
v-for=
"item in domainOptions"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
/>
<el-option
v-for=
"item in domainOptions"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
/>
</el-select>
<el-input
v-model=
"searchKeyword"
placeholder=
"搜索实体"
<el-input
v-model=
"searchKeyword"
placeholder=
"搜索实体"
style=
"width: 150px; border: 1px solid rgba(170, 173, 177, 0.4); border-radius: 5px"
:suffix-icon=
"Search"
/>
:suffix-icon=
"Search"
/>
</div>
</div>
<div
class=
"stats-row"
>
...
...
@@ -117,14 +136,21 @@
<div
class=
"stats-info"
>
<div
class=
"stat-item"
>
<span
class=
"dot red"
></span>
<span
class=
"text"
>
新增
<span
class=
"num red"
>
{{
addCount
}}
</span>
家 (50%规则涉及
<span
class=
"num red"
>
{{
addRuleCount
}}
</span>
家)
</span>
<span
class=
"text"
>
新增
<span
class=
"num red"
>
{{
addCount
}}
</span>
家 (50%规则涉及
<span
class=
"num red"
>
{{
addRuleCount
}}
</span
>
家)
</span
>
</div>
<div
class=
"stat-item"
>
<span
class=
"dot green"
></span>
<span
class=
"text"
>
移除
<span
class=
"num green"
>
{{
removeCount
}}
</span>
家 (50%规则涉及
<span
class=
"num green"
>
{{
removeRuleCount
}}
</span>
家)
</span>
<span
class=
"text"
>
移除
<span
class=
"num green"
>
{{
removeCount
}}
</span>
家 (50%规则涉及
<span
class=
"num green"
>
{{
removeRuleCount
}}
</span
>
家)
</span
>
</div>
</div>
</div>
...
...
@@ -156,7 +182,11 @@
>
{{
item
}}
</span
>
-->
<div
class=
"domain-box"
>
<AreaTag
v-for=
"(domain, index) in scope.row.fields"
:key=
"index"
:tagName=
"domain"
/>
<AreaTag
v-for=
"(domain, index) in scope.row.fields"
:key=
"index"
:tagName=
"domain"
/>
</div>
</
template
>
</el-table-column>
...
...
@@ -165,8 +195,11 @@
<el-table-column
prop=
"revenue"
label=
"营收(亿元)"
width=
"110"
align=
"center"
/>
<el-table-column
label=
"50%规则子企业"
width=
"180"
align=
"center"
>
<
template
#
default=
"scope"
>
<span
v-if=
"scope.row.subsidiaryCount"
class=
"subsidiary-link"
@
click=
"handleSubsidiaryClick(scope.row)"
>
<span
v-if=
"scope.row.subsidiaryCount"
class=
"subsidiary-link"
@
click=
"handleSubsidiaryClick(scope.row)"
>
{{
scope
.
row
.
subsidiaryText
}}
<span
class=
"blue-text"
>
{{
scope
.
row
.
subsidiaryCount
}}
家 >
</span>
</span>
...
...
@@ -184,8 +217,12 @@
</div>
</div>
<!-- 50%规则子企业弹框 -->
<RuleSubsidiaryDialog
v-model=
"subsidiaryDialogVisible"
:company-name=
"currentSubsidiaryCompanyName"
:total-count=
"currentSubsidiaryCount"
:data-list=
"currentSubsidiaryList"
/>
<RuleSubsidiaryDialog
v-model=
"subsidiaryDialogVisible"
:company-name=
"currentSubsidiaryCompanyName"
:total-count=
"currentSubsidiaryCount"
:data-list=
"currentSubsidiaryList"
/>
</div>
</template>
...
...
@@ -310,8 +347,8 @@ const getSanctionOverviewList = async () => {
subsidiaryText
:
org
.
ruleOrgList
&&
org
.
ruleOrgList
.
length
>
0
?
(
org
.
ruleOrgList
[
0
].
orgName
.
length
>
10
?
org
.
ruleOrgList
[
0
].
orgName
.
slice
(
0
,
10
)
+
"..."
:
org
.
ruleOrgList
[
0
].
orgName
)
+
"...等"
?
org
.
ruleOrgList
[
0
].
orgName
.
slice
(
0
,
10
)
+
"..."
:
org
.
ruleOrgList
[
0
].
orgName
)
+
"...等"
:
""
}))
}));
...
...
@@ -445,7 +482,8 @@ const formattedData = computed(() => {
administrativeOrderId
:
info
.
administrativeOrderId
?
`No.
${
info
.
administrativeOrderId
}
`
:
""
,
postPersonName
:
info
.
postPersonName
,
domains
:
info
.
domainNames
,
avartar
:
info
.
postPersonAvatarUrl
avartar
:
info
.
postPersonAvatarUrl
,
postOrgLogoUrl
:
info
.
postOrgLogoUrl
};
});
...
...
@@ -525,23 +563,22 @@ const entityDistribution = ref([
const
sanTypeId
=
ref
(
""
);
// 跳转到数据资源库
const
handleToDataLibrary
=
(
item
)
=>
{
console
.
log
(
'item'
,
item
);
const
dateStr
=
formattedData
.
value
.
postDate
.
replace
(
/
(\d{4})
年
(\d{1,2})
月
(\d{1,2})
日/
,
(
_
,
y
,
m
,
d
)
=>
`
${
y
}
-
${
m
.
padStart
(
2
,
'0'
)}
-
${
d
.
padStart
(
2
,
'0'
)}
`
const
handleToDataLibrary
=
item
=>
{
console
.
log
(
"item"
,
item
);
const
dateStr
=
formattedData
.
value
.
postDate
.
replace
(
/
(\d{4})
年
(\d{1,2})
月
(\d{1,2})
日/
,
(
_
,
y
,
m
,
d
)
=>
`
${
y
}
-
${
m
.
padStart
(
2
,
"0"
)}
-
${
d
.
padStart
(
2
,
"0"
)}
`
);
const
route
=
router
.
resolve
({
const
route
=
router
.
resolve
({
path
:
"/dataLibrary/dataEntityList"
,
query
:{
selectedDate
:
JSON
.
stringify
([
dateStr
,
dateStr
]),
query
:
{
selectedDate
:
JSON
.
stringify
([
dateStr
,
dateStr
]),
selectedCountryId
:
item
.
id
}
});
window
.
open
(
route
.
href
,
"_blank"
);
}
};
onMounted
(()
=>
{
// 获取路由参数中的sanTypeId
...
...
src/views/exportControl/v2.0SingleSanction/originPage/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -51,7 +51,7 @@
object-fit: contain;
"
/>
</div>
<div
class=
"translate-text"
>
{{
"显示
原
文"
}}
</div>
<div
class=
"translate-text"
>
{{
"显示
译
文"
}}
</div>
</div>
<div
class=
"btn"
@
click=
"handleDownload"
>
<div
class=
"icon"
>
...
...
@@ -62,13 +62,13 @@
</div>
</div>
<div
class=
"report-box"
>
<div
class=
"pdf-pane-wrap"
v-if=
"valueSwitch && reportUrlEnWithPage"
>
<pdf
ref=
"leftPdfRef"
:pdfUrl=
"reportUrlEnWithPage"
class=
"pdf-pane-inner"
/>
</div>
<div
class=
"pdf-pane-wrap"
:class=
"
{ 'is-full': !valueSwitch }" v-if="reportUrlWithPage">
<pdf
:key=
"`right-pdf-$
{valueSwitch ? 'split' : 'full'}`" ref="rightPdfRef" :pdfUrl="reportUrlWithPage"
class="pdf-pane-inner" />
</div>
<div
class=
"pdf-pane-wrap"
v-if=
"valueSwitch && reportUrlEnWithPage"
>
<pdf
ref=
"leftPdfRef"
:pdfUrl=
"reportUrlEnWithPage"
class=
"pdf-pane-inner"
/>
</div>
</div>
</div>
</div>
...
...
src/views/finance/components/title.vue
浏览文件 @
e4e9a0d4
...
...
@@ -43,8 +43,8 @@ defineProps({
.title-text
{
color
:
rgba
(
10
,
18
,
30
,
1
);
font-size
:
32px
;
font-family
:
$base-font-family
;
font-weight
:
bold
;
font-family
:
"Microsoft YaHei"
;
font-weight
:
700
;
margin-left
:
20px
;
white-space
:
nowrap
;
}
...
...
src/views/finance/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -234,13 +234,17 @@
<div
class=
"data-origin-icon"
>
<img
:src=
"tipsIcon"
alt=
""
/>
</div>
<div
class=
"data-origin-text"
>
美国商务部发布实体清单的频次,数据来源:美国财政部海外资产管理办公室官网
</div>
<div
class=
"data-origin-text"
>
数据来源:美国财政部海外资产管理办公室官网
</div>
</div>
<div
class=
"ai-pane"
>
<AiButton
/>
<AiPane
:aiContent=
"entityListReleaseFreqChart.interpretation"
/>
<!-- <AiButton />
<AiPane :aiContent="entityListReleaseFreqChart.interpretation" /> -->
<AiButton
@
mouseenter=
"handleShowAiPane('entityListReleaseFreqChart')"
/>
<AiPane
v-if=
"aiPaneVisible?.entityListReleaseFreqChart"
:aiContent=
"overviewAiContent.entityListReleaseFreqChart"
@
mouseleave=
"handleHideAiPane('entityListReleaseFreqChart')"
/>
</div>
</div>
<div
class=
"box3-content"
>
...
...
@@ -280,13 +284,17 @@
<div
class=
"data-origin-icon"
>
<img
:src=
"tipsIcon"
alt=
""
/>
</div>
<div
class=
"data-origin-text"
>
美国商务部发布商业管制清单的频次,数据来源:美国财政部海外资产管理办公室官网
</div>
<div
class=
"data-origin-text"
>
数据来源:美国财政部海外资产管理办公室官网
</div>
</div>
<div
class=
"ai-pane"
>
<AiButton
/>
<AiPane
:aiContent=
"commerceControlListReleaseFreqChart.interpretation"
/>
<!-- <AiButton />
<AiPane :aiContent="commerceControlListReleaseFreqChart.interpretation" /> -->
<AiButton
@
mouseenter=
"handleShowAiPane('commerceControlListReleaseFreqChart')"
/>
<AiPane
v-if=
"aiPaneVisible?.commerceControlListReleaseFreqChart"
:aiContent=
"overviewAiContent.commerceControlListReleaseFreqChart"
@
mouseleave=
"handleHideAiPane('commerceControlListReleaseFreqChart')"
/>
</div>
</div>
<div
class=
"box3-content"
style=
"display: none"
>
...
...
@@ -339,13 +347,13 @@
<div
class=
"data-origin-icon"
>
<img
:src=
"tipsIcon"
alt=
""
/>
</div>
<div
class=
"data-origin-text"
>
进入SDN清单的中国实体领域分布情况,数据来源:美国财政部海外资产管理办公室官网
</div>
<div
class=
"data-origin-text"
>
数据来源:美国财政部海外资产管理办公室官网
</div>
</div>
<div
class=
"ai-pane"
>
<AiButton
/>
<AiPane
:aiContent=
"radarChart.interpretation"
/>
<!--
<AiButton
/>
<AiPane
:aiContent=
"radarChart.interpretation"
/>
-->
<AiButton
@
mouseenter=
"handleShowAiPane('radarChart')"
/>
<AiPane
:aiContent=
"overviewAiContent.radarChart"
@
mouseleave=
"handleHideAiPane('radarChart')"
/>
</div>
</
template
>
</custom-container>
...
...
@@ -371,13 +379,17 @@
<div
class=
"data-origin-icon"
>
<img
:src=
"tipsIcon"
alt=
""
/>
</div>
<div
class=
"data-origin-text"
>
进入SDN清单的中国实体数量变化趋势,数据来源:美国财政部海外资产管理办公室官网
</div>
<div
class=
"data-origin-text"
>
数据来源:美国财政部海外资产管理办公室官网
</div>
</div>
<div
class=
"ai-pane"
>
<AiButton
/>
<AiPane
:aiContent=
"trendChart.interpretation"
/>
<!--
<AiButton
/>
<AiPane
:aiContent=
"trendChart.interpretation"
/>
-->
<AiButton
@
mouseenter=
"handleShowAiPane('trendChart')"
/>
<AiPane
v-if=
"aiPaneVisible?.trendChart"
:aiContent=
"overviewAiContent.trendChart"
@
mouseleave=
"handleHideAiPane('trendChart')"
/>
</div>
</
template
>
</custom-container>
...
...
@@ -705,6 +717,7 @@ import RiskSignal from "@/components/base/riskSignal/index.vue";
import
RiskSignalOverviewDetailDialog
from
"@/components/base/RiskSignalOverviewDetailDialog/index.vue"
;
import
{
onMounted
,
ref
,
computed
,
reactive
,
shallowRef
,
watch
,
nextTick
}
from
"vue"
;
import
{
useContainerScroll
}
from
"@/hooks/useScrollShow"
;
import
{
getChartAnalysis
}
from
"@/api/aiAnalysis/index"
;
const
homeMainRef
=
ref
(
null
);
const
{
isShow
}
=
useContainerScroll
(
homeMainRef
);
import
*
as
echarts
from
"echarts"
;
...
...
@@ -1265,7 +1278,8 @@ const fetchRadarData = async checked => {
};
});
console
.
log
(
"图例 =>"
,
radarOption
.
value
);
radarChart
.
interpret
({
type
:
"雷达图"
,
name
:
"实体清单领域分布情况"
,
data
:
data
});
radarChartData
.
value
=
data
;
// radarChart.interpret({ type: "雷达图", name: "实体清单领域分布情况", data: data });
}
}
catch
(
error
)
{
console
.
error
(
"获取雷达图数据失败:"
,
error
);
...
...
@@ -1824,6 +1838,128 @@ const handleToDataLibrary = item => {
window
.
open
(
route
.
href
,
"_blank"
);
};
const
requestAiPaneContent
=
async
key
=>
{
if
(
!
key
||
aiPaneLoading
.
value
[
key
]
||
aiPaneFetched
.
value
[
key
])
return
;
aiPaneLoading
.
value
=
{
...
aiPaneLoading
.
value
,
[
key
]:
true
};
overviewAiContent
.
value
=
{
...
overviewAiContent
.
value
,
[
key
]:
"智能总结生成中..."
};
try
{
const
payload
=
buildAiChartPayload
(
key
);
const
res
=
await
getChartAnalysis
(
{
text
:
JSON
.
stringify
(
payload
)
},
{
onChunk
:
chunk
=>
{
const
current
=
overviewAiContent
.
value
[
key
];
const
base
=
current
===
"智能总结生成中..."
?
""
:
current
;
overviewAiContent
.
value
=
{
...
overviewAiContent
.
value
,
[
key
]:
base
+
chunk
};
}
}
);
const
list
=
res
?.
data
;
const
first
=
Array
.
isArray
(
list
)
?
list
[
0
]
:
null
;
const
interpretation
=
first
?.
解读
||
first
?.[
"解读"
];
// 流式已渲染过内容,最终用解析出的解读覆盖(保证显示格式统一)
if
(
interpretation
)
{
overviewAiContent
.
value
=
{
...
overviewAiContent
.
value
,
[
key
]:
interpretation
};
}
aiPaneFetched
.
value
=
{
...
aiPaneFetched
.
value
,
[
key
]:
true
};
}
catch
(
error
)
{
console
.
error
(
"获取图表解读失败"
,
error
);
overviewAiContent
.
value
=
{
...
overviewAiContent
.
value
,
[
key
]:
"智能总结生成失败"
};
}
finally
{
aiPaneLoading
.
value
=
{
...
aiPaneLoading
.
value
,
[
key
]:
false
};
}
};
const
trendChartData
=
ref
([]);
const
radarChartData
=
ref
([]);
const
entityListReleaseFreqChartData
=
ref
([]);
const
commerceControlListReleaseFreqChartData
=
ref
([]);
const
aiPaneVisible
=
ref
({
trendChart
:
false
,
radarChart
:
false
,
entityListReleaseFreqChart
:
false
,
commerceControlListReleaseFreqChart
:
false
});
const
overviewAiContent
=
ref
({
trendChart
:
"智能总结生成中..."
,
radarChart
:
"智能总结生成中..."
,
entityListReleaseFreqChart
:
"智能总结生成中..."
,
commerceControlListReleaseFreqChart
:
"智能总结生成中..."
});
const
aiPaneFetched
=
ref
({
trendChart
:
false
,
radarChart
:
false
,
entityListReleaseFreqChart
:
false
,
commerceControlListReleaseFreqChart
:
false
});
const
aiPaneLoading
=
ref
({
trendChart
:
false
,
radarChart
:
false
,
entityListReleaseFreqChart
:
false
,
commerceControlListReleaseFreqChart
:
false
});
const
chartLoading
=
ref
({
trendChart
:
false
,
radarChart
:
false
,
entityListReleaseFreqChart
:
false
,
commerceControlListReleaseFreqChart
:
false
});
const
buildAiChartPayload
=
key
=>
{
if
(
key
===
"trendChart"
)
{
return
{
type
:
"柱状图"
,
name
:
"制裁清单数量增长趋势"
,
data
:
trendChartData
.
value
};
}
if
(
key
===
"radarChart"
)
{
return
{
type
:
"雷达图"
,
name
:
"实体清单领域分布情况"
,
data
:
radarChartData
.
value
};
}
if
(
key
===
"entityListReleaseFreqChart"
)
{
return
{
type
:
"柱状图"
,
name
:
"美国商务部发布实体清单的频次"
,
data
:
entityListReleaseFreqChartData
.
value
};
}
if
(
key
===
"commerceControlListReleaseFreqChart"
)
{
return
{
type
:
"柱状图"
,
name
:
"美国商务部发布商业管制清单的频次"
,
data
:
commerceControlListReleaseFreqChartData
.
value
};
}
return
{
type
:
""
,
name
:
""
,
data
:
[]
};
};
const
handleShowAiPane
=
key
=>
{
aiPaneVisible
.
value
=
{
...
aiPaneVisible
.
value
,
[
key
]:
true
};
requestAiPaneContent
(
key
);
};
const
handleHideAiPane
=
key
=>
{
aiPaneVisible
.
value
=
{
...
aiPaneVisible
.
value
,
[
key
]:
false
};
};
onMounted
(
async
()
=>
{
console
.
log
(
"finance 页面 mounted"
);
try
{
...
...
@@ -1886,11 +2022,12 @@ onMounted(async () => {
await
fetchRadarData
(
domainChecked
.
value
);
// 获取出口管制制裁措施
await
fetchSanctionList
();
entityListReleaseFreqChart
.
interpret
({
type
:
"柱状图"
,
name
:
"美国商务部发布实体清单的频次"
,
data
:
entityListReleaseFreq
.
value
});
entityListReleaseFreqChartData
.
value
=
entityListReleaseFreq
.
value
;
// entityListReleaseFreqChart.interpret({
// type: "柱状图",
// name: "美国商务部发布实体清单的频次",
// data: entityListReleaseFreq.value
// });
commerceControlListReleaseFreq
.
value
=
_
.
map
(
cclList1
,
item
=>
{
return
{
year
:
item
.
year
,
...
...
@@ -1899,11 +2036,12 @@ onMounted(async () => {
tags
:
item
.
domain
};
});
commerceControlListReleaseFreqChart
.
interpret
({
type
:
"柱状图"
,
name
:
"美国商务部发布商业管制清单的频次"
,
data
:
commerceControlListReleaseFreq
.
value
});
commerceControlListReleaseFreqChartData
.
value
=
commerceControlListReleaseFreq
.
value
;
// commerceControlListReleaseFreqChart.interpret({
// type: "柱状图",
// name: "美国商务部发布商业管制清单的频次",
// data: commerceControlListReleaseFreq.value
// });
}
catch
(
err
)
{
console
.
log
(
"此处报错?"
);
console
.
log
(
err
);
...
...
src/views/finance/singleSanction/components/sanctionsOverview/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -8,7 +8,7 @@
<div
class=
"info-row"
>
<div
class=
"label"
>
发布机构:
</div>
<div
class=
"value link"
>
<img
:src=
"title"
alt=
""
class=
"icon"
/>
<img
:src=
"
formattedData.postOrgLogoUrl ||
title"
alt=
""
class=
"icon"
/>
<span
@
click=
"handleClickDp"
>
{{
formattedData
.
postOrgName
}}
>
</span>
</div>
</div>
...
...
@@ -46,17 +46,24 @@
<div
class=
"left-top-content"
>
<div
class=
"content-title"
>
制裁实体分布:
</div>
<div
class=
"distribution-list"
>
<div
class=
"list-item"
v-for=
"(item, index) in entityDistribution"
:key=
"index"
@
click=
"handleToDataLibrary(item)"
>
<div
class=
"list-item"
v-for=
"(item, index) in entityDistribution"
:key=
"index"
@
click=
"handleToDataLibrary(item)"
>
<img
:src=
"item.imageUrl || flag"
alt=
""
class=
"flag"
/>
<div
class=
"country-name"
>
{{
item
.
name
}}
</div>
<div
class=
"progress-bar-container"
>
<div
class=
"progress-bar"
:style=
"
{
width: item.width,
background: item.gradient
}">
</div>
<div
class=
"progress-bar"
:style=
"
{
width: item.width,
background: item.gradient
}"
>
</div>
</div>
<div
class=
"count"
:class=
"
{ highlight: i
ndex === 0
}">
{{
item
.
count
}}
家
</div>
<div
class=
"count"
:class=
"
{ highlight: i
tem.name === '中国'
}">
{{
item
.
count
}}
家
</div>
</div>
</div>
</div>
...
...
@@ -97,13 +104,25 @@
</div>
<div
class=
"filter-right"
>
<el-checkbox
v-model=
"onlyChina"
label=
"只看中国实体"
/>
<el-select
v-model=
"filterField"
placeholder=
"全部领域"
style=
"width: 150px; margin: 0 12px 0 16px"
>
<el-select
v-model=
"filterField"
placeholder=
"全部领域"
style=
"width: 150px; margin: 0 12px 0 16px"
>
<el-option
label=
"全部领域"
value=
""
/>
<el-option
v-for=
"item in domainOptions"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
/>
<el-option
v-for=
"item in domainOptions"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
/>
</el-select>
<el-input
v-model=
"searchKeyword"
placeholder=
"搜索实体"
<el-input
v-model=
"searchKeyword"
placeholder=
"搜索实体"
style=
"width: 150px; border: 1px solid rgba(170, 173, 177, 0.4); border-radius: 5px"
:suffix-icon=
"Search"
/>
:suffix-icon=
"Search"
/>
</div>
</div>
<div
class=
"stats-row"
>
...
...
@@ -118,14 +137,21 @@
<div
class=
"stats-info"
>
<div
class=
"stat-item"
>
<span
class=
"dot red"
></span>
<span
class=
"text"
>
新增
<span
class=
"num red"
>
{{
addCount
}}
</span>
家 (50%规则涉及
<span
class=
"num red"
>
{{
addRuleCount
}}
</span>
家)
</span>
<span
class=
"text"
>
新增
<span
class=
"num red"
>
{{
addCount
}}
</span>
家 (50%规则涉及
<span
class=
"num red"
>
{{
addRuleCount
}}
</span
>
家)
</span
>
</div>
<div
class=
"stat-item"
>
<span
class=
"dot green"
></span>
<span
class=
"text"
>
移除
<span
class=
"num green"
>
{{
removeCount
}}
</span>
家 (50%规则涉及
<span
class=
"num green"
>
{{
removeRuleCount
}}
</span>
家)
</span>
<span
class=
"text"
>
移除
<span
class=
"num green"
>
{{
removeCount
}}
</span>
家 (50%规则涉及
<span
class=
"num green"
>
{{
removeRuleCount
}}
</span
>
家)
</span
>
</div>
</div>
</div>
...
...
@@ -157,7 +183,11 @@
>
{{
item
}}
</span
>
-->
<div
class=
"domain-box"
>
<AreaTag
v-for=
"(domain, index) in scope.row.fields"
:key=
"index"
:tagName=
"domain"
/>
<AreaTag
v-for=
"(domain, index) in scope.row.fields"
:key=
"index"
:tagName=
"domain"
/>
</div>
</
template
>
</el-table-column>
...
...
@@ -166,20 +196,26 @@
<el-table-column
prop=
"entityTypeId"
label=
"类型"
width=
"120"
align=
"center"
>
<
template
#
default=
"scope"
>
<div
style=
"display: flex; gap: 4px; justify-content: center"
>
<AreaTag
:tagName=
"scope.row.entityType === 1
? '个人'
: scope.row.entityType === 2
? '实体'
: '公司'
"
/>
<AreaTag
:tagName=
"
scope.row.entityType === 1
? '个人'
: scope.row.entityType === 2
? '实体'
: '公司'
"
/>
</div>
</
template
>
</el-table-column>
<!-- <el-table-column prop="revenue" label="营收(亿元)" width="110" align="center" /> -->
<el-table-column
label=
"50%规则子企业"
width=
"180"
align=
"center"
>
<
template
#
default=
"scope"
>
<span
v-if=
"scope.row.subsidiaryCount"
class=
"subsidiary-link"
@
click=
"handleSubsidiaryClick(scope.row)"
>
<span
v-if=
"scope.row.subsidiaryCount"
class=
"subsidiary-link"
@
click=
"handleSubsidiaryClick(scope.row)"
>
{{
scope
.
row
.
subsidiaryText
}}
<span
class=
"blue-text"
>
{{
scope
.
row
.
subsidiaryCount
}}
家 >
</span>
</span>
...
...
@@ -223,8 +259,12 @@
</div>
</div>
<!-- 50%规则子企业弹框 -->
<RuleSubsidiaryDialog
v-model=
"subsidiaryDialogVisible"
:company-name=
"currentSubsidiaryCompanyName"
:total-count=
"currentSubsidiaryCount"
:data-list=
"currentSubsidiaryList"
/>
<RuleSubsidiaryDialog
v-model=
"subsidiaryDialogVisible"
:company-name=
"currentSubsidiaryCompanyName"
:total-count=
"currentSubsidiaryCount"
:data-list=
"currentSubsidiaryList"
/>
</div>
</template>
...
...
@@ -333,8 +373,8 @@ const getSanctionOverviewList = async () => {
subsidiaryText
:
org
.
ruleOrgList
&&
org
.
ruleOrgList
.
length
>
0
?
(
org
.
ruleOrgList
[
0
].
orgName
.
length
>
10
?
org
.
ruleOrgList
[
0
].
orgName
.
slice
(
0
,
10
)
+
"..."
:
org
.
ruleOrgList
[
0
].
orgName
)
+
"...等"
?
org
.
ruleOrgList
[
0
].
orgName
.
slice
(
0
,
10
)
+
"..."
:
org
.
ruleOrgList
[
0
].
orgName
)
+
"...等"
:
""
}))
}));
...
...
@@ -481,7 +521,8 @@ const formattedData = computed(() => {
administrativeOrderId
:
info
.
administrativeOrderId
?
`No.
${
info
.
administrativeOrderId
}
`
:
""
,
postPersonName
:
info
.
postPersonName
,
domains
:
info
.
domainNames
,
avartar
:
info
.
postPersonAvatarUrl
avartar
:
info
.
postPersonAvatarUrl
,
postOrgLogoUrl
:
info
.
postOrgLogoUrl
};
});
...
...
@@ -590,10 +631,11 @@ const getReasonHistoryList = async () => {
const
sanTypeId
=
ref
(
""
);
// 跳转到数据资源库
const
handleToDataLibrary
=
(
item
)
=>
{
console
.
log
(
'item'
,
item
);
const
dateStr
=
formattedData
.
value
.
postDate
.
replace
(
/
(\d{4})
年
(\d{1,2})
月
(\d{1,2})
日/
,
(
_
,
y
,
m
,
d
)
=>
`
${
y
}
-
${
m
.
padStart
(
2
,
'0'
)}
-
${
d
.
padStart
(
2
,
'0'
)}
`
const
handleToDataLibrary
=
item
=>
{
console
.
log
(
"item"
,
item
);
const
dateStr
=
formattedData
.
value
.
postDate
.
replace
(
/
(\d{4})
年
(\d{1,2})
月
(\d{1,2})
日/
,
(
_
,
y
,
m
,
d
)
=>
`
${
y
}
-
${
m
.
padStart
(
2
,
"0"
)}
-
${
d
.
padStart
(
2
,
"0"
)}
`
);
const
route
=
router
.
resolve
({
...
...
@@ -604,7 +646,7 @@ const handleToDataLibrary = (item) => {
}
});
window
.
open
(
route
.
href
,
"_blank"
);
}
}
;
onMounted
(()
=>
{
// 获取路由参数中的sanTypeId
...
...
src/views/finance/singleSanction/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -28,7 +28,7 @@
</div>
<div
class=
"original-text-btn"
@
click=
"handleClickOriginalText"
>
<img
:src=
"icon1"
alt=
""
/>
<span>
实体
清单原文
</span>
<span>
SDN
清单原文
</span>
</div>
<div
class=
"btn3"
@
click=
"handleAnalysisClick"
>
<div
class=
"icon"
>
...
...
src/views/finance/singleSanction/originPage/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -68,7 +68,7 @@
"
/>
</div>
<div
class=
"translate-text"
>
{{
"显示
原
文"
}}
</div>
<div
class=
"translate-text"
>
{{
"显示
译
文"
}}
</div>
</div>
<div
class=
"btn"
@
click=
"handleDownload"
>
<div
class=
"icon"
>
...
...
@@ -79,9 +79,6 @@
</div>
</div>
<div
class=
"report-box"
>
<div
class=
"pdf-pane-wrap"
v-if=
"valueSwitch && reportUrlEnWithPage"
>
<pdf
ref=
"leftPdfRef"
:pdfUrl=
"reportUrlEnWithPage"
class=
"pdf-pane-inner"
/>
</div>
<div
class=
"pdf-pane-wrap"
:class=
"
{ 'is-full': !valueSwitch }" v-if="reportUrlWithPage">
<pdf
:key=
"`right-pdf-$
{valueSwitch ? 'split' : 'full'}`"
...
...
@@ -90,6 +87,9 @@
class="pdf-pane-inner"
/>
</div>
<div
class=
"pdf-pane-wrap"
v-if=
"valueSwitch && reportUrlEnWithPage"
>
<pdf
ref=
"leftPdfRef"
:pdfUrl=
"reportUrlEnWithPage"
class=
"pdf-pane-inner"
/>
</div>
</div>
</div>
</div>
...
...
src/views/thinkTank/CongressHearingView/index.vue
浏览文件 @
e4e9a0d4
<
template
>
<div
class=
"wrap"
>
<div
class=
"scroll-inner"
>
<div
class=
"scroll-inner"
ref=
"pageScrollRef"
>
<div
class=
"header"
>
<div
class=
"header-top"
>
<div
class=
"header-top-left"
>
...
...
@@ -231,7 +231,7 @@ import DefaultIcon1 from '@/assets/icons/default-icon1.png'
import
WarningPane
from
"@/components/base/WarningPane/index.vue"
import
WordCloudChart
from
"@/components/base/WordCloundChart/index.vue"
import
SearchContainer
from
"@/components/SearchContainer.vue"
;
import
{
ref
,
onMounted
,
computed
,
defineProps
}
from
"vue"
;
import
{
ref
,
onMounted
,
computed
,
defineProps
,
nextTick
}
from
"vue"
;
import
{
ElMessage
}
from
"element-plus"
;
import
{
getThinkTankReportAbstract
,
...
...
@@ -304,7 +304,7 @@ const handleGetThinkTankHearingInfo = async () => {
}
};
const
REPORT_ANALYSIS_TIP_BOX5
=
"
国会听证会关键词云,
数据来源:美国兰德公司官网"
;
"数据来源:美国兰德公司官网"
;
// 默认仅展示 AiButton,悬停后再请求 AI
const
isShowAiContentBox5
=
ref
(
false
);
const
aiContentBox5
=
ref
(
""
);
...
...
@@ -592,8 +592,30 @@ const switchTab = name => {
const
currentPage
=
ref
(
1
);
const
pageSize
=
ref
(
10
);
const
total
=
ref
(
0
);
const
pageScrollRef
=
ref
(
null
);
const
getScrollableParent
=
(
el
)
=>
{
let
cur
=
el
;
while
(
cur
&&
cur
!==
document
.
body
&&
cur
!==
document
.
documentElement
)
{
const
style
=
window
.
getComputedStyle
(
cur
);
const
overflowY
=
style
?.
overflowY
;
const
isScrollable
=
overflowY
===
"auto"
||
overflowY
===
"scroll"
;
if
(
isScrollable
&&
cur
.
scrollHeight
>
cur
.
clientHeight
+
1
)
{
return
cur
;
}
cur
=
cur
.
parentElement
;
}
return
null
;
};
const
scrollToTop
=
async
()
=>
{
await
nextTick
();
const
anchor
=
pageScrollRef
.
value
;
if
(
!
anchor
)
return
;
const
scrollEl
=
getScrollableParent
(
anchor
)
||
anchor
;
scrollEl
.
scrollTop
=
0
;
};
const
handleCurrentChange
=
page
=>
{
currentPage
.
value
=
page
;
scrollToTop
();
handleGetThinkTankReportViewpoint
();
};
...
...
@@ -749,9 +771,6 @@ onMounted(() => {
border-bottom
:
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
);
position
:
sticky
;
top
:
0
;
z-index
:
99999
;
overflow
:
hidden
;
.header-top
{
...
...
src/views/thinkTank/ReportDetail/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -232,9 +232,6 @@ const handleDownloadDocument = async () => {
border-bottom
:
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
);
position
:
sticky
;
top
:
0
;
z-index
:
99999
;
overflow
:
hidden
;
.header-top
{
...
...
src/views/thinkTank/ReportDetail/reportAnalysis/index.vue
浏览文件 @
e4e9a0d4
<
template
>
<div
class=
"wrap"
>
<div
class=
"wrap"
ref=
"pageScrollRef"
>
<div
class=
"top"
>
<WarningPane
:warnningLevel=
"riskSignal?.level"
:warnningContent=
"riskSignal?.content"
v-if=
"riskSignal?.level"
>
</WarningPane>
...
...
@@ -217,7 +217,7 @@ import DefaultIcon1 from '@/assets/icons/default-icon1.png'
import
WarningPane
from
"@/components/base/WarningPane/index.vue"
import
WordCloudChart
from
"@/components/base/WordCloundChart/index.vue"
import
SearchContainer
from
"@/components/SearchContainer.vue"
;
import
{
ref
,
onMounted
,
computed
,
defineProps
}
from
"vue"
;
import
{
ref
,
onMounted
,
computed
,
defineProps
,
nextTick
}
from
"vue"
;
import
{
ElMessage
}
from
"element-plus"
;
import
{
getThinkTankReportAbstract
,
...
...
@@ -258,7 +258,7 @@ const props = defineProps({
}
});
const
REPORT_ANALYSIS_TIP_BOX5
=
"
智库报告关键词云,
数据来源:美国兰德公司官网"
;
"数据来源:美国兰德公司官网"
;
// 默认仅展示 AiButton,悬停后再请求 AI
const
isShowAiContentBox5
=
ref
(
false
);
const
aiContentBox5
=
ref
(
""
);
...
...
@@ -545,8 +545,30 @@ const switchTab = name => {
const
currentPage
=
ref
(
1
);
const
pageSize
=
ref
(
10
);
const
total
=
ref
(
0
);
const
pageScrollRef
=
ref
(
null
);
const
getScrollableParent
=
(
el
)
=>
{
let
cur
=
el
;
while
(
cur
&&
cur
!==
document
.
body
&&
cur
!==
document
.
documentElement
)
{
const
style
=
window
.
getComputedStyle
(
cur
);
const
overflowY
=
style
?.
overflowY
;
const
isScrollable
=
overflowY
===
"auto"
||
overflowY
===
"scroll"
;
if
(
isScrollable
&&
cur
.
scrollHeight
>
cur
.
clientHeight
+
1
)
{
return
cur
;
}
cur
=
cur
.
parentElement
;
}
return
null
;
};
const
scrollToTop
=
async
()
=>
{
await
nextTick
();
const
anchor
=
pageScrollRef
.
value
;
if
(
!
anchor
)
return
;
const
scrollEl
=
getScrollableParent
(
anchor
)
||
anchor
;
scrollEl
.
scrollTop
=
0
;
};
const
handleCurrentChange
=
page
=>
{
currentPage
.
value
=
page
;
scrollToTop
();
handleGetThinkTankReportViewpoint
();
};
...
...
src/views/thinkTank/SurveyProjectView/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -184,7 +184,7 @@ const applySurveyProjectDocumentTitle = (title) => {
document
.
title
=
text
;
};
const
REPORT_ANALYSIS_TIP_BOX5
=
"
调查项目关键词云,
数据来源:美国兰德公司官网"
;
"数据来源:美国兰德公司官网"
;
// 默认仅展示 AiButton,悬停后再请求 AI
const
isShowAiContentBox5
=
ref
(
false
);
const
aiContentBox5
=
ref
(
""
);
...
...
@@ -636,9 +636,6 @@ onMounted(() => {
border-bottom
:
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
);
position
:
sticky
;
top
:
0
;
z-index
:
99999
;
overflow
:
hidden
;
.header-top
{
...
...
src/views/thinkTank/ThinkTankDetail/PolicyTracking/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -149,27 +149,8 @@
</div>
<div
class=
"select-box"
>
<el-select
class=
"select-box-sort"
v-model=
"sort"
placeholder=
"发布时间"
style=
"width: 120px"
:teleported=
"true"
:placement=
"'bottom-start'"
:popper-options=
"{
modifiers: [
{
name: 'preventOverflow',
options: { mainAxis: false, altAxis: false }
},
{
name: 'flip',
enabled: false
}
]
}"
>
<
template
#
prefix
>
<img
v-if=
"sort !== true"
src=
"../thinkDynamics/images/image down.png"
class=
"select-prefix-img"
alt=
""
/>
<img
v-else
src=
"../thinkDynamics/images/image up.png"
class=
"select-prefix-img"
alt=
""
/>
</
template
>
<el-option
@
click=
"handleGetThinkPolicy()"
:key=
"true"
label=
"正序"
:value=
"true"
/>
<el-option
@
click=
"handleGetThinkPolicy()"
:key=
"false"
label=
"倒序"
:value=
"false"
/>
</el-select>
<TimeSortSelectBox
:key=
"`policy-tracking-sort-${router.currentRoute.value?.params?.id || ''}`"
:sort-demension=
"1"
@
handle-px-change=
"handlePolicyTrackingPxChange"
/>
</div>
</div>
<div
class=
"bottom-main"
>
...
...
@@ -279,8 +260,8 @@
<div
class=
"right-footer"
>
<div
class=
"info"
>
共{{ total }}条政策建议
</div>
<div
class=
"page-box"
>
<el-pagination
:page-size=
"10"
:page-count=
"pageCount"
background
layout=
"prev, pager, next"
:total=
"total"
@
current-change=
"handleCurrentChange"
:current-page=
"currentPage"
/>
<el-pagination
:page-size=
"10"
:page-count=
"pageCount"
background
layout=
"prev, pager, next"
:total=
"total"
@
current-change=
"handleCurrentChange"
:current-page=
"currentPage"
/>
</div>
</div>
</div>
...
...
@@ -306,6 +287,7 @@ import {
import
{
getChartAnalysis
}
from
"@/api/aiAnalysis/index"
;
import
AiButton
from
"@/components/base/Ai/AiButton/index.vue"
;
import
AiPane
from
"@/components/base/Ai/AiPane/index.vue"
;
import
TimeSortSelectBox
from
"@/components/base/TimeSortSelectBox/index.vue"
;
import
TipTab
from
"@/views/thinkTank/TipTab/index.vue"
;
import
defaultNewsIcon
from
"@/assets/icons/default-icon-news.png"
;
import
AreaTag
from
"@/components/base/AreaTag/index.vue"
;
...
...
@@ -353,11 +335,11 @@ const getAreaTagColor = (name, idx = 0) =>
/** 与智库概览 TipTab 文案格式一致(政策追踪-美国国会) */
const
POLICY_TRACKING_TIP_BOX1
=
"
智库报告中政策建议的领域分布情况,
数据来源:美国兰德公司官网"
;
"数据来源:美国兰德公司官网"
;
const
POLICY_TRACKING_TIP_BOX2
=
"
智库报告中政策建议部门分布情况,
数据来源:美国兰德公司官网"
;
"数据来源:美国兰德公司官网"
;
const
POLICY_TRACKING_TIP_BOX3
=
"
智库报告热门研究领域变化趋势,
数据来源:美国兰德公司官网"
;
"数据来源:美国兰德公司官网"
;
/** 筛选「全部」项文案,与市场准入概览-资源库复选逻辑一致 */
const
POLICY_FILTER_ALL_AREA
=
"全部领域"
;
...
...
@@ -1319,6 +1301,12 @@ const handleSwithSort = () => {
handleGetThinkPolicy
();
};
/** 政策追踪排序公共组件回调:1=时间倒序,2=时间正序(映射到现有 sort(true/false/null)) */
const
handlePolicyTrackingPxChange
=
(
val
)
=>
{
sort
.
value
=
Number
(
val
)
===
2
?
true
:
false
;
handleGetThinkPolicy
();
};
const
currentPage
=
ref
(
1
);
const
pageCount
=
computed
(()
=>
{
const
size
=
10
;
...
...
@@ -1610,6 +1598,8 @@ onMounted(async () => {
width
:
420px
;
height
:
22px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
flex-start
;
}
.chart-box
{
...
...
@@ -1669,10 +1659,13 @@ onMounted(async () => {
.source
{
position
:
absolute
;
bottom
:
24px
;
left
:
24px
;
width
:
420px
;
height
:
22px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
flex-start
;
}
.chart-box
{
...
...
@@ -2150,7 +2143,7 @@ onMounted(async () => {
.right
{
width
:
1224px
;
min-height
:
1670px
;
margin-bottom
:
20px
;
box-sizing
:
border-box
;
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
...
...
@@ -2167,7 +2160,7 @@ onMounted(async () => {
box-sizing
:
border-box
;
padding-left
:
37px
;
padding-right
:
0
;
max-height
:
1540px
;
flex
:
1
;
.right-empty
{
...
...
@@ -2187,7 +2180,7 @@ onMounted(async () => {
padding-left
:
37px
;
padding-right
:
36px
;
width
:
calc
(
100%
+
37px
-
36px
);
height
:
153px
;
border-bottom
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
display
:
flex
;
...
...
src/views/thinkTank/ThinkTankDetail/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -123,9 +123,6 @@ onMounted(async () => {
box-shadow
:
0
0
20px
0
rgba
(
25
,
69
,
130
,
0
.1
);
background
:
rgba
(
255
,
255
,
255
,
1
);
position
:
relative
;
position
:
sticky
;
top
:
0
;
z-index
:
99999
;
overflow
:
visible
;
.header-top
{
...
...
src/views/thinkTank/ThinkTankDetail/thinkDynamics/index.vue
浏览文件 @
e4e9a0d4
<
template
>
<div
class=
"wrap"
>
<div
class=
"wrap"
ref=
"pageScrollRef"
>
<div
class=
"main-header"
>
<div
class=
"search-box"
>
...
...
@@ -19,33 +19,8 @@
<div
class=
"select-box"
>
<div
class=
"select-box-sort"
>
<el-select
v-model=
"sort"
placeholder=
"发布时间"
style=
"width: 120px"
:teleported=
"true"
:placement=
"'bottom-start'"
:popper-options=
"
{
modifiers: [
{
name: 'preventOverflow', // 禁用自动翻转逻辑
options: {
mainAxis: false, // 禁用垂直方向的自动调整
altAxis: false, // 禁用水平方向的自动调整
}
},
{
name: 'flip', // 完全禁用翻转功能
enabled: false
}
]
}">
<template
#
prefix
>
<img
v-if=
"sort !== true"
src=
"./images/image down.png"
class=
"select-prefix-img"
alt=
""
@
click
.
stop=
"toggleSortAndFetch()"
/>
<img
v-else
src=
"./images/image up.png"
class=
"select-prefix-img"
alt=
""
@
click
.
stop=
"toggleSortAndFetch()"
/>
</
template
>
<el-option
:key=
"'think-dynamics-sort-asc'"
label=
"正序"
:value=
"true"
@
click=
"handleGetThinkDynamicsReport()"
/>
<el-option
:key=
"'think-dynamics-sort-desc'"
label=
"倒序"
:value=
"false"
@
click=
"handleGetThinkDynamicsReport()"
/>
</el-select>
<TimeSortSelectBox
:key=
"`dynamics-sort-$
{tabResetKey}`" :sort-demension="1"
@handle-px-change="handleDynamicsPxChange" />
</div>
</div>
</div>
...
...
@@ -53,8 +28,7 @@
<ThinkTankReport
v-if=
"isThinkTankReport"
:research-type-list=
"researchTypeList"
:research-time-list=
"researchTimeList"
:key=
"`智库报告-$
{tabResetKey}`" :selected-filters="selectedFilters"
:cur-footer-list="curFooterList" :total="total" :current-page="currentPage" :search-keyword="searchReport"
:loading=
"isThinkTankReportLoading"
@
update:selected-filters=
"handleSelectedFiltersUpdate"
:loading="isThinkTankReportLoading" @update:selected-filters="handleSelectedFiltersUpdate"
@filter-change="(payload) => handleGetThinkDynamicsReport(payload)" @page-change="handleCurrentChange"
@report-click="handleToReportDetail" />
<CongressHearing
v-else-if=
"isCongressHearing"
:research-type-list=
"researchTypeList"
...
...
@@ -76,6 +50,7 @@
<
script
setup
>
import
{
ref
,
reactive
,
onMounted
,
nextTick
}
from
"vue"
;
import
SurveyForm
from
"./SurveyForm/index.vue"
import
TimeSortSelectBox
from
"@/components/base/TimeSortSelectBox/index.vue"
;
// import Img1 from "./images/img1.png";
// import Img2 from "./images/img2.png";
// import Img3 from "./images/img3.png";
...
...
@@ -330,14 +305,37 @@ const handleGetHylyList = async () => {
}
};
const
toggleSortAndFetch
=
async
()
=>
{
sort
.
value
=
sort
.
value
===
true
?
false
:
true
;
/** 智库动态排序公共组件回调:1=时间倒序,2=时间正序(映射到现有 sort(true/false/null)) */
const
handleDynamicsPxChange
=
async
(
val
)
=>
{
sort
.
value
=
Number
(
val
)
===
2
?
true
:
false
;
await
handleGetThinkDynamicsReport
();
};
const
currentPage
=
ref
(
1
);
const
pageScrollRef
=
ref
(
null
);
const
getScrollableParent
=
(
el
)
=>
{
let
cur
=
el
;
while
(
cur
&&
cur
!==
document
.
body
&&
cur
!==
document
.
documentElement
)
{
const
style
=
window
.
getComputedStyle
(
cur
);
const
overflowY
=
style
?.
overflowY
;
const
isScrollable
=
overflowY
===
"auto"
||
overflowY
===
"scroll"
;
if
(
isScrollable
&&
cur
.
scrollHeight
>
cur
.
clientHeight
+
1
)
{
return
cur
;
}
cur
=
cur
.
parentElement
;
}
return
null
;
};
const
scrollToTop
=
async
()
=>
{
await
nextTick
();
const
anchor
=
pageScrollRef
.
value
;
if
(
!
anchor
)
return
;
const
scrollEl
=
getScrollableParent
(
anchor
)
||
anchor
;
scrollEl
.
scrollTop
=
0
;
};
// 处理页码改变事件
const
handleCurrentChange
=
page
=>
{
currentPage
.
value
=
page
;
scrollToTop
();
handleGetThinkDynamicsReport
()
};
...
...
@@ -615,14 +613,6 @@ onMounted(async () => {
margin-top
:
16px
;
display
:
flex
;
.select-box-time
,
.select-box-sort
{
background
:
rgb
(
255
,
255
,
255
);
box-shadow
:
0px
0px
20px
0px
rgba
(
94
,
95
,
95
,
0
.1
);
border
:
1px
solid
rgb
(
230
,
231
,
232
);
border-radius
:
4px
;
height
:
32px
;
}
.select-prefix-img
{
width
:
8px
;
...
...
src/views/thinkTank/ThinkTankDetail/thinkInfo/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -115,7 +115,7 @@
</div>
<div
class=
"source"
>
<div
class=
"info"
><img
src=
"./images/image-exclamation.png"
></div>
<div
class=
"text"
>
数据来源:美国国会官网
,数据时间:2015.1至2025.12
</div>
<div
class=
"text"
>
数据来源:美国国会官网
</div>
</div>
</AnalysisBox>
</div>
...
...
@@ -222,7 +222,7 @@
</div>
<div
class=
"source"
>
<div
class=
"info"
><img
src=
"./images/image-exclamation.png"
></div>
<div
class=
"text"
>
数据来源:美国国会官网
,数据时间:2015.1至2025.12
</div>
<div
class=
"text"
>
数据来源:美国国会官网
</div>
</div>
<div
class=
"middle"
>
<div
class=
"middle-text"
>
{{
"共"
}}{{
personTotal
}}{{
"名核心研究人员"
}}
</div>
...
...
src/views/thinkTank/TipTab/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -32,7 +32,7 @@ const props = defineProps({
width
:
100
%
;
display
:
flex
;
gap
:
8
px
;
justify
-
content
:
center
;
justify
-
content
:
flex
-
start
;
align
-
items
:
center
;
height
:
22
px
;
...
...
src/views/thinkTank/components/ThinkTankPolicyAdviceOverview.vue
浏览文件 @
e4e9a0d4
...
...
@@ -319,7 +319,8 @@ const handleYearGroupChange = (val) => {
.card-box
{
width
:
100%
;
height
:
1134px
;
padding-bottom
:
32px
;
;
display
:
flex
;
background
:
rgba
(
255
,
255
,
255
,
1
);
box-sizing
:
border-box
;
...
...
@@ -339,7 +340,7 @@ const handleYearGroupChange = (val) => {
.card-content
{
width
:
1211px
;
height
:
1067px
;
margin-top
:
33px
;
margin-left
:
37px
;
...
...
src/views/thinkTank/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -221,7 +221,7 @@
<MessageBubble
:messageList=
"messageList"
imageUrl=
"personImage"
@
more-click=
"handleToSocialDetail"
@
person-click=
"handleClickPerson"
name=
"personName"
content=
"remarks"
source=
"orgName"
/>
</div>
<DivideHeader
id=
"position3"
class=
"divide-header"
:titleText=
"'
数据总
览'"
></DivideHeader>
<DivideHeader
id=
"position3"
class=
"divide-header"
:titleText=
"'
全景概
览'"
></DivideHeader>
<div
class=
"center-footer"
>
<div
class=
"box5"
>
<div
class=
"box5-header"
>
...
...
@@ -253,7 +253,7 @@
<div
id=
"box5Chart"
class=
"box5-chart-canvas"
></div>
</div>
<div
class=
"source"
>
<TipTab
:text=
"'
智库报告数量变化趋势,
数据来源:美国各智库官网'"
/>
<TipTab
:text=
"'数据来源:美国各智库官网'"
/>
</div>
<div
class=
"chart-box"
>
<div
class=
"btn-box"
v-if=
"!isShowAiContentBox5"
@
mouseenter=
"handleSwitchAiContentShowBox5(true)"
>
...
...
@@ -293,7 +293,7 @@
<
template
v-else
>
<div
id=
"box6Chart"
></div>
<div
class=
"source"
>
<TipTab
:text=
"'
智库报告领域分布情况,
数据来源:美国各智库官网'"
/>
<TipTab
:text=
"'数据来源:美国各智库官网'"
/>
</div>
<div
class=
"chart-box"
>
<div
class=
"btn-box"
v-if=
"!isShowAiContentBox6"
@
mouseenter=
"handleSwitchAiContentShowBox6(true)"
>
...
...
@@ -325,7 +325,7 @@
<
template
v-else
>
<div
id=
"box7Chart"
></div>
<div
class=
"source"
>
<TipTab
:text=
"'
美国科技智库与主要政府机构之间的资金往来,
数据来源:美国各智库官网'"
/>
<TipTab
:text=
"'数据来源:美国各智库官网'"
/>
</div>
<div
class=
"chart-box"
>
<div
class=
"btn-box"
v-if=
"!isShowAiContentBox7"
@
mouseenter=
"handleSwitchAiContentShowBox7(true)"
>
...
...
@@ -375,7 +375,7 @@
</div>
<div
class=
"home-main-footer"
>
<DivideHeader
id=
"position4"
class=
"divide-header"
:titleText=
"'
资源
库'"
></DivideHeader>
<DivideHeader
id=
"position4"
class=
"divide-header"
:titleText=
"'
美国科技智库数据
库'"
></DivideHeader>
<div
class=
"home-main-footer-header"
>
<div
class=
"btn-box"
>
...
...
@@ -385,19 +385,8 @@
</div>
</div>
<div
class=
"select-box"
>
<el-select
v-model=
"resourceLibrarySortModel"
class=
"resource-library-sort-select"
placeholder=
"发布时间"
style=
"width: 120px"
:teleported=
"true"
placement=
"bottom-start"
:popper-options=
"resourceLibrarySortPopperOptions"
@
change=
"handleResourceLibrarySortChange"
>
<
template
#
prefix
>
<img
v-if=
"resourceLibrarySortModel !== true"
src=
"@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image down.png"
class=
"resource-library-sort-prefix-img"
alt=
""
@
click
.
stop=
"toggleResourceLibrarySortPrefix"
/>
<img
v-else
src=
"@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image up.png"
class=
"resource-library-sort-prefix-img"
alt=
""
@
click
.
stop=
"toggleResourceLibrarySortPrefix"
/>
</
template
>
<el-option
:key=
"'resource-lib-sort-asc'"
label=
"正序"
:value=
"true"
/>
<el-option
:key=
"'resource-lib-sort-desc'"
label=
"倒序"
:value=
"false"
/>
</el-select>
<TimeSortSelectBox
:key=
"`reslib-sort-${resourceTabResetKey}`"
:sort-demension=
"1"
@
handle-px-change=
"handleResourceLibraryPxChange"
/>
</div>
<!-- <el-select v-model="sort" placeholder="发布时间" style="width: 120px; margin-left: 8px">
<el-option @click="handleGetetThinkTankReport()" :key="true" label="正序" :value="true" />
...
...
@@ -409,29 +398,28 @@
v-model:selectedAreaList=
"selectedAreaList"
:pub-time-list=
"pubTimeList"
v-model:selectedPubTimeList=
"selectedPubTimeList"
@
filter-change=
"handleThinkTankReportFilterChange"
:cur-footer-list=
"curFooterList"
:total=
"total"
:current-page=
"currentPage"
:loading=
"isResourceReportLoading"
@
report-click=
"handleToReportDetail"
@
page-change=
"handleCurrentChange"
/>
:loading=
"isResourceReportLoading"
@
report-click=
"handleToReportDetail"
@
page-change=
"handleCurrentChange"
/>
<HomeMainFooterSurvey
v-else-if=
"activeCate === '调查项目'"
:area-list=
"areaList"
v-model:selectedAreaList=
"surveySelectedAreaList"
:pub-time-list=
"pubTimeList"
v-model:selectedPubTimeList=
"surveySelectedPubTimeList"
@
filter-change=
"handleSurveyFilterChange"
:cur-footer-list=
"surveyFooterList"
:total=
"surveyTotal"
:current-page=
"surveyCurrentPage"
:loading=
"isResourceSurveyLoading"
@
report-click=
"handleToSurveyProjectView"
@
page-change=
"handleSurveyCurrentChange"
/>
:loading=
"isResourceSurveyLoading"
@
report-click=
"handleToSurveyProjectView"
@
page-change=
"handleSurveyCurrentChange"
/>
<ThinkTankCongressHearingOverview
v-else-if=
"activeCate === '国会听证会'"
:key=
"`congress-${resourceTabResetKey}`"
:hearing-data=
"hearingData"
:research-type-list=
"areaList"
:research-time-list=
"pubTimeList"
v-model:selectedAreaList=
"congressSelectedAreaList"
v-model:selectedPubTimeList=
"congressSelectedPubTimeList"
:total=
"congressTotal"
:current-page=
"congressCurrentPage"
@
filter-change=
"handleCongressFilterChange"
:loading=
"isResourceHearingLoading"
@
page-change=
"handleCongressCurrentChange"
@
report-click=
"handleToHearingDetail"
/>
:loading=
"isResourceHearingLoading"
@
page-change=
"handleCongressCurrentChange"
@
report-click=
"handleToHearingDetail"
/>
<ThinkTankPolicyAdviceOverview
v-else
:key=
"`policy-${resourceTabResetKey}`"
:research-type-list=
"areaList"
:research-time-list=
"pubTimeList"
:list=
"policyFooterList"
:total=
"policyTotal"
:current-page=
"policyCurrentPage"
:page-size=
"7"
@
filter-change=
"handlePolicyFilterChange"
:loading=
"isResourcePolicyLoading"
@
page-change=
"handlePolicyCurrentChange"
/>
:loading=
"isResourcePolicyLoading"
@
page-change=
"handlePolicyCurrentChange"
/>
</div>
</div>
...
...
@@ -477,6 +465,7 @@ import getPieChart from "./utils/piechart";
import
{
MUTICHARTCOLORS
}
from
"@/common/constant.js"
;
import
getSankeyChart
from
"./utils/sankey"
;
import
{
getChartAnalysis
}
from
"@/api/aiAnalysis/index"
;
import
TimeSortSelectBox
from
"@/components/base/TimeSortSelectBox/index.vue"
;
import
defaultNewsIcon
from
"@/assets/icons/default-icon-news.png"
;
import
defaultHeaderIcin
from
"@/assets/icons/default-icon1.png"
;
import
News1
from
"./assets/images/news1.png"
;
...
...
@@ -1998,6 +1987,14 @@ const toggleResourceLibrarySortPrefix = () => {
}
};
/** 资源库排序公共组件回调:1=时间倒序,2=时间正序(与现有 sort(true/false/null) 映射) */
const
handleResourceLibraryPxChange
=
(
val
)
=>
{
// 组件默认值为 1(时间倒序),这里保持与旧逻辑一致:倒序用 false,正序用 true
const
mapped
=
Number
(
val
)
===
2
?
true
:
false
;
resourceLibrarySortModel
.
value
=
mapped
;
handleResourceLibrarySortChange
();
};
/** 与调查项目 surveyFooterList 一致:初始空列表,由接口填充;失败或非 200 时清空 */
const
curFooterList
=
ref
([]);
const
currentPage
=
ref
(
1
);
...
...
src/views/thinkTank/reportOriginal/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -309,8 +309,9 @@ onMounted(async () => {
<
style
lang=
"scss"
scoped
>
.wrap
{
overflow-y
:
auto
;
height
:
100vh
;
overflow-y
:
auto
;
overflow-x
:
hidden
;
.header
{
...
...
@@ -320,9 +321,6 @@ onMounted(async () => {
border-bottom
:
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
);
position
:
sticky
;
top
:
0
;
z-index
:
99999
;
overflow
:
hidden
;
.header-top
{
...
...
@@ -703,7 +701,6 @@ onMounted(async () => {
height
:
881px
;
display
:
flex
;
overflow-y
:
auto
;
/* 右侧统一滚动条,控制两侧原文+译文一起滚动 */
overflow-x
:
hidden
;
}
...
...
src/views/thinkTank/reportOriginal/pdf.vue
浏览文件 @
e4e9a0d4
...
...
@@ -326,7 +326,79 @@ export default {
}
}
if
(
matchList
.
value
.
length
>
0
)
jumpTo
(
0
);
if
(
matchList
.
value
.
length
>
0
)
{
// 先把所有命中都标黄,再把“当前命中”改成蓝底
renderAllHighlights
();
jumpTo
(
0
);
}
};
const
setActiveHighlight
=
(
idx
)
=>
{
const
all
=
document
.
querySelectorAll
(
'.highlight-rect[data-match-idx]'
);
all
.
forEach
((
el
)
=>
{
const
isActive
=
Number
(
el
.
getAttribute
(
'data-match-idx'
))
===
Number
(
idx
);
if
(
isActive
)
{
el
.
classList
.
add
(
'highlight-rect--active'
);
}
else
{
el
.
classList
.
remove
(
'highlight-rect--active'
);
}
});
};
const
renderAllHighlights
=
()
=>
{
clearHighlights
();
const
list
=
Array
.
isArray
(
matchList
.
value
)
?
matchList
.
value
:
[];
list
.
forEach
((
m
,
idx
)
=>
{
if
(
!
m
||
m
.
fallback
)
return
;
const
layer
=
overlayMap
[
m
.
pageNum
];
if
(
!
layer
)
return
;
const
pageWrap
=
layer
.
closest
(
'.page-wrap'
);
const
container
=
(
pageWrap
||
layer
);
const
containerRect
=
container
.
getBoundingClientRect
();
const
segs
=
Array
.
isArray
(
m
?.
segments
)
?
m
.
segments
:
[];
for
(
const
seg
of
segs
)
{
const
segEl
=
seg
?.
el
;
if
(
!
segEl
)
continue
;
const
textNode
=
segEl
.
firstChild
;
if
(
!
textNode
||
textNode
.
nodeType
!==
Node
.
TEXT_NODE
)
continue
;
try
{
const
range
=
document
.
createRange
();
range
.
setStart
(
textNode
,
Math
.
max
(
0
,
seg
.
startIdx
??
0
));
range
.
setEnd
(
textNode
,
Math
.
max
(
0
,
seg
.
endIdx
??
0
));
const
rectList
=
Array
.
from
(
range
.
getClientRects
());
if
(
rectList
.
length
)
{
rectList
.
forEach
(
r
=>
{
const
mark
=
document
.
createElement
(
'div'
);
mark
.
className
=
'highlight-rect'
;
mark
.
setAttribute
(
'data-match-idx'
,
String
(
idx
));
mark
.
style
.
zIndex
=
'5'
;
mark
.
style
.
left
=
(
r
.
left
-
containerRect
.
left
)
+
'px'
;
mark
.
style
.
top
=
(
r
.
top
-
containerRect
.
top
)
+
'px'
;
mark
.
style
.
width
=
r
.
width
+
'px'
;
mark
.
style
.
height
=
r
.
height
+
'px'
;
container
.
appendChild
(
mark
);
});
}
else
{
const
r
=
segEl
.
getBoundingClientRect
();
if
(
r
.
width
>
0
&&
r
.
height
>
0
)
{
const
mark
=
document
.
createElement
(
'div'
);
mark
.
className
=
'highlight-rect'
;
mark
.
setAttribute
(
'data-match-idx'
,
String
(
idx
));
mark
.
style
.
zIndex
=
'5'
;
mark
.
style
.
left
=
(
r
.
left
-
containerRect
.
left
)
+
'px'
;
mark
.
style
.
top
=
(
r
.
top
-
containerRect
.
top
)
+
'px'
;
mark
.
style
.
width
=
r
.
width
+
'px'
;
mark
.
style
.
height
=
r
.
height
+
'px'
;
container
.
appendChild
(
mark
);
}
}
range
.
detach
?.();
}
catch
(
e
)
{
// ignore
}
}
});
setActiveHighlight
(
matchIdx
.
value
);
};
// 跳转到第 N 个匹配项
...
...
@@ -342,55 +414,7 @@ export default {
const
firstSeg
=
m
?.
segments
?.[
0
];
const
el
=
firstSeg
?.
el
;
if
(
!
el
)
return
;
clearHighlights
();
const
layer
=
overlayMap
[
m
.
pageNum
];
if
(
!
layer
)
return
;
const
pageWrap
=
layer
.
closest
(
'.page-wrap'
);
// 用 Range 精确计算“子串”在页面上的矩形位置,再画黄色块(支持跨 span)
const
containerRect
=
(
pageWrap
||
layer
).
getBoundingClientRect
();
const
segs
=
Array
.
isArray
(
m
?.
segments
)
?
m
.
segments
:
[];
for
(
const
seg
of
segs
)
{
const
segEl
=
seg
?.
el
;
if
(
!
segEl
)
continue
;
const
textNode
=
segEl
.
firstChild
;
if
(
!
textNode
||
textNode
.
nodeType
!==
Node
.
TEXT_NODE
)
continue
;
try
{
const
range
=
document
.
createRange
();
range
.
setStart
(
textNode
,
Math
.
max
(
0
,
seg
.
startIdx
??
0
));
range
.
setEnd
(
textNode
,
Math
.
max
(
0
,
seg
.
endIdx
??
0
));
const
rectList
=
Array
.
from
(
range
.
getClientRects
());
if
(
rectList
.
length
)
{
rectList
.
forEach
(
r
=>
{
const
mark
=
document
.
createElement
(
'div'
);
mark
.
className
=
'highlight-rect'
;
mark
.
style
.
zIndex
=
'5'
;
mark
.
style
.
left
=
(
r
.
left
-
containerRect
.
left
)
+
'px'
;
mark
.
style
.
top
=
(
r
.
top
-
containerRect
.
top
)
+
'px'
;
mark
.
style
.
width
=
r
.
width
+
'px'
;
mark
.
style
.
height
=
r
.
height
+
'px'
;
(
pageWrap
||
layer
).
appendChild
(
mark
);
});
}
else
{
// Range 兜底为空时:用 span 自身的矩形画块(精度低,但尽量可见)
const
r
=
segEl
.
getBoundingClientRect
();
if
(
r
.
width
>
0
&&
r
.
height
>
0
)
{
const
mark
=
document
.
createElement
(
'div'
);
mark
.
className
=
'highlight-rect'
;
mark
.
style
.
zIndex
=
'5'
;
mark
.
style
.
left
=
(
r
.
left
-
containerRect
.
left
)
+
'px'
;
mark
.
style
.
top
=
(
r
.
top
-
containerRect
.
top
)
+
'px'
;
mark
.
style
.
width
=
r
.
width
+
'px'
;
mark
.
style
.
height
=
r
.
height
+
'px'
;
(
pageWrap
||
layer
).
appendChild
(
mark
);
}
}
range
.
detach
?.();
}
catch
(
e
)
{
// ignore
}
}
setActiveHighlight
(
idx
);
// 优先只滚动右侧 report-box,避免触发整页滚动导致 header 遮挡
const
container
=
el
.
closest
(
'.report-box'
);
...
...
@@ -510,6 +534,10 @@ canvas {
z-index
:
5
;
}
.textLayer
:deep
(
.highlight-rect--active
)
{
background
:
rgb
(
184
,
222
,
254
);
}
.page-wrap
:deep
(
.highlight-rect
)
{
position
:
absolute
;
background
:
#ff0
;
...
...
@@ -519,6 +547,10 @@ canvas {
z-index
:
3
;
}
.page-wrap
:deep
(
.highlight-rect--active
)
{
background
:
rgb
(
184
,
222
,
254
);
}
.loading
{
position
:
absolute
;
top
:
50%
;
...
...
src/views/viewRiskSignal/assets/images/risk-icon-blue.png
0 → 100644
浏览文件 @
e4e9a0d4
14.9 KB
src/views/viewRiskSignal/assets/images/risk-icon-green.png
0 → 100644
浏览文件 @
e4e9a0d4
15.2 KB
src/views/viewRiskSignal/assets/images/risk-icon-orange.png
0 → 100644
浏览文件 @
e4e9a0d4
14.0 KB
src/views/viewRiskSignal/assets/images/risk-icon.png
→
src/views/viewRiskSignal/assets/images/risk-icon
-red
.png
浏览文件 @
e4e9a0d4
File moved
src/views/viewRiskSignal/assets/images/risk-icon-yellow.png
0 → 100644
浏览文件 @
e4e9a0d4
14.7 KB
src/views/viewRiskSignal/assets/images/warning.svg
→
src/views/viewRiskSignal/assets/images/warning
-red
.svg
浏览文件 @
e4e9a0d4
File moved
src/views/viewRiskSignal/index.vue
浏览文件 @
e4e9a0d4
...
...
@@ -5,7 +5,7 @@
<div
class=
"center-center"
>
<div
class=
"center-header"
>
<div
class=
"center-header-left"
>
<img
class=
"iconstyle"
src=
"./assets/images/warning.svg"
/>
<img
class=
"iconstyle"
src=
"./assets/images/warning
-red
.svg"
/>
<div
class=
"center-header-title"
>
风险信号管理
</div>
</div>
<div
class=
"center-header-right"
>
...
...
@@ -16,7 +16,7 @@
<div
class=
"center-middle"
>
<div
class=
"center-middle-left"
>
<div
class=
"lineitem"
>
<div
class=
"item"
>
<div
class=
"item
item--shift-left
"
>
<div
class=
"top"
>
<div
class=
"dot"
style=
"background-color: rgba(95, 101, 108, 1)"
></div>
<div
class=
"text1"
>
本年新增风险
</div>
...
...
@@ -36,7 +36,7 @@
</div>
</div>
<div
class=
"lineitem"
>
<div
class=
"item"
>
<div
class=
"item
item--shift-left
"
>
<div
class=
"top"
>
<div
class=
"dot"
style=
"background-color: rgba(5, 95, 194, 1)"
></div>
<div
class=
"text1"
>
已处理风险
</div>
...
...
@@ -50,7 +50,7 @@
<div
class=
"text1"
>
待处理风险
</div>
</div>
<div
class=
"text2"
style=
"color: rgba(206, 79, 81, 1)"
>
<span
class=
"text2-inner"
>
{{
basicInfo
.
pendingCount
+
" 项"
}}
</span>
<span
class=
"text2-inner"
>
{{
formatThousands
(
basicInfo
.
pendingCount
)
+
" 项"
}}
</span>
</div>
</div>
</div>
...
...
@@ -213,7 +213,7 @@
</div>
<div
class=
"right-footer"
>
<div
class=
"footer-left"
>
{{
`共 ${
totalNum
}
项调查`
}}
{{
`共 ${
formatThousands(totalNum)
}
项调查`
}}
<
/div
>
<
div
class
=
"footer-right"
>
<
el
-
pagination
@
current
-
change
=
"handleCurrentChange"
:
pageSize
=
"pageSize"
:
current
-
page
=
"currentPage"
...
...
@@ -233,7 +233,7 @@
<
el
-
dialog
v
-
model
=
"isRiskDetailVisible"
class
=
"risk-signal-detail-dialog"
modal
-
class
=
"risk-signal-detail-modal"
width
=
"1280px"
align
-
center
:
show
-
close
=
"true"
destroy
-
on
-
close
@
closed
=
"handleCloseRiskDetail"
>
<
template
#
header
>
<
img
class
=
"header-icon"
src
=
"./assets/images/risk-icon.png
"
alt
=
""
/>
<
img
class
=
"header-icon"
:
src
=
"riskDetailHeaderIconSrc
"
alt
=
""
/>
<
span
v
-
if
=
"riskDetailListLevelText"
class
=
"risk-signal-detail-dialog__level"
:
class
=
"riskDetailListLevelModifierClass"
>
{{
riskDetailListLevelText
}}
<
/span
>
<
div
v
-
if
=
"riskDetailBodyFromApi"
class
=
"risk-signal-detail-dialog__read-indicator"
>
...
...
@@ -597,6 +597,15 @@ const riskDetailListLevelModifierClass = computed(
()
=>
`risk-signal-detail-dialog__level--${getRiskDetailLevelModifier(riskDetailListLevelText.value)
}
`
);
const
riskDetailHeaderIconSrc
=
computed
(()
=>
{
const
key
=
getRiskListItemLevelKey
(
riskDetailListLevelText
.
value
);
if
(
key
===
"lv1"
)
return
new
URL
(
"./assets/images/risk-icon-red.png"
,
import
.
meta
.
url
).
href
;
if
(
key
===
"lv2"
)
return
new
URL
(
"./assets/images/risk-icon-orange.png"
,
import
.
meta
.
url
).
href
;
if
(
key
===
"lv3"
)
return
new
URL
(
"./assets/images/risk-icon-yellow.png"
,
import
.
meta
.
url
).
href
;
if
(
key
===
"lv4"
)
return
new
URL
(
"./assets/images/risk-icon-green.png"
,
import
.
meta
.
url
).
href
;
return
new
URL
(
"./assets/images/risk-icon-blue.png"
,
import
.
meta
.
url
).
href
;
}
);
/** 列表项风险等级样式键:与 `@/components/base/riskSignal` itemLeftStatus1~5 一致 */
const
getRiskListItemLevelKey
=
(
level
)
=>
{
const
t
=
String
(
level
??
""
).
trim
();
...
...
@@ -623,6 +632,12 @@ const isRiskLevelNoData = (level) => {
const
route
=
useRoute
();
const
router
=
useRouter
();
const
formatThousands
=
(
val
)
=>
{
const
n
=
Number
(
val
??
0
);
if
(
!
Number
.
isFinite
(
n
))
return
"0"
;
return
new
Intl
.
NumberFormat
(
"en-US"
).
format
(
n
);
}
;
const
consumeOpenFirstDetailFromQuery
=
async
()
=>
{
if
(
route
.
query
[
OPEN_FIRST_RISK_DETAIL_QUERY_KEY
]
!==
"1"
)
{
return
;
...
...
@@ -1095,6 +1110,10 @@ onMounted(async () => {
unicode
-
bidi
:
isolate
;
}
}
.
item
--
shift
-
left
{
transform
:
translateX
(
-
20
px
);
}
}
}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论