Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
d85c3e2c
提交
d85c3e2c
authored
3月 23, 2026
作者:
yanpeng
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
出口管制相关修改
上级
3b47eb31
隐藏空白字符变更
内嵌
并排
正在显示
12 个修改的文件
包含
2279 行增加
和
1338 行删除
+2279
-1338
exportControl.js
src/api/exportControl.js
+2
-2
exportControlV2.0.js
src/api/exportControlV2.0.js
+18
-2
exportControl.js
src/router/modules/exportControl.js
+1
-1
ChartChain.vue
src/views/decree/decreeLayout/influence/com/ChartChain.vue
+503
-475
index.vue
src/views/exportControl/index.vue
+1
-1
index.vue
...s/sanctionsOverview/components/introductionPage/index.vue
+1
-1
index.vue
...omponents/sanctionsOverview/components/listPage/index.vue
+99
-66
index.vue
src/views/exportControl/v2.0CommercialControlList/index.vue
+13
-5
RelationGraph-back.vue
...n/components/deepMining/components/RelationGraph-back.vue
+815
-0
RelationGraph.vue
...nction/components/deepMining/components/RelationGraph.vue
+785
-749
index.vue
...ontrol/v2.0SingleSanction/components/deepMining/index.vue
+3
-1
index.vue
...v2.0SingleSanction/components/sanctionsOverview/index.vue
+38
-35
没有找到文件。
src/api/exportControl.js
浏览文件 @
d85c3e2c
...
...
@@ -393,13 +393,13 @@ export function getDomainDistribution(sanctionDate = "2025-11-11") {
* startTime: string
* }[]>}
*/
export
function
getEntitiesList
(
typeName
=
"实体清单"
,
pageNum
=
1
,
pageSize
=
10
,
sanctionDate
=
""
,
isCn
=
false
)
{
export
function
getEntitiesList
(
sanTypeId
=
1
,
pageNum
=
1
,
pageSize
=
10
,
sanctionDate
=
""
,
isCn
=
false
)
{
return
request200
(
request
({
method
:
"POST"
,
url
:
"/api/sanctionList/pageQuery"
,
data
:
{
typeName
,
sanTypeId
,
pageNum
,
pageSize
,
sanctionDate
,
...
...
src/api/exportControlV2.0.js
浏览文件 @
d85c3e2c
...
...
@@ -392,6 +392,14 @@ export function getSingleSanctionEntitySupplyChain(params) {
});
}
// 单次制裁-深度挖掘-制裁实体信息
export
function
getSingleSanctionEntityInfo
(
id
)
{
return
request
({
method
:
"GET"
,
url
:
`/api/organization/sanInfo?orgId=
${
id
}
`
,
});
}
// 单次制裁-深度挖掘-制裁实体股权信息
/**
* @param {Object} params
...
...
@@ -550,10 +558,10 @@ export function getSingleSanctionEntityInternationalPaper(params) {
}
// 商业管制清单-CCL清单简介-基本信息
export
function
getCCLInfo
()
{
export
function
getCCLInfo
(
sanTypeId
=
13
)
{
return
request
({
method
:
"GET"
,
url
:
`/api/sanctionList/baseInfo
/ccl
`
url
:
`/api/sanctionList/baseInfo
ById/
${
sanTypeId
}
`
});
}
...
...
@@ -620,3 +628,11 @@ export function getCclQuery(data) {
data
});
}
// 商业管制清单-CCL清单列表-清单版本
export
function
getCCLVersionList
()
{
return
request
({
method
:
"GET"
,
url
:
`/api/ccl/version/dateList`
});
}
src/router/modules/exportControl.js
浏览文件 @
d85c3e2c
...
...
@@ -120,7 +120,7 @@ const exportControlRoutes = [
name
:
"commercialControlList"
,
component
:
()
=>
import
(
"@/views/exportControl/v2.0CommercialControlList/index.vue"
),
meta
:
{
title
:
"
全部实体
清单"
title
:
"
商业管制
清单"
}
}
]
...
...
src/views/decree/decreeLayout/influence/com/ChartChain.vue
浏览文件 @
d85c3e2c
<
template
>
<div
class=
"view-box"
>
<el-empty
v-if=
"!dataList?.length"
style=
"padding-top: 15%;"
description=
"暂无数据"
:image-size=
"100"
/>
<div
v-if=
"dataList.length"
class=
"main-content-main"
>
<div
class=
"main-mask"
@
wheel
.
prevent=
"handleWheel"
@
mousedown=
"handleMouseDown"
@
mouseup=
"handleMouseUp"
@
mouseleave=
"handleMouseUp"
@
mousemove=
"handleMouseMove"
></div>
<div
class=
"fishbone-container"
:style=
"
{ transform: `translate(${translateX}px, ${translateY}px) scale(${scale})`, transformOrigin: 'center center' }">
<!-- 主轴上的标签 -->
<div
class=
"main-line"
:style=
"
{ width: dataList.length * 200 + 300 + 'px' }">
<div
class=
"main-line-text"
v-for=
"(item, index) in dataList"
:key=
"'label-' + index"
:class=
"
{
'blue-theme': index
<
2
,
'
green-theme
'
:
index
>
= 2
&&
index
<
4
,
'
purple-theme
'
:
index
>
= 4
}" :style="{ left: index * 200 + 220 + 'px' }">
{{
item
.
text
}}
</div>
</div>
<!-- 奇数索引的数据组放在上方 -->
<div
v-for=
"(causeGroup, groupIndex) in onFilterData(1)"
:key=
"groupIndex"
class=
"top-bone"
:style=
"
{ left: groupIndex * 400 + 510 + 'px', height: (causeGroup.causes?.length) * 22 + 100 + 'px' }">
<div
class=
"left-bone"
>
<div
class=
"left-bone-item"
v-for=
"item in getLeftItems(causeGroup.causes)"
:key=
"item.id"
>
<img
:src=
"defaultIcon2 || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{
item
.
name
}}
</div>
<div
class=
"line"
></div>
</div>
</div>
<div
class=
"right-bone"
>
<div
class=
"right-bone-item"
v-for=
"item in getRightItems(causeGroup.causes)"
:key=
"item.id"
>
<div
class=
"line"
></div>
<img
:src=
"defaultIcon2 || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{
item
.
name
}}
</div>
</div>
</div>
</div>
<!-- 偶数索引的数据组放在下方 -->
<div
v-for=
"(causeGroup, groupIndex) in onFilterData(0)"
:key=
"groupIndex"
class=
"bottom-bone"
:style=
"
{ left: groupIndex * 400 + 310 + 'px', height: (causeGroup.causes?.length) * 22 + 100 + 'px' }">
<div
class=
"left-bone"
>
<div
class=
"left-bone-item"
v-for=
"item in getRightItems(causeGroup.causes)"
:key=
"item.id"
>
<img
:src=
"defaultIcon2 || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{
item
.
name
}}
</div>
<div
class=
"line"
></div>
</div>
</div>
<div
class=
"right-bone"
>
<div
class=
"right-bone-item"
v-for=
"item in getLeftItems(causeGroup.causes)"
:key=
"item.id"
>
<div
class=
"line"
></div>
<img
:src=
"defaultIcon2 || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{
item
.
name
}}
</div>
</div>
</div>
</div>
</div>
</div>
<div
v-if=
"dataList.length"
class=
"main-content-footer"
>
<div
class=
"footer-item footer-item1"
>
<div
class=
"footer-item-bottom"
>
<div
class=
"icon"
>
<img
:src=
"noticeIcon"
alt=
""
/>
</div>
<div
class=
"text"
>
{{
`中国企业${cnEntityOnChainData.upstreamInternalCount ||
0
}
家(${formatRate(cnEntityOnChainData.upstreamInternalRate)
}
%),受制裁${cnEntityOnChainData.upstreamEntityCount
|| 0
}
家(${formatRate(cnEntityOnChainData.upstreamEntityRate)
}
%)`
}}
<
/div
>
<
/div
>
<
div
class
=
"footer-item-top"
>
{{
"上游"
}}
<
/div
>
<
/div
>
<
div
class
=
"footer-item footer-item2"
>
<
div
class
=
"footer-item-bottom"
>
<
div
class
=
"icon"
>
<
img
:
src
=
"noticeIcon"
alt
=
""
/>
<
/div
>
<
div
class
=
"text"
>
{{
`中国企业${cnEntityOnChainData.midstreamInternalCount ||
0
}
家(${formatRate(cnEntityOnChainData.midstreamInternalRate)
}
%),受制裁${cnEntityOnChainData.midstreamEntityCount
|| 0
}
家(${formatRate(cnEntityOnChainData.midstreamEntityRate)
}
%)`
}}
<
/div
>
<
/div
>
<
div
class
=
"footer-item-top"
>
{{
"中游"
}}
<
/div
>
<
/div
>
<
div
class
=
"footer-item footer-item3"
>
<
div
class
=
"footer-item-bottom"
>
<
div
class
=
"icon"
>
<
img
:
src
=
"noticeIcon"
alt
=
""
/>
<
/div
>
<
div
class
=
"text"
>
{{
`中国企业${cnEntityOnChainData.downstreamInternalCount ||
0
}
家(${formatRate(cnEntityOnChainData.downstreamInternalRate)
}
%),受制裁${cnEntityOnChainData.downstreamEntityCount
|| 0
}
家(${formatRate(cnEntityOnChainData.downstreamEntityRate)
}
%)`
}}
<
/div
>
<
/div
>
<
div
class
=
"footer-item-top"
>
{{
"下游"
}}
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<div
class=
"view-box"
>
<el-empty
v-if=
"!dataList?.length"
style=
"padding-top: 15%"
description=
"暂无数据"
:image-size=
"100"
/>
<div
v-if=
"dataList.length"
class=
"main-content-main"
>
<div
class=
"main-mask"
@
wheel
.
prevent=
"handleWheel"
@
mousedown=
"handleMouseDown"
@
mouseup=
"handleMouseUp"
@
mouseleave=
"handleMouseUp"
@
mousemove=
"handleMouseMove"
></div>
<div
class=
"fishbone-container"
:style=
"
{
transform: `translate(${translateX}px, ${translateY}px) scale(${scale})`,
transformOrigin: 'center center'
}"
>
<!-- 主轴上的标签 -->
<div
class=
"main-line"
:style=
"
{ width: dataList.length * 200 + 300 + 'px' }">
<div
class=
"main-line-text"
v-for=
"(item, index) in dataList"
:key=
"'label-' + index"
:class=
"
{
'blue-theme': index
<
2
,
'
green-theme
'
:
index
>
= 2
&&
index
<
4
,
'
purple-theme
'
:
index
>
= 4
}"
:style="{ left: index * 200 + 220 + 'px' }"
>
{{
item
.
text
}}
</div>
</div>
<!-- 奇数索引的数据组放在上方 -->
<div
v-for=
"(causeGroup, groupIndex) in onFilterData(1)"
:key=
"groupIndex"
class=
"top-bone"
:style=
"
{ left: groupIndex * 400 + 510 + 'px', height: causeGroup.causes?.length * 22 + 100 + 'px' }"
>
<div
class=
"left-bone"
>
<div
class=
"left-bone-item"
v-for=
"item in getLeftItems(causeGroup.causes)"
:key=
"item.id"
>
<img
:src=
"defaultIcon2 || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{
item
.
name
}}
</div>
<div
class=
"line"
></div>
</div>
</div>
<div
class=
"right-bone"
>
<div
class=
"right-bone-item"
v-for=
"item in getRightItems(causeGroup.causes)"
:key=
"item.id"
>
<div
class=
"line"
></div>
<img
:src=
"defaultIcon2 || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{
item
.
name
}}
</div>
</div>
</div>
</div>
<!-- 偶数索引的数据组放在下方 -->
<div
v-for=
"(causeGroup, groupIndex) in onFilterData(0)"
:key=
"groupIndex"
class=
"bottom-bone"
:style=
"
{ left: groupIndex * 400 + 310 + 'px', height: causeGroup.causes?.length * 22 + 100 + 'px' }"
>
<div
class=
"left-bone"
>
<div
class=
"left-bone-item"
v-for=
"item in getRightItems(causeGroup.causes)"
:key=
"item.id"
>
<img
:src=
"defaultIcon2 || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{
item
.
name
}}
</div>
<div
class=
"line"
></div>
</div>
</div>
<div
class=
"right-bone"
>
<div
class=
"right-bone-item"
v-for=
"item in getLeftItems(causeGroup.causes)"
:key=
"item.id"
>
<div
class=
"line"
></div>
<img
:src=
"defaultIcon2 || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{
item
.
name
}}
</div>
</div>
</div>
</div>
</div>
</div>
<div
v-if=
"dataList.length"
class=
"main-content-footer"
>
<div
class=
"footer-item footer-item1"
>
<div
class=
"footer-item-bottom"
>
<div
class=
"icon"
>
<img
:src=
"noticeIcon"
alt=
""
/>
</div>
<div
class=
"text"
>
{{
`中国企业${cnEntityOnChainData.upstreamInternalCount || 0
}
家(${formatRate(
cnEntityOnChainData.upstreamInternalRate
)
}
%),受制裁${cnEntityOnChainData.upstreamEntityCount || 0
}
家(${formatRate(
cnEntityOnChainData.upstreamEntityRate
)
}
%)`
}}
<
/div
>
<
/div
>
<
div
class
=
"footer-item-top"
>
{{
"上游"
}}
<
/div
>
<
/div
>
<
div
class
=
"footer-item footer-item2"
>
<
div
class
=
"footer-item-bottom"
>
<
div
class
=
"icon"
>
<
img
:
src
=
"noticeIcon"
alt
=
""
/>
<
/div
>
<
div
class
=
"text"
>
{{
`中国企业${cnEntityOnChainData.midstreamInternalCount || 0
}
家(${formatRate(
cnEntityOnChainData.midstreamInternalRate
)
}
%),受制裁${cnEntityOnChainData.midstreamEntityCount || 0
}
家(${formatRate(
cnEntityOnChainData.midstreamEntityRate
)
}
%)`
}}
<
/div
>
<
/div
>
<
div
class
=
"footer-item-top"
>
{{
"中游"
}}
<
/div
>
<
/div
>
<
div
class
=
"footer-item footer-item3"
>
<
div
class
=
"footer-item-bottom"
>
<
div
class
=
"icon"
>
<
img
:
src
=
"noticeIcon"
alt
=
""
/>
<
/div
>
<
div
class
=
"text"
>
{{
`中国企业${cnEntityOnChainData.downstreamInternalCount || 0
}
家(${formatRate(
cnEntityOnChainData.downstreamInternalRate
)
}
%),受制裁${cnEntityOnChainData.downstreamEntityCount || 0
}
家(${formatRate(
cnEntityOnChainData.downstreamEntityRate
)
}
%)`
}}
<
/div
>
<
/div
>
<
div
class
=
"footer-item-top"
>
{{
"下游"
}}
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/template
>
<
script
setup
name
=
"ChartChain"
>
import
{
ref
,
onMounted
}
from
"vue"
;
import
defaultIcon2
from
"@/assets/icons/default-icon2.png"
;
import
noticeIcon
from
"../assets/images/notice-icon.png"
;
import
{
getDeepMiningSelect
,
getDeepMiningIndustry
,
getDeepMiningIndustryFishbone
,
getDeepMiningIndustryEntity
}
from
"@/api/exportControlV2.0"
;
import
{
getDeepMiningSelect
,
getDeepMiningIndustry
,
getDeepMiningIndustryFishbone
,
getDeepMiningIndustryEntity
}
from
"@/api/exportControlV2.0"
;
// 缩放功能处理
const
scale
=
ref
(
1
)
const
minScale
=
0.1
const
maxScale
=
10
const
handleWheel
=
(
e
)
=>
{
if
(
e
.
deltaY
<
0
)
{
// 放大:不超过最大值
scale
.
value
=
Math
.
min
(
scale
.
value
+
0.1
,
maxScale
)
}
else
{
// 缩小:不低于最小值
scale
.
value
=
Math
.
max
(
scale
.
value
-
0.1
,
minScale
)
}
}
const
scale
=
ref
(
1
)
;
const
minScale
=
0.1
;
const
maxScale
=
10
;
const
handleWheel
=
e
=>
{
if
(
e
.
deltaY
<
0
)
{
// 放大:不超过最大值
scale
.
value
=
Math
.
min
(
scale
.
value
+
0.1
,
maxScale
);
}
else
{
// 缩小:不低于最小值
scale
.
value
=
Math
.
max
(
scale
.
value
-
0.1
,
minScale
);
}
}
;
// 移动功能处理
const
translateX
=
ref
(
0
)
// X轴位移
const
translateY
=
ref
(
0
)
// Y轴位移
let
isDragging
=
false
let
startX
=
0
let
startY
=
0
const
handleMouseMove
=
(
e
)
=>
{
if
(
!
isDragging
)
return
translateX
.
value
=
e
.
clientX
-
startX
translateY
.
value
=
e
.
clientY
-
startY
}
const
handleMouseDown
=
(
e
)
=>
{
// 排除右键/中键,只响应左键(e.button=0为左键)
if
(
e
.
button
!==
0
)
return
isDragging
=
true
startX
=
e
.
clientX
-
translateX
.
value
startY
=
e
.
clientY
-
translateY
.
value
}
const
translateX
=
ref
(
0
)
;
// X轴位移
const
translateY
=
ref
(
0
)
;
// Y轴位移
let
isDragging
=
false
;
let
startX
=
0
;
let
startY
=
0
;
const
handleMouseMove
=
e
=>
{
if
(
!
isDragging
)
return
;
translateX
.
value
=
e
.
clientX
-
startX
;
translateY
.
value
=
e
.
clientY
-
startY
;
}
;
const
handleMouseDown
=
e
=>
{
// 排除右键/中键,只响应左键(e.button=0为左键)
if
(
e
.
button
!==
0
)
return
;
isDragging
=
true
;
startX
=
e
.
clientX
-
translateX
.
value
;
startY
=
e
.
clientY
-
translateY
.
value
;
}
;
const
handleMouseUp
=
()
=>
{
isDragging
=
false
}
isDragging
=
false
;
}
;
// 实体清单-深度挖掘-产业链中国企业实体信息查询
const
cnEntityOnChainData
=
ref
({
}
);
const
getCnEntityOnChainData
=
async
()
=>
{
const
currentSanction
=
sanctionList
.
value
.
find
(
item
=>
item
.
id
===
currentSanctionId
.
value
);
const
date
=
currentSanction
?
currentSanction
.
date
:
''
;
const
date
=
currentSanction
?
currentSanction
.
date
:
""
;
// 确保 date 格式正确
const
formattedDate
=
date
&&
date
.
includes
(
'年'
)
?
date
.
replace
(
'年'
,
'-'
).
replace
(
'月'
,
'-'
).
replace
(
'日'
,
''
)
:
date
;
const
formattedDate
=
date
&&
date
.
includes
(
"年"
)
?
date
.
replace
(
"年"
,
"-"
).
replace
(
"月"
,
"-"
).
replace
(
"日"
,
""
)
:
date
;
const
params
=
{
date
:
formattedDate
...
...
@@ -166,7 +197,7 @@ const getCnEntityOnChainData = async () => {
}
try
{
const
res
=
await
getDeepMiningIndustryEntity
(
params
);
console
.
log
(
"企业信息"
,
res
)
console
.
log
(
"企业信息"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
cnEntityOnChainData
.
value
=
res
.
data
;
}
else
{
...
...
@@ -176,12 +207,12 @@ const getCnEntityOnChainData = async () => {
console
.
error
(
"获取产业链中国企业实体信息失败:"
,
error
);
cnEntityOnChainData
.
value
=
{
}
;
}
}
}
;
// 产业链鱼骨数据
const
dataList
=
ref
([]);
// 奇数索引的数据组放在上方, 偶数索引的数据组放在下方
const
onFilterData
=
(
num
)
=>
{
const
onFilterData
=
num
=>
{
return
dataList
.
value
.
filter
((
_
,
index
)
=>
index
%
2
===
num
);
}
;
// 获取左侧显示的项目(前半部分)
...
...
@@ -196,9 +227,9 @@ const getRightItems = items => {
}
;
const
getFishboneData
=
async
()
=>
{
const
currentSanction
=
sanctionList
.
value
.
find
(
item
=>
item
.
id
===
currentSanctionId
.
value
);
const
date
=
currentSanction
?
currentSanction
.
date
:
''
;
const
date
=
currentSanction
?
currentSanction
.
date
:
""
;
// 确保 date 格式正确
const
formattedDate
=
date
&&
date
.
includes
(
'年'
)
?
date
.
replace
(
'年'
,
'-'
).
replace
(
'月'
,
'-'
).
replace
(
'日'
,
''
)
:
date
;
const
formattedDate
=
date
&&
date
.
includes
(
"年"
)
?
date
.
replace
(
"年"
,
"-"
).
replace
(
"月"
,
"-"
).
replace
(
"日"
,
""
)
:
date
;
const
params
=
{
date
:
formattedDate
...
...
@@ -208,7 +239,7 @@ const getFishboneData = async () => {
}
try
{
const
res
=
await
getDeepMiningIndustryFishbone
(
params
);
console
.
log
(
"获取产业链数据:"
,
res
);
console
.
log
(
"获取产业链数据:"
,
res
);
if
(
res
.
code
===
200
&&
res
.
data
?.
causes
?.
length
)
{
dataList
.
value
=
res
.
data
.
causes
;
}
else
{
...
...
@@ -218,7 +249,7 @@ const getFishboneData = async () => {
console
.
error
(
"获取产业链鱼骨图数据失败:"
,
error
);
dataList
.
value
=
[];
}
}
}
;
// 实体清单-深度挖掘-产业链列表信息
const
selectedIndustryId
=
ref
(
null
);
...
...
@@ -226,12 +257,10 @@ const getIndustryList = async () => {
try
{
const
res
=
await
getDeepMiningIndustry
();
if
(
res
.
code
===
200
&&
res
.
data
&&
res
.
data
.
length
>
0
)
{
selectedIndustryId
.
value
=
res
.
data
[
0
].
id
;
getFishboneData
();
getCnEntityOnChainData
();
}
else
{
selectedIndustryId
.
value
=
null
;
}
}
catch
(
error
)
{
...
...
@@ -239,7 +268,7 @@ const getIndustryList = async () => {
selectedIndustryId
.
value
=
null
;
}
}
}
;
// 获取选择制裁
const
loading
=
ref
(
false
);
...
...
@@ -249,8 +278,8 @@ const pageSize = ref(10000);
const
getDeepMiningSelectData
=
async
()
=>
{
loading
.
value
=
true
;
const
params
=
{
startDate
:
dateRange
.
value
&&
dateRange
.
value
[
0
]
?
dateRange
.
value
[
0
]
:
''
,
endDate
:
dateRange
.
value
&&
dateRange
.
value
[
1
]
?
dateRange
.
value
[
1
]
:
''
,
startDate
:
dateRange
.
value
&&
dateRange
.
value
[
0
]
?
dateRange
.
value
[
0
]
:
""
,
endDate
:
dateRange
.
value
&&
dateRange
.
value
[
1
]
?
dateRange
.
value
[
1
]
:
""
,
typeName
:
"实体清单"
,
isCn
:
false
,
pageNum
:
currentPage
.
value
,
...
...
@@ -264,7 +293,7 @@ const getDeepMiningSelectData = async () => {
date
:
item
.
postDate
,
title
:
item
.
name
,
count
:
item
.
cnEntityCount
,
unit
:
'家中国实体'
,
// 接口未返回单位,暂时固定
unit
:
"家中国实体"
,
// 接口未返回单位,暂时固定
summary
:
item
.
summary
,
// 保留额外信息备用
techDomainList
:
item
.
techDomainList
// 保留额外信息备用
}
));
...
...
@@ -283,7 +312,7 @@ const getDeepMiningSelectData = async () => {
}
finally
{
loading
.
value
=
false
;
}
}
}
;
const
dateRange
=
ref
([
"2025-01-01"
,
"2025-12-31"
]);
const
sanctionList
=
ref
([
...
...
@@ -299,8 +328,8 @@ const sanctionList = ref([
const
currentSanctionId
=
ref
(
5
);
// 格式化比率
const
formatRate
=
(
rate
,
ratio
=
false
)
=>
{
if
(
!
rate
)
return
'0.00'
;
const
formatRate
=
(
rate
,
ratio
=
false
)
=>
{
if
(
!
rate
)
return
"0.00"
;
return
(
rate
*
100
).
toFixed
(
2
);
}
;
...
...
@@ -314,320 +343,320 @@ onMounted(() => {
<
style
scoped
lang
=
"scss"
>
.
view
-
box
{
width
:
100
%
;
height
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
.
main
-
content
-
main
{
position
:
relative
;
height
:
20
px
;
flex
:
auto
;
display
:
flex
;
align
-
items
:
center
;
justify
-
content
:
center
;
overflow
:
hidden
;
.
main
-
mask
{
position
:
absolute
;
top
:
0
;
left
:
0
;
width
:
100
%
;
height
:
100
%
;
z
-
index
:
3
;
}
.
fishbone
-
container
{
position
:
relative
;
.
main
-
line
{
height
:
3
px
;
background
:
rgb
(
230
,
231
,
232
);
display
:
flex
;
justify
-
content
:
space
-
between
;
align
-
items
:
center
;
// 添加中间的文字块
.
main
-
line
-
text
{
position
:
absolute
;
// top: -14px;
font
-
size
:
16
px
;
color
:
#
055
FC
2
;
font
-
weight
:
bold
;
background
-
color
:
#
f7f8f9
;
padding
:
0
10
px
;
z
-
index
:
2
;
// 箭头背景
height
:
32
px
;
line
-
height
:
32
px
;
width
:
160
px
;
text
-
align
:
center
;
background
:
rgba
(
231
,
243
,
255
,
1
);
clip
-
path
:
polygon
(
0
%
0
%
,
90
%
0
%
,
100
%
50
%
,
90
%
100
%
,
0
%
100
%
,
10
%
50
%
);
&
.
blue
-
theme
{
background
:
rgba
(
231
,
243
,
255
,
1
);
color
:
rgba
(
22
,
119
,
255
,
1
);
}
&
.
green
-
theme
{
background
:
rgba
(
225
,
255
,
251
,
1
);
color
:
rgba
(
19
,
168
,
168
,
1
);
}
&
.
purple
-
theme
{
background
:
rgba
(
246
,
235
,
255
,
1
);
color
:
rgba
(
146
,
84
,
222
,
1
);
}
}
}
}
.
company
-
icon
{
width
:
16
px
;
height
:
16
px
;
margin
:
0
4
px
;
object
-
fit
:
contain
;
}
.
top
-
bone
{
position
:
absolute
;
bottom
:
0
px
;
width
:
3
px
;
background
:
rgb
(
230
,
231
,
232
);
transform
-
origin
:
bottom
center
;
transform
:
skew
(
30
deg
);
z
-
index
:
1
;
.
left
-
bone
{
color
:
#
777
;
position
:
absolute
;
top
:
-
20
px
;
right
:
0
;
width
:
180
px
;
height
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
justify
-
content
:
flex
-
end
;
.
left
-
bone
-
item
{
transform
:
skew
(
-
30
deg
);
height
:
40
px
;
margin
:
4
px
0
;
display
:
flex
;
justify
-
content
:
flex
-
end
;
align
-
items
:
center
;
.
text
{
margin
-
left
:
4
px
;
height
:
25
px
;
line
-
height
:
25
px
;
overflow
:
hidden
;
text
-
overflow
:
ellipsis
;
white
-
space
:
nowrap
;
}
.
line
{
margin
-
left
:
7
px
;
width
:
40
px
;
height
:
2
px
;
background
:
rgb
(
230
,
231
,
232
);
}
}
}
.
right
-
bone
{
color
:
#
777
;
position
:
absolute
;
top
:
-
44
px
;
left
:
0
;
width
:
180
px
;
height
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
justify
-
content
:
flex
-
end
;
.
right
-
bone
-
item
{
transform
:
skew
(
-
30
deg
);
height
:
40
px
;
margin
:
4
px
0
;
display
:
flex
;
justify
-
content
:
flex
-
start
;
align
-
items
:
center
;
.
line
{
margin
-
right
:
7
px
;
width
:
30
px
;
height
:
2
px
;
background
:
rgb
(
230
,
231
,
232
);
}
.
text
{
max
-
width
:
100
px
;
margin
-
right
:
4
px
;
height
:
25
px
;
line
-
height
:
25
px
;
overflow
:
hidden
;
text
-
overflow
:
ellipsis
;
white
-
space
:
nowrap
;
}
}
}
}
.
bottom
-
bone
{
position
:
absolute
;
top
:
0
px
;
width
:
3
px
;
background
:
rgb
(
230
,
231
,
232
);
transform
-
origin
:
top
center
;
transform
:
skew
(
-
30
deg
);
z
-
index
:
1
;
.
left
-
bone
{
color
:
#
777
;
position
:
absolute
;
bottom
:
-
44
px
;
right
:
0
;
width
:
180
px
;
height
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
justify
-
content
:
flex
-
start
;
.
left
-
bone
-
item
{
transform
:
skew
(
30
deg
);
height
:
40
px
;
margin
:
4
px
0
;
display
:
flex
;
justify
-
content
:
flex
-
end
;
align
-
items
:
center
;
.
text
{
margin
-
left
:
4
px
;
height
:
25
px
;
max
-
width
:
130
px
;
line
-
height
:
25
px
;
overflow
:
hidden
;
text
-
overflow
:
ellipsis
;
white
-
space
:
nowrap
;
}
.
line
{
margin
-
left
:
7
px
;
width
:
40
px
;
height
:
2
px
;
background
:
rgb
(
230
,
231
,
232
);
}
}
}
.
right
-
bone
{
color
:
#
777
;
position
:
absolute
;
bottom
:
-
20
px
;
left
:
0
;
width
:
180
px
;
height
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
justify
-
content
:
flex
-
start
;
.
right
-
bone
-
item
{
transform
:
skew
(
30
deg
);
height
:
40
px
;
margin
:
4
px
0
;
display
:
flex
;
justify
-
content
:
flex
-
start
;
align
-
items
:
center
;
.
line
{
margin
-
right
:
7
px
;
width
:
30
px
;
height
:
2
px
;
background
:
rgb
(
230
,
231
,
232
);
}
.
text
{
max
-
width
:
100
px
;
margin
-
right
:
4
px
;
height
:
25
px
;
line
-
height
:
25
px
;
overflow
:
hidden
;
text
-
overflow
:
ellipsis
;
white
-
space
:
nowrap
;
}
}
}
}
}
.
main
-
content
-
footer
{
margin
-
top
:
16
px
;
display
:
flex
;
justify
-
content
:
space
-
between
;
.
footer
-
item
{
flex
:
1
;
display
:
flex
;
flex
-
direction
:
column
;
justify
-
content
:
flex
-
end
;
}
.
footer
-
item
{
.
footer
-
item
-
top
{
height
:
28
px
;
text
-
align
:
center
;
line
-
height
:
28
px
;
font
-
family
:
Microsoft
YaHei
;
font
-
size
:
16
px
;
font
-
weight
:
700
;
margin
-
top
:
10
px
;
position
:
relative
;
z
-
index
:
1
;
}
.
footer
-
item
-
bottom
{
display
:
flex
;
justify
-
content
:
center
;
align
-
items
:
center
;
.
text
{
color
:
rgba
(
206
,
79
,
81
,
1
);
font
-
size
:
14
px
;
line
-
height
:
14
px
;
margin
-
left
:
6
px
;
}
.
icon
{
width
:
16
px
;
height
:
16
px
;
font
-
size
:
0
;
img
{
width
:
100
%
;
height
:
100
%
;
}
}
}
}
.
footer
-
item1
{
color
:
rgba
(
22
,
119
,
255
,
1
);
.
footer
-
item
-
top
{
background
:
rgba
(
231
,
243
,
255
,
1
);
}
}
.
footer
-
item2
{
color
:
rgba
(
19
,
168
,
168
,
1
);
.
footer
-
item
-
top
{
background
:
rgba
(
225
,
255
,
251
,
1
);
}
}
.
footer
-
item3
{
color
:
rgba
(
146
,
84
,
222
,
1
);
.
footer
-
item
-
top
{
background
:
rgba
(
246
,
235
,
255
,
1
);
}
}
}
width
:
100
%
;
height
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
.
main
-
content
-
main
{
position
:
relative
;
height
:
20
px
;
flex
:
auto
;
display
:
flex
;
align
-
items
:
center
;
justify
-
content
:
center
;
overflow
:
hidden
;
.
main
-
mask
{
position
:
absolute
;
top
:
0
;
left
:
0
;
width
:
100
%
;
height
:
100
%
;
z
-
index
:
3
;
}
.
fishbone
-
container
{
position
:
relative
;
.
main
-
line
{
height
:
3
px
;
background
:
rgb
(
230
,
231
,
232
);
display
:
flex
;
justify
-
content
:
space
-
between
;
align
-
items
:
center
;
// 添加中间的文字块
.
main
-
line
-
text
{
position
:
absolute
;
// top: -14px;
font
-
size
:
16
px
;
color
:
#
055
fc
2
;
font
-
weight
:
bold
;
background
-
color
:
#
f7f8f9
;
padding
:
0
10
px
;
z
-
index
:
2
;
// 箭头背景
height
:
32
px
;
line
-
height
:
32
px
;
width
:
160
px
;
text
-
align
:
center
;
background
:
rgba
(
231
,
243
,
255
,
1
);
clip
-
path
:
polygon
(
0
%
0
%
,
90
%
0
%
,
100
%
50
%
,
90
%
100
%
,
0
%
100
%
,
10
%
50
%
);
&
.
blue
-
theme
{
background
:
rgba
(
231
,
243
,
255
,
1
);
color
:
rgba
(
22
,
119
,
255
,
1
);
}
&
.
green
-
theme
{
background
:
rgba
(
225
,
255
,
251
,
1
);
color
:
rgba
(
19
,
168
,
168
,
1
);
}
&
.
purple
-
theme
{
background
:
rgba
(
246
,
235
,
255
,
1
);
color
:
rgba
(
146
,
84
,
222
,
1
);
}
}
}
}
.
company
-
icon
{
width
:
16
px
;
height
:
16
px
;
margin
:
0
4
px
;
object
-
fit
:
contain
;
}
.
top
-
bone
{
position
:
absolute
;
bottom
:
0
px
;
width
:
3
px
;
background
:
rgb
(
230
,
231
,
232
);
transform
-
origin
:
bottom
center
;
transform
:
skew
(
30
deg
);
z
-
index
:
1
;
.
left
-
bone
{
color
:
#
777
;
position
:
absolute
;
top
:
-
20
px
;
right
:
0
;
width
:
180
px
;
height
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
justify
-
content
:
flex
-
end
;
.
left
-
bone
-
item
{
transform
:
skew
(
-
30
deg
);
height
:
40
px
;
margin
:
4
px
0
;
display
:
flex
;
justify
-
content
:
flex
-
end
;
align
-
items
:
center
;
.
text
{
margin
-
left
:
4
px
;
height
:
25
px
;
line
-
height
:
25
px
;
overflow
:
hidden
;
text
-
overflow
:
ellipsis
;
white
-
space
:
nowrap
;
}
.
line
{
margin
-
left
:
7
px
;
width
:
40
px
;
height
:
2
px
;
background
:
rgb
(
230
,
231
,
232
);
}
}
}
.
right
-
bone
{
color
:
#
777
;
position
:
absolute
;
top
:
-
44
px
;
left
:
0
;
width
:
180
px
;
height
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
justify
-
content
:
flex
-
end
;
.
right
-
bone
-
item
{
transform
:
skew
(
-
30
deg
);
height
:
40
px
;
margin
:
4
px
0
;
display
:
flex
;
justify
-
content
:
flex
-
start
;
align
-
items
:
center
;
.
line
{
margin
-
right
:
7
px
;
width
:
30
px
;
height
:
2
px
;
background
:
rgb
(
230
,
231
,
232
);
}
.
text
{
max
-
width
:
100
px
;
margin
-
right
:
4
px
;
height
:
25
px
;
line
-
height
:
25
px
;
overflow
:
hidden
;
text
-
overflow
:
ellipsis
;
white
-
space
:
nowrap
;
}
}
}
}
.
bottom
-
bone
{
position
:
absolute
;
top
:
0
px
;
width
:
3
px
;
background
:
rgb
(
230
,
231
,
232
);
transform
-
origin
:
top
center
;
transform
:
skew
(
-
30
deg
);
z
-
index
:
1
;
.
left
-
bone
{
color
:
#
777
;
position
:
absolute
;
bottom
:
-
44
px
;
right
:
0
;
width
:
180
px
;
height
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
justify
-
content
:
flex
-
start
;
.
left
-
bone
-
item
{
transform
:
skew
(
30
deg
);
height
:
40
px
;
margin
:
4
px
0
;
display
:
flex
;
justify
-
content
:
flex
-
end
;
align
-
items
:
center
;
.
text
{
margin
-
left
:
4
px
;
height
:
25
px
;
max
-
width
:
130
px
;
line
-
height
:
25
px
;
overflow
:
hidden
;
text
-
overflow
:
ellipsis
;
white
-
space
:
nowrap
;
}
.
line
{
margin
-
left
:
7
px
;
width
:
40
px
;
height
:
2
px
;
background
:
rgb
(
230
,
231
,
232
);
}
}
}
.
right
-
bone
{
color
:
#
777
;
position
:
absolute
;
bottom
:
-
20
px
;
left
:
0
;
width
:
180
px
;
height
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
justify
-
content
:
flex
-
start
;
.
right
-
bone
-
item
{
transform
:
skew
(
30
deg
);
height
:
40
px
;
margin
:
4
px
0
;
display
:
flex
;
justify
-
content
:
flex
-
start
;
align
-
items
:
center
;
.
line
{
margin
-
right
:
7
px
;
width
:
30
px
;
height
:
2
px
;
background
:
rgb
(
230
,
231
,
232
);
}
.
text
{
max
-
width
:
100
px
;
margin
-
right
:
4
px
;
height
:
25
px
;
line
-
height
:
25
px
;
overflow
:
hidden
;
text
-
overflow
:
ellipsis
;
white
-
space
:
nowrap
;
}
}
}
}
}
.
main
-
content
-
footer
{
margin
-
top
:
16
px
;
display
:
flex
;
justify
-
content
:
space
-
between
;
.
footer
-
item
{
flex
:
1
;
display
:
flex
;
flex
-
direction
:
column
;
justify
-
content
:
flex
-
end
;
}
.
footer
-
item
{
.
footer
-
item
-
top
{
height
:
28
px
;
text
-
align
:
center
;
line
-
height
:
28
px
;
font
-
family
:
Microsoft
YaHei
;
font
-
size
:
16
px
;
font
-
weight
:
700
;
margin
-
top
:
10
px
;
position
:
relative
;
z
-
index
:
1
;
}
.
footer
-
item
-
bottom
{
display
:
flex
;
justify
-
content
:
center
;
align
-
items
:
center
;
.
text
{
color
:
rgba
(
206
,
79
,
81
,
1
);
font
-
size
:
14
px
;
line
-
height
:
14
px
;
margin
-
left
:
6
px
;
}
.
icon
{
width
:
16
px
;
height
:
16
px
;
font
-
size
:
0
;
img
{
width
:
100
%
;
height
:
100
%
;
}
}
}
}
.
footer
-
item1
{
color
:
rgba
(
22
,
119
,
255
,
1
);
.
footer
-
item
-
top
{
background
:
rgba
(
231
,
243
,
255
,
1
);
}
}
.
footer
-
item2
{
color
:
rgba
(
19
,
168
,
168
,
1
);
.
footer
-
item
-
top
{
background
:
rgba
(
225
,
255
,
251
,
1
);
}
}
.
footer
-
item3
{
color
:
rgba
(
146
,
84
,
222
,
1
);
.
footer
-
item
-
top
{
background
:
rgba
(
246
,
235
,
255
,
1
);
}
}
}
}
<
/style>
\ No newline at end of file
<
/style
>
src/views/exportControl/index.vue
浏览文件 @
d85c3e2c
...
...
@@ -1453,7 +1453,7 @@ watch(
// 获取实体清单数据
const
fetchEntitiesList
=
async
(
page
=
1
,
size
=
10
)
=>
{
try
{
const
res
=
await
getEntitiesList
(
"实体清单"
,
page
,
size
);
const
res
=
await
getEntitiesList
(
activeResourceTabItem
.
value
.
id
,
page
,
size
);
if
(
res
)
{
entitiesList
.
value
=
res
.
content
.
map
(
item
=>
({
...
item
,
...
...
src/views/exportControl/v2.0CommercialControlList/components/sanctionsOverview/components/introductionPage/index.vue
浏览文件 @
d85c3e2c
...
...
@@ -407,7 +407,7 @@ const CCLInfo = ref({
});
const
getCCLInfoFn
=
async
()
=>
{
try
{
const
res
=
await
getCCLInfo
();
const
res
=
await
getCCLInfo
(
route
.
query
.
sanTypeId
||
13
);
if
(
res
&&
res
.
code
===
200
)
{
CCLInfo
.
value
=
res
.
data
;
console
.
log
(
"getCCLInfoFn"
,
CCLInfo
.
value
);
...
...
src/views/exportControl/v2.0CommercialControlList/components/sanctionsOverview/components/listPage/index.vue
浏览文件 @
d85c3e2c
...
...
@@ -18,16 +18,26 @@
<div
class=
"text"
>
科技领域
</div>
</div>
<div
class=
"checkbox-group"
>
<el-checkbox
v-for=
"(item, index) in techFields"
:key=
"index"
v-model=
"item.checked"
:label=
"item.name"
@
change=
"handleFilterChange(item, techFields, 'tech')"
/>
<el-checkbox
v-for=
"(item, index) in techFields"
:key=
"index"
v-model=
"item.checked"
:label=
"item.name"
@
change=
"handleFilterChange(item, techFields, 'tech')"
/>
</div>
<div
class=
"title"
>
<div
class=
"box"
></div>
<div
class=
"text"
>
管控原因
</div>
</div>
<div
class=
"checkbox-group"
>
<el-checkbox
v-for=
"(item, index) in controlReason"
:key=
"index"
v-model=
"item.checked"
:label=
"item.name"
@
change=
"handleFilterChange(item, controlReason, 'reason')"
/>
<el-checkbox
v-for=
"(item, index) in controlReason"
:key=
"index"
v-model=
"item.checked"
:label=
"item.name"
@
change=
"handleFilterChange(item, controlReason, 'reason')"
/>
</div>
</div>
<div
class=
"right"
>
...
...
@@ -41,7 +51,7 @@
</div>
<div
style=
"width: 100%"
v-if=
"item.isExpand"
>
<div
style=
"width: 100%"
v-for=
"
element,index
in item.cclChildren"
:key=
"index"
>
<div
style=
"width: 100%"
v-for=
"
(element, index)
in item.cclChildren"
:key=
"index"
>
<div
class=
"list-desc"
>
{{
element
.
cclCode
}}
.
{{
element
.
cclTitleZh
}}
</div>
<div
class=
"list-content"
v-for=
"(ele, j) in element.cclChildren"
:key=
"j"
>
...
...
@@ -74,71 +84,80 @@ import { ref, computed, onMounted, watch } from "vue";
import
{
useRouter
}
from
"vue-router"
;
import
{
Search
,
Select
}
from
"@element-plus/icons-vue"
;
import
defaultIcon
from
"../../../../../assets/icons/default-avatar.png"
;
import
{
getECCNCategory
,
getAreaType
,
getControlReason
,
getCclQuery
,
getExportControlList
,
get50PercentEntityCount
}
from
"@/api/exportControlV2.0.js"
import
{
getECCNCategory
,
getAreaType
,
getControlReason
,
getCclQuery
,
getCCLVersionList
,
getExportControlList
,
get50PercentEntityCount
}
from
"@/api/exportControlV2.0.js"
;
const
router
=
useRouter
();
const
currentCCLType
=
ref
(
''
)
const
CCLTypeList
=
ref
([])
const
currentCCLType
=
ref
(
""
);
const
CCLTypeList
=
ref
([])
;
const
getTypeList
=
async
()
=>
{
try
{
const
res
=
await
getECCNCategory
()
const
res
=
await
getECCNCategory
()
;
if
(
res
&&
res
.
code
===
200
)
{
console
.
log
(
'-----getTypeList'
,
res
.
data
)
CCLTypeList
.
value
=
res
.
data
currentCCLType
.
value
=
CCLTypeList
.
value
[
0
].
id
console
.
log
(
"-----getTypeList"
,
res
.
data
);
CCLTypeList
.
value
=
res
.
data
;
currentCCLType
.
value
=
CCLTypeList
.
value
[
0
].
id
;
}
}
catch
(
error
)
{
console
.
error
(
"获取类别字典失败:"
,
error
);
}
}
}
;
const
techFields
=
ref
([])
const
techFields
=
ref
([])
;
const
getTechFields
=
async
()
=>
{
try
{
const
res
=
await
getAreaType
()
const
res
=
await
getAreaType
()
;
if
(
res
&&
res
.
code
===
200
)
{
console
.
log
(
'-----getTechFields'
,
res
.
data
)
techFields
.
value
=
res
.
data
console
.
log
(
"-----getTechFields"
,
res
.
data
);
techFields
.
value
=
res
.
data
;
// 默认选中第一个
techFields
.
value
[
0
].
checked
=
true
techFields
.
value
[
0
].
checked
=
true
;
}
}
catch
(
error
)
{
console
.
error
(
"获取科技领域字典失败:"
,
error
);
}
}
}
;
const
controlReason
=
ref
([])
const
controlReason
=
ref
([])
;
const
getControlReasonList
=
async
()
=>
{
try
{
const
res
=
await
getControlReason
()
const
res
=
await
getControlReason
()
;
if
(
res
&&
res
.
code
===
200
)
{
console
.
log
(
'-----getControlReasonList'
,
res
.
data
)
controlReason
.
value
=
res
.
data
console
.
log
(
"-----getControlReasonList"
,
res
.
data
);
controlReason
.
value
=
res
.
data
;
// 默认选中第一个
controlReason
.
value
[
0
].
checked
=
true
controlReason
.
value
[
0
].
checked
=
true
;
}
}
catch
(
error
)
{
console
.
error
(
"获取管控原因字典失败:"
,
error
);
}
}
}
;
const
searchKeyword
=
ref
(
''
);
const
searchKeyword
=
ref
(
""
);
const
onlyChina
=
ref
(
false
);
const
viewNew
=
ref
(
true
)
const
viewNew
=
ref
(
true
)
;
// 获取ccl清单列表
const
getCclList
=
async
()
=>
{
let
techDomains
=
techFields
.
value
.
filter
(
item
=>
item
.
checked
).
map
(
item
=>
+
item
.
id
)
let
controls
=
controlReason
.
value
.
filter
(
item
=>
item
.
checked
).
map
(
item
=>
+
item
.
id
)
let
techDomains
=
techFields
.
value
.
filter
(
item
=>
item
.
checked
).
map
(
item
=>
+
item
.
id
)
;
let
controls
=
controlReason
.
value
.
filter
(
item
=>
item
.
checked
).
map
(
item
=>
+
item
.
id
)
;
const
params
=
{
categoryCode
:
currentCCLType
.
value
,
techDomainIds
:
techDomains
,
keyword
:
searchKeyword
.
value
||
''
,
keyword
:
searchKeyword
.
value
||
""
,
controlIds
:
controls
,
isLatest
:
viewNew
.
value
}
console
.
log
(
JSON
.
stringify
(
params
))
isLatest
:
viewNew
.
value
,
recordId
:
""
};
console
.
log
(
JSON
.
stringify
(
params
));
try
{
// const res = await getCclQuery(null);
const
res
=
await
getCclQuery
(
params
);
...
...
@@ -207,38 +226,52 @@ const getCclList = async () => {
// })
// })
if
(
res
&&
res
.
code
===
200
)
{
console
.
log
(
'----getCclList'
,
res
.
data
)
cclList
.
value
=
res
.
data
console
.
log
(
"----getCclList"
,
res
.
data
);
cclList
.
value
=
res
.
data
;
// 给数据添加isExpand字段
cclList
.
value
.
forEach
(
(
item
)
=>
{
item
.
isExpand
=
false
item
.
cclChildren
.
forEach
(
(
ele
)
=>
{
ele
.
cclChildren
.
forEach
(
(
i
)
=>
{
i
.
isExpand
=
false
})
})
})
cclList
.
value
.
forEach
(
item
=>
{
item
.
isExpand
=
false
;
item
.
cclChildren
.
forEach
(
ele
=>
{
ele
.
cclChildren
.
forEach
(
i
=>
{
i
.
isExpand
=
false
;
})
;
})
;
})
;
}
}
catch
(
error
)
{
console
.
error
(
"获取ccl清单列表失败:"
,
error
);
}
};
// 商业管制清单-CCL清单列表-清单版本
const
cclVersionList
=
ref
([]);
// 获取清单版本列表
const
getCCLVersionListApi
=
async
()
=>
{
try
{
const
res
=
await
getCCLVersionList
();
if
(
res
&&
res
.
code
===
200
)
{
console
.
log
(
"----getCCLVersionList"
,
res
.
data
);
cclVersionList
.
value
=
res
.
data
;
}
}
catch
(
error
)
{
console
.
error
(
"获取清单版本列表失败:"
,
error
);
}
}
}
;
// 筛选逻辑处理
const
handleFilterChange
=
(
item
,
list
,
type
)
=>
{
console
.
log
(
item
,
list
,
type
)
getCclList
()
console
.
log
(
item
,
list
,
type
)
;
getCclList
()
;
};
watch
(
viewNew
,
(
newValue
)
=>
{
watch
(
viewNew
,
newValue
=>
{
getCclList
();
});
watch
(
currentCCLType
,
(
newValue
)
=>
{
console
.
log
(
newValue
)
getCclList
()
})
watch
(
currentCCLType
,
newValue
=>
{
console
.
log
(
newValue
)
;
getCclList
()
;
})
;
// const searchDebounceTimer = ref(null);
// watch(searchKeyword, () => {
...
...
@@ -250,52 +283,52 @@ watch(currentCCLType, (newValue) => {
// getExportControlListApi();
// }, 300);
// });
watch
(
searchKeyword
,
(
newValue
)
=>
{
console
.
log
(
'-----searchKey'
,
newValue
)
getCclList
()
watch
(
searchKeyword
,
newValue
=>
{
console
.
log
(
"-----searchKey"
,
newValue
);
getCclList
()
;
});
// 模拟清单列表
const
cclList
=
ref
([
{
name
:
'类别 0-核材料、设施和设备、枪支、弹药[以及其他物品]'
,
name
:
"类别 0-核材料、设施和设备、枪支、弹药[以及其他物品]"
,
desc
:
'A."最终产品"、"设备"、"附件"、"附加装置"、"零件"、"组件"和"系统"'
,
isExpand
:
true
,
list
:
[
{
code
:
'0A002'
,
code
:
"0A002"
,
name
:
'发电或推进设备,"专门设计"用于与太空、海洋或移动"核反应堆"一起使用。(这些项目"受 ITAR 管辖。"参见 22 CFR 第 120 至 130 部分。)'
,
isExpand
:
false
,
isDot
:
false
},
{
code
:
'0A501'
,
name
:
'枪支(不包括 0A502 霰弹枪、0A506 半自动步枪、0A507 半自动手枪和 0A508 半自动霰弹枪)及相关商品(不包括在 Eccn 0A509 中列举或以其他方式描述的与半自动相关的商品,用于 Eccn 0A506、0A507 或 0A508)如下(参见受控物品清单)'
,
code
:
"0A501"
,
name
:
"枪支(不包括 0A502 霰弹枪、0A506 半自动步枪、0A507 半自动手枪和 0A508 半自动霰弹枪)及相关商品(不包括在 Eccn 0A509 中列举或以其他方式描述的与半自动相关的商品,用于 Eccn 0A506、0A507 或 0A508)如下(参见受控物品清单)"
,
isExpand
:
true
,
isDot
:
true
},
{
code
:
'0A501'
,
name
:
'枪支(不包括 0A502 霰弹枪、0A506 半自动步枪、0A507 半自动手枪和 0A508 半自动霰弹枪)及相关商品(不包括在 Eccn 0A509 中列举或以其他方式描述的与半自动相关的商品,用于 Eccn 0A506、0A507 或 0A508)如下(参见受控物品清单)'
,
code
:
"0A501"
,
name
:
"枪支(不包括 0A502 霰弹枪、0A506 半自动步枪、0A507 半自动手枪和 0A508 半自动霰弹枪)及相关商品(不包括在 Eccn 0A509 中列举或以其他方式描述的与半自动相关的商品,用于 Eccn 0A506、0A507 或 0A508)如下(参见受控物品清单)"
,
isExpand
:
false
,
isDot
:
true
}
]
}
])
])
;
onMounted
(
async
()
=>
{
// 获取类别字段
await
getTypeList
()
await
getTypeList
()
;
// 获取科技领域字典
await
getTechFields
()
await
getTechFields
()
;
// 获取管控原因字典
await
getControlReasonList
()
await
getControlReasonList
()
;
// 获取ccl清单列表
getCclList
();
// 获取清单版本列表
getCCLVersionListApi
();
});
</
script
>
<
style
scoped
lang=
"scss"
>
...
...
src/views/exportControl/v2.0CommercialControlList/index.vue
浏览文件 @
d85c3e2c
...
...
@@ -17,8 +17,8 @@
class=
"nav-item"
v-for=
"(item, index) in headerNavList"
:key=
"index"
:class=
"
{ active: activeIndex === index }"
@click="
activeIndex = index
"
:class=
"
{ active: activeIndex === index
, disabled: item.disable
}"
@click="
!item.disable
&&
(activeIndex = index)
"
>
<img
:src=
"activeIndex === index ? item.imgActive : item.img"
alt
/>
<span>
{{
item
.
title
}}
</span>
...
...
@@ -81,17 +81,20 @@ const headerNavList = ref([
{
img
:
icon5
,
imgActive
:
icon5Active
,
title
:
"数据统计"
title
:
"数据统计"
,
disable
:
true
},
{
img
:
icon2
,
imgActive
:
icon2Active
,
title
:
"深度挖掘"
title
:
"深度挖掘"
,
disable
:
true
},
{
img
:
icon3
,
imgActive
:
icon3Active
,
title
:
"影响分析"
title
:
"影响分析"
,
disable
:
true
}
]);
</
script
>
...
...
@@ -206,6 +209,11 @@ const headerNavList = ref([
font-weight
:
700
;
}
&
.disabled
{
cursor
:
not
-
allowed
;
color
:
rgb
(
95
,
101
,
108
);
}
.active-line
{
position
:
absolute
;
bottom
:
0
;
...
...
src/views/exportControl/v2.0SingleSanction/components/deepMining/components/RelationGraph-back.vue
0 → 100644
浏览文件 @
d85c3e2c
<
template
>
<div
class=
"relation-graph-wrapper"
>
<div
class=
"graph-controls"
>
<div
v-for=
"item in controlBtns"
:key=
"item.type"
:class=
"['control-btn',
{ 'control-btn-active': currentLayoutType === item.type }]"
@click="handleClickControlBtn(item.type)"
>
<img
:src=
"item.icon"
alt=
""
/>
</div>
</div>
<div
ref=
"containerRef"
class=
"graph-container"
></div>
<div
v-if=
"selectedNode"
class=
"node-popup"
>
<div
class=
"popup-header"
>
<img
:src=
"selectedNode.image || defaultIcon"
alt=
""
class=
"popup-icon"
/>
<div
class=
"popup-title"
>
{{
selectedNode
.
name
}}
</div>
<el-icon
class=
"close-icon"
@
click=
"selectedNode = null"
>
<Close
/>
</el-icon>
</div>
<div
class=
"popup-body"
>
<div
v-if=
"selectedNode.isSanctioned"
class=
"tag-row"
>
<span
class=
"red-dot"
></span>
<span
class=
"red-text"
>
被制裁实体
</span>
</div>
<div
v-if=
"selectedNode.description && selectedNode.description.length > 0"
class=
"desc-row"
>
<p
class=
"desc"
v-for=
"item in selectedNode.description"
:key=
"item"
>
{{
item
}}
</p>
</div>
<p
v-else
class=
"desc"
>
暂无描述
</p>
</div>
</div>
</div>
</
template
>
<
script
setup
>
import
{
ref
,
onMounted
,
onUnmounted
,
watch
,
nextTick
}
from
"vue"
;
import
G6
from
"@antv/g6"
;
import
{
Close
}
from
"@element-plus/icons-vue"
;
import
echartsIcon01
from
"../assets/echartsicon01.png"
;
import
echartsIcon02
from
"../assets/echartsicon02.png"
;
import
echartsIcon03
from
"../assets/echartsicon03.png"
;
import
defaultIcon
from
"../assets/echartsicon03.png"
;
import
{
getSingleSanctionEntityInfo
}
from
"@/api/exportControlV2.0"
;
const
props
=
defineProps
({
graphData
:
{
type
:
Object
,
default
:
()
=>
({
nodes
:
[],
links
:
[]
})
},
treeData
:
{
type
:
Object
,
default
:
()
=>
null
},
controlActive
:
{
type
:
Number
,
default
:
1
}
});
const
emit
=
defineEmits
([
"nodeClick"
,
"layoutChange"
]);
const
containerRef
=
ref
(
null
);
const
graphInstance
=
ref
(
null
);
const
currentLayoutType
=
ref
(
1
);
const
selectedNode
=
ref
(
null
);
const
controlBtns
=
[
{
type
:
1
,
icon
:
echartsIcon01
,
name
:
"力导向布局"
},
{
type
:
2
,
icon
:
echartsIcon02
,
name
:
"树布局"
},
{
type
:
3
,
icon
:
echartsIcon03
,
name
:
"环状布局"
}
];
const
initGraph
=
(
layoutType
=
1
)
=>
{
if
(
!
containerRef
.
value
)
return
;
destroyGraph
();
nextTick
(()
=>
{
const
container
=
containerRef
.
value
;
const
width
=
container
.
offsetWidth
||
800
;
const
height
=
container
.
offsetHeight
||
600
;
if
(
layoutType
===
2
)
{
initTreeGraph
(
width
,
height
);
}
else
if
(
layoutType
===
3
)
{
initCircularGraph
(
width
,
height
);
}
else
{
initNormalGraph
(
layoutType
,
width
,
height
);
}
});
};
const
initNormalGraph
=
(
layoutType
,
width
,
height
)
=>
{
const
data
=
processGraphData
(
props
.
graphData
);
if
(
!
data
.
nodes
||
data
.
nodes
.
length
===
0
)
return
;
const
layout
=
{
type
:
"force"
,
center
:
[
width
/
2
,
height
/
2
],
preventOverlap
:
true
,
nodeSpacing
:
80
,
linkDistance
:
250
,
nodeStrength
:
-
800
,
edgeStrength
:
0.1
,
collideStrength
:
0.8
,
alphaDecay
:
0.01
,
alphaMin
:
0.001
};
graphInstance
.
value
=
new
G6
.
Graph
({
container
:
containerRef
.
value
,
width
,
height
,
fitView
:
true
,
fitViewPadding
:
100
,
fitCenter
:
true
,
animate
:
true
,
animateCfg
:
{
duration
:
300
,
easing
:
"easeLinear"
},
minZoom
:
0.1
,
maxZoom
:
10
,
modes
:
{
default
:
[
"drag-canvas"
,
"zoom-canvas"
,
"drag-node"
,
{
type
:
"activate-relations"
,
trigger
:
"mouseenter"
,
resetSelected
:
true
}
]
},
layout
,
defaultNode
:
{
type
:
"image"
,
size
:
40
,
clipCfg
:
{
show
:
true
,
type
:
"circle"
,
r
:
20
},
labelCfg
:
{
position
:
"bottom"
,
offset
:
10
,
style
:
{
fill
:
"#333"
,
fontSize
:
11
,
fontFamily
:
"Microsoft YaHei"
,
textAlign
:
"center"
,
background
:
{
fill
:
"rgba(255, 255, 255, 0.95)"
,
padding
:
[
4
,
6
,
4
,
6
],
radius
:
4
}
}
}
},
defaultEdge
:
{
type
:
"quadratic"
,
style
:
{
stroke
:
"#5B8FF9"
,
lineWidth
:
3
,
opacity
:
0.9
,
// 添加边的边框效果
strokeOpacity
:
1
,
endArrow
:
{
path
:
"M 0,0 L 12,6 L 12,-6 Z"
,
fill
:
"#5B8FF9"
}
},
labelCfg
:
{
autoRotate
:
true
,
style
:
{
fill
:
"#333"
,
fontSize
:
10
,
fontFamily
:
"Microsoft YaHei"
,
background
:
{
fill
:
"#fff"
,
padding
:
[
2
,
4
,
2
,
4
],
radius
:
2
}
}
}
},
nodeStateStyles
:
{
active
:
{
shadowColor
:
"#1459BB"
,
shadowBlur
:
15
,
stroke
:
"#1459BB"
,
lineWidth
:
3
},
inactive
:
{
opacity
:
0.3
}
},
edgeStateStyles
:
{
active
:
{
stroke
:
"#1459BB"
,
lineWidth
:
4
},
inactive
:
{
opacity
:
0.15
}
}
});
graphInstance
.
value
.
data
(
data
);
graphInstance
.
value
.
render
();
bindGraphEvents
();
};
const
initCircularGraph
=
(
width
,
height
)
=>
{
const
data
=
processGraphData
(
props
.
graphData
);
if
(
!
data
.
nodes
||
data
.
nodes
.
length
===
0
)
return
;
const
centerX
=
width
/
2
;
const
centerY
=
height
/
2
;
const
radius
=
Math
.
min
(
width
,
height
)
/
2
-
120
;
const
otherNodes
=
data
.
nodes
.
filter
(
n
=>
!
n
.
isCenter
);
const
nodeCount
=
otherNodes
.
length
;
otherNodes
.
forEach
((
node
,
index
)
=>
{
const
angle
=
(
2
*
Math
.
PI
*
index
)
/
nodeCount
-
Math
.
PI
/
2
;
node
.
x
=
centerX
+
radius
*
Math
.
cos
(
angle
);
node
.
y
=
centerY
+
radius
*
Math
.
sin
(
angle
);
});
const
centerNode
=
data
.
nodes
.
find
(
n
=>
n
.
isCenter
);
if
(
centerNode
)
{
centerNode
.
x
=
centerX
;
centerNode
.
y
=
centerY
;
centerNode
.
fx
=
centerX
;
centerNode
.
fy
=
centerY
;
}
graphInstance
.
value
=
new
G6
.
Graph
({
container
:
containerRef
.
value
,
width
,
height
,
fitView
:
false
,
fitCenter
:
false
,
animate
:
true
,
animateCfg
:
{
duration
:
300
,
easing
:
"easeLinear"
},
minZoom
:
0.1
,
maxZoom
:
10
,
modes
:
{
default
:
[
"drag-canvas"
,
"zoom-canvas"
,
"drag-node"
,
{
type
:
"activate-relations"
,
trigger
:
"mouseenter"
,
resetSelected
:
true
}
]
},
defaultNode
:
{
type
:
"image"
,
size
:
40
,
clipCfg
:
{
show
:
true
,
type
:
"circle"
,
r
:
20
},
labelCfg
:
{
position
:
"bottom"
,
offset
:
10
,
style
:
{
fill
:
"#333"
,
fontSize
:
11
,
fontFamily
:
"Microsoft YaHei"
,
textAlign
:
"center"
,
background
:
{
fill
:
"rgba(255, 255, 255, 0.95)"
,
padding
:
[
4
,
6
,
4
,
6
],
radius
:
4
}
}
}
},
defaultEdge
:
{
type
:
"quadratic"
,
style
:
{
stroke
:
"#5B8FF9"
,
lineWidth
:
3
,
opacity
:
0.9
,
endArrow
:
{
path
:
"M 0,0 L 12,6 L 12,-6 Z"
,
fill
:
"#5B8FF9"
}
},
labelCfg
:
{
autoRotate
:
true
,
style
:
{
fill
:
"#333"
,
fontSize
:
10
,
fontFamily
:
"Microsoft YaHei"
,
background
:
{
fill
:
"#fff"
,
padding
:
[
2
,
4
,
2
,
4
],
radius
:
2
}
}
}
},
nodeStateStyles
:
{
active
:
{
shadowColor
:
"#1459BB"
,
shadowBlur
:
15
,
stroke
:
"#1459BB"
,
lineWidth
:
3
},
inactive
:
{
opacity
:
0.3
}
},
edgeStateStyles
:
{
active
:
{
stroke
:
"#1459BB"
,
lineWidth
:
4
},
inactive
:
{
opacity
:
0.15
}
}
});
graphInstance
.
value
.
data
(
data
);
graphInstance
.
value
.
render
();
bindGraphEvents
();
};
const
initTreeGraph
=
(
width
,
height
)
=>
{
const
treeDataSource
=
convertGraphToTree
(
props
.
graphData
);
if
(
!
treeDataSource
)
return
;
graphInstance
.
value
=
new
G6
.
TreeGraph
({
container
:
containerRef
.
value
,
width
,
height
,
fitView
:
true
,
fitViewPadding
:
80
,
animate
:
true
,
animateCfg
:
{
duration
:
300
,
easing
:
"easeLinear"
},
minZoom
:
0.1
,
maxZoom
:
10
,
modes
:
{
default
:
[
"drag-canvas"
,
"zoom-canvas"
,
"drag-node"
,
{
type
:
"collapse-expand"
,
onChange
:
function
onChange
(
item
,
collapsed
)
{
const
data
=
item
.
getModel
();
data
.
collapsed
=
collapsed
;
return
true
;
}
}
]
},
layout
:
{
type
:
"compactBox"
,
direction
:
"LR"
,
getId
:
function
getId
(
d
)
{
return
d
.
id
;
},
getHeight
:
function
getHeight
()
{
return
16
;
},
getWidth
:
function
getWidth
()
{
return
16
;
},
getVGap
:
function
getVGap
()
{
return
30
;
},
getHGap
:
function
getHGap
()
{
return
120
;
}
},
defaultNode
:
{
type
:
"image"
,
size
:
40
,
clipCfg
:
{
show
:
true
,
type
:
"circle"
,
r
:
20
},
labelCfg
:
{
position
:
"right"
,
offset
:
10
,
style
:
{
fill
:
"#333"
,
fontSize
:
11
,
fontFamily
:
"Microsoft YaHei"
,
background
:
{
fill
:
"rgba(255, 255, 255, 0.95)"
,
padding
:
[
4
,
6
,
4
,
6
],
radius
:
4
}
}
}
},
defaultEdge
:
{
type
:
"cubic-horizontal"
,
style
:
{
stroke
:
"#5B8FF9"
,
lineWidth
:
3
}
},
nodeStateStyles
:
{
active
:
{
shadowColor
:
"#1459BB"
,
shadowBlur
:
15
,
stroke
:
"#1459BB"
,
lineWidth
:
3
}
}
});
graphInstance
.
value
.
data
(
treeDataSource
);
graphInstance
.
value
.
render
();
graphInstance
.
value
.
fitView
();
bindGraphEvents
();
};
const
convertGraphToTree
=
graphData
=>
{
if
(
!
graphData
||
!
graphData
.
nodes
||
graphData
.
nodes
.
length
===
0
)
{
return
null
;
}
const
nodes
=
graphData
.
nodes
;
const
links
=
graphData
.
links
||
graphData
.
edges
||
[];
const
centerNode
=
nodes
[
0
];
const
centerId
=
String
(
centerNode
.
id
||
"0"
);
const
childIdSet
=
new
Set
();
const
childrenNodes
=
[];
links
.
forEach
(
link
=>
{
const
source
=
String
(
link
.
source
);
const
target
=
String
(
link
.
target
);
if
(
source
===
centerId
&&
!
childIdSet
.
has
(
target
))
{
const
node
=
nodes
.
find
(
n
=>
String
(
n
.
id
)
===
target
);
if
(
node
)
{
childIdSet
.
add
(
target
);
childrenNodes
.
push
({
id
:
target
,
label
:
node
.
name
||
""
,
img
:
node
.
image
||
defaultIcon
,
size
:
node
.
symbolSize
||
40
,
name
:
node
.
name
,
image
:
node
.
image
,
isSanctioned
:
node
.
isSanctioned
});
}
}
else
if
(
target
===
centerId
&&
!
childIdSet
.
has
(
source
))
{
const
node
=
nodes
.
find
(
n
=>
String
(
n
.
id
)
===
source
);
if
(
node
)
{
childIdSet
.
add
(
source
);
childrenNodes
.
push
({
id
:
source
,
label
:
node
.
name
||
""
,
img
:
node
.
image
||
defaultIcon
,
size
:
node
.
symbolSize
||
40
,
name
:
node
.
name
,
image
:
node
.
image
,
isSanctioned
:
node
.
isSanctioned
});
}
}
});
if
(
childrenNodes
.
length
===
0
)
{
nodes
.
slice
(
1
).
forEach
(
node
=>
{
const
nodeId
=
String
(
node
.
id
);
if
(
!
childIdSet
.
has
(
nodeId
))
{
childIdSet
.
add
(
nodeId
);
childrenNodes
.
push
({
id
:
nodeId
,
label
:
node
.
name
||
""
,
img
:
node
.
image
||
defaultIcon
,
size
:
node
.
symbolSize
||
40
,
name
:
node
.
name
,
image
:
node
.
image
,
isSanctioned
:
node
.
isSanctioned
});
}
});
}
return
{
id
:
centerId
,
label
:
centerNode
.
name
||
""
,
img
:
centerNode
.
image
||
defaultIcon
,
size
:
centerNode
.
symbolSize
||
60
,
name
:
centerNode
.
name
,
image
:
centerNode
.
image
,
isSanctioned
:
centerNode
.
isSanctioned
,
children
:
childrenNodes
};
};
const
processGraphData
=
rawData
=>
{
if
(
!
rawData
||
!
rawData
.
nodes
||
rawData
.
nodes
.
length
===
0
)
{
return
{
nodes
:
[],
edges
:
[]
};
}
const
nodeMap
=
new
Map
();
const
nodes
=
[];
rawData
.
nodes
.
forEach
((
node
,
index
)
=>
{
const
nodeId
=
String
(
node
.
id
||
index
);
if
(
nodeMap
.
has
(
nodeId
))
{
return
;
}
nodeMap
.
set
(
nodeId
,
true
);
const
isCenter
=
index
===
0
;
const
size
=
node
.
symbolSize
||
(
isCenter
?
60
:
40
);
nodes
.
push
({
id
:
nodeId
,
label
:
node
.
name
||
""
,
img
:
node
.
image
||
defaultIcon
,
size
,
isCenter
,
clipCfg
:
{
show
:
true
,
type
:
"circle"
,
r
:
size
/
2
},
style
:
{
cursor
:
"pointer"
},
labelCfg
:
{
position
:
"bottom"
,
offset
:
12
,
style
:
{
fill
:
isCenter
?
"#1459BB"
:
"#333"
,
fontSize
:
isCenter
?
13
:
11
,
fontWeight
:
isCenter
?
"bold"
:
"normal"
,
fontFamily
:
"Microsoft YaHei"
,
textAlign
:
"center"
}
},
...
node
,
id
:
nodeId
});
});
const
edgeMap
=
new
Map
();
const
edges
=
[];
const
rawEdges
=
rawData
.
links
||
rawData
.
edges
||
[];
rawEdges
.
forEach
((
edge
,
index
)
=>
{
const
source
=
String
(
edge
.
source
);
const
target
=
String
(
edge
.
target
);
const
edgeKey
=
`
${
source
}
-
${
target
}
`
;
if
(
edgeMap
.
has
(
edgeKey
))
{
return
;
}
if
(
!
nodeMap
.
has
(
source
)
||
!
nodeMap
.
has
(
target
))
{
return
;
}
edgeMap
.
set
(
edgeKey
,
true
);
edges
.
push
({
id
:
`edge-
${
index
}
`
,
source
,
target
,
label
:
edge
.
name
||
""
});
});
return
{
nodes
,
edges
};
};
const
bindGraphEvents
=
()
=>
{
if
(
!
graphInstance
.
value
)
return
;
graphInstance
.
value
.
on
(
"node:click"
,
evt
=>
{
const
node
=
evt
.
item
;
const
model
=
node
.
getModel
();
console
.
log
(
"点击节点model"
,
model
);
const
id
=
model
.
id
.
split
(
"-"
)[
1
];
let
desList
=
[];
getSingleSanctionEntityInfo
(
id
).
then
(
res
=>
{
console
.
log
(
"制裁实体信息"
,
res
);
if
(
res
.
data
.
sanInfoList
&&
res
.
data
.
sanInfoList
.
length
>
0
)
{
desList
=
res
.
data
.
sanInfoList
.
map
(
item
=>
item
.
sanReason
);
}
});
selectedNode
.
value
=
{
...
model
,
description
:
desList
};
emit
(
"nodeClick"
,
model
);
});
graphInstance
.
value
.
on
(
"canvas:click"
,
()
=>
{
selectedNode
.
value
=
null
;
});
};
const
handleClickControlBtn
=
btn
=>
{
currentLayoutType
.
value
=
btn
;
emit
(
"layoutChange"
,
btn
);
initGraph
(
btn
);
};
const
destroyGraph
=
()
=>
{
if
(
graphInstance
.
value
)
{
graphInstance
.
value
.
destroy
();
graphInstance
.
value
=
null
;
}
};
const
handleResize
=
()
=>
{
if
(
graphInstance
.
value
&&
containerRef
.
value
)
{
const
width
=
containerRef
.
value
.
offsetWidth
;
const
height
=
containerRef
.
value
.
offsetHeight
;
graphInstance
.
value
.
changeSize
(
width
,
height
);
graphInstance
.
value
.
fitView
();
}
};
watch
(
()
=>
props
.
graphData
,
()
=>
{
initGraph
(
currentLayoutType
.
value
);
},
{
deep
:
true
}
);
watch
(
()
=>
props
.
treeData
,
()
=>
{
if
(
currentLayoutType
.
value
===
2
)
{
initGraph
(
2
);
}
},
{
deep
:
true
}
);
watch
(
()
=>
props
.
controlActive
,
newVal
=>
{
if
(
newVal
!==
currentLayoutType
.
value
)
{
handleClickControlBtn
(
newVal
);
}
}
);
onMounted
(()
=>
{
initGraph
(
1
);
window
.
addEventListener
(
"resize"
,
handleResize
);
});
onUnmounted
(()
=>
{
window
.
removeEventListener
(
"resize"
,
handleResize
);
destroyGraph
();
});
defineExpose
({
refresh
:
()
=>
initGraph
(
currentLayoutType
.
value
),
changeLayout
:
type
=>
handleClickControlBtn
(
type
),
getGraph
:
()
=>
graphInstance
.
value
});
</
script
>
<
style
lang=
"scss"
scoped
>
.relation-graph-wrapper
{
position
:
relative
;
width
:
100%
;
height
:
100%
;
}
.graph-container
{
width
:
100%
;
height
:
100%
;
}
.graph-controls
{
position
:
absolute
;
top
:
16px
;
right
:
16px
;
display
:
flex
;
gap
:
8px
;
z-index
:
10
;
.control-btn
{
width
:
32px
;
height
:
32px
;
border-radius
:
4px
;
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
background
:
rgba
(
255
,
255
,
255
,
1
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
cursor
:
pointer
;
img
{
width
:
16px
;
height
:
16px
;
}
&
:hover
{
border-color
:
rgba
(
5
,
95
,
194
,
0
.5
);
}
}
.control-btn-active
{
border-color
:
rgba
(
5
,
95
,
194
,
1
);
background
:
rgba
(
231
,
243
,
255
,
1
);
}
}
.node-popup
{
position
:
absolute
;
bottom
:
16px
;
left
:
16px
;
width
:
320px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
border-radius
:
8px
;
box-shadow
:
0px
4px
16px
rgba
(
0
,
0
,
0
,
0
.1
);
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
z-index
:
20
;
.popup-header
{
display
:
flex
;
align-items
:
center
;
padding
:
12px
16px
;
border-bottom
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
.popup-icon
{
width
:
32px
;
height
:
32px
;
margin-right
:
8px
;
border-radius
:
50%
;
object-fit
:
cover
;
}
.popup-title
{
flex
:
1
;
font-size
:
16px
;
font-weight
:
700
;
font-family
:
"Microsoft YaHei"
;
color
:
rgba
(
59
,
65
,
75
,
1
);
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
}
.close-icon
{
cursor
:
pointer
;
color
:
rgba
(
132
,
136
,
142
,
1
);
font-size
:
16px
;
&
:hover
{
color
:
rgba
(
5
,
95
,
194
,
1
);
}
}
}
.popup-body
{
padding
:
12px
16px
;
.tag-row
{
display
:
flex
;
align-items
:
center
;
margin-bottom
:
8px
;
.red-dot
{
width
:
6px
;
height
:
6px
;
border-radius
:
50%
;
background
:
rgba
(
245
,
63
,
63
,
1
);
margin-right
:
8px
;
}
.red-text
{
font-size
:
14px
;
font-family
:
"Microsoft YaHei"
;
color
:
rgba
(
245
,
63
,
63
,
1
);
}
}
.desc
{
font-size
:
14px
;
font-family
:
"Microsoft YaHei"
;
line-height
:
22px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
margin
:
0
;
}
}
}
</
style
>
src/views/exportControl/v2.0SingleSanction/components/deepMining/components/RelationGraph.vue
浏览文件 @
d85c3e2c
<
template
>
<div
class=
"relation-graph-wrapper"
>
<div
class=
"graph-controls"
>
<div
v-for=
"item in controlBtns"
:key=
"item.type"
:class=
"['control-btn',
{ 'control-btn-active': currentLayoutType === item.type }]"
@click="handleClickControlBtn(item.type)">
<img
:src=
"item.icon"
alt=
""
/>
</div>
</div>
<div
ref=
"containerRef"
class=
"graph-container"
></div>
<div
v-if=
"selectedNode"
class=
"node-popup"
>
<div
class=
"popup-header"
>
<img
:src=
"selectedNode.image || defaultIcon"
alt=
""
class=
"popup-icon"
/>
<div
class=
"popup-title"
>
{{
selectedNode
.
name
}}
</div>
<el-icon
class=
"close-icon"
@
click=
"selectedNode = null"
>
<Close
/>
</el-icon>
</div>
<div
class=
"popup-body"
>
<div
v-if=
"selectedNode.isSanctioned"
class=
"tag-row"
>
<span
class=
"red-dot"
></span>
<span
class=
"red-text"
>
被制裁实体
</span>
</div>
<p
class=
"desc"
>
{{
selectedNode
.
description
||
'暂无描述'
}}
</p>
</div>
</div>
</div>
<div
class=
"relation-graph-wrapper"
>
<div
class=
"graph-controls"
>
<div
v-for=
"item in controlBtns"
:key=
"item.type"
:class=
"['control-btn',
{ 'control-btn-active': currentLayoutType === item.type }]"
@click="handleClickControlBtn(item.type)"
>
<img
:src=
"item.icon"
alt=
""
/>
</div>
</div>
<div
ref=
"containerRef"
class=
"graph-container"
></div>
<div
v-if=
"selectedNode"
class=
"node-popup"
>
<div
class=
"popup-header"
>
<img
:src=
"selectedNode.image || defaultIcon"
alt=
""
class=
"popup-icon"
/>
<div
class=
"popup-title"
>
{{
selectedNode
.
name
}}
</div>
<el-icon
class=
"close-icon"
@
click=
"selectedNode = null"
>
<Close
/>
</el-icon>
</div>
<div
class=
"popup-body"
>
<div
v-if=
"selectedNode.isSanctioned"
class=
"tag-row"
>
<span
class=
"red-dot"
></span>
<span
class=
"red-text"
>
被制裁实体
</span>
</div>
<div
v-if=
"selectedNode.description && selectedNode.description.length > 0"
class=
"desc-row"
>
<p
class=
"desc"
v-for=
"item in selectedNode.description"
:key=
"item"
>
{{
item
}}
</p>
</div>
<p
v-else
class=
"desc"
>
暂无描述
</p>
</div>
</div>
</div>
</
template
>
<
script
setup
>
import
{
ref
,
onMounted
,
onUnmounted
,
watch
,
nextTick
}
from
'vue'
import
G6
from
'@antv/g6'
import
{
Close
}
from
'@element-plus/icons-vue'
import
echartsIcon01
from
'../assets/echartsicon01.png'
import
echartsIcon02
from
'../assets/echartsicon02.png'
import
echartsIcon03
from
'../assets/echartsicon03.png'
import
defaultIcon
from
'../assets/echartsicon03.png'
import
{
ref
,
onMounted
,
onUnmounted
,
watch
,
nextTick
}
from
"vue"
;
import
G6
from
"@antv/g6"
;
import
{
Close
}
from
"@element-plus/icons-vue"
;
import
echartsIcon01
from
"../assets/echartsicon01.png"
;
import
echartsIcon02
from
"../assets/echartsicon02.png"
;
import
echartsIcon03
from
"../assets/echartsicon03.png"
;
import
defaultIcon
from
"../assets/echartsicon03.png"
;
import
{
getSingleSanctionEntityInfo
}
from
"@/api/exportControlV2.0"
;
const
props
=
defineProps
({
graphData
:
{
type
:
Object
,
default
:
()
=>
({
nodes
:
[],
links
:
[]
})
},
treeData
:
{
type
:
Object
,
default
:
()
=>
null
},
controlActive
:
{
type
:
Number
,
default
:
1
}
})
const
emit
=
defineEmits
([
'nodeClick'
,
'layoutChange'
])
const
containerRef
=
ref
(
null
)
const
graphInstance
=
ref
(
null
)
const
currentLayoutType
=
ref
(
1
)
const
selectedNode
=
ref
(
null
)
graphData
:
{
type
:
Object
,
default
:
()
=>
({
nodes
:
[],
links
:
[]
})
},
treeData
:
{
type
:
Object
,
default
:
()
=>
null
},
controlActive
:
{
type
:
Number
,
default
:
1
}
})
;
const
emit
=
defineEmits
([
"nodeClick"
,
"layoutChange"
]);
const
containerRef
=
ref
(
null
)
;
const
graphInstance
=
ref
(
null
)
;
const
currentLayoutType
=
ref
(
1
)
;
const
selectedNode
=
ref
(
null
)
;
const
controlBtns
=
[
{
type
:
1
,
icon
:
echartsIcon01
,
name
:
'力导向布局'
},
{
type
:
2
,
icon
:
echartsIcon02
,
name
:
'树布局'
},
{
type
:
3
,
icon
:
echartsIcon03
,
name
:
'环状布局'
}
]
{
type
:
1
,
icon
:
echartsIcon01
,
name
:
"力导向布局"
},
{
type
:
2
,
icon
:
echartsIcon02
,
name
:
"树布局"
},
{
type
:
3
,
icon
:
echartsIcon03
,
name
:
"环状布局"
}
]
;
const
initGraph
=
(
layoutType
=
1
)
=>
{
if
(
!
containerRef
.
value
)
return
destroyGraph
()
nextTick
(()
=>
{
const
container
=
containerRef
.
value
const
width
=
container
.
offsetWidth
||
800
const
height
=
container
.
offsetHeight
||
600
if
(
layoutType
===
2
)
{
initTreeGraph
(
width
,
height
)
}
else
if
(
layoutType
===
3
)
{
initCircularGraph
(
width
,
height
)
}
else
{
initNormalGraph
(
layoutType
,
width
,
height
)
}
})
}
if
(
!
containerRef
.
value
)
return
;
destroyGraph
();
nextTick
(()
=>
{
const
container
=
containerRef
.
value
;
const
width
=
container
.
offsetWidth
||
800
;
const
height
=
container
.
offsetHeight
||
600
;
if
(
layoutType
===
2
)
{
initTreeGraph
(
width
,
height
);
}
else
if
(
layoutType
===
3
)
{
initCircularGraph
(
width
,
height
);
}
else
{
initNormalGraph
(
layoutType
,
width
,
height
);
}
});
}
;
const
initNormalGraph
=
(
layoutType
,
width
,
height
)
=>
{
const
data
=
processGraphData
(
props
.
graphData
)
if
(
!
data
.
nodes
||
data
.
nodes
.
length
===
0
)
return
const
layout
=
{
type
:
'force'
,
center
:
[
width
/
2
,
height
/
2
],
preventOverlap
:
true
,
nodeSpacing
:
80
,
linkDistance
:
250
,
nodeStrength
:
-
800
,
edgeStrength
:
0.1
,
collideStrength
:
0.8
,
alphaDecay
:
0.01
,
alphaMin
:
0.001
}
graphInstance
.
value
=
new
G6
.
Graph
({
container
:
containerRef
.
value
,
width
,
height
,
fitView
:
true
,
fitViewPadding
:
100
,
fitCenter
:
true
,
animate
:
true
,
animateCfg
:
{
duration
:
300
,
easing
:
'easeLinear'
},
minZoom
:
0.1
,
maxZoom
:
10
,
modes
:
{
default
:
[
'drag-canvas'
,
'zoom-canvas'
,
'drag-node'
,
{
type
:
'activate-relations'
,
trigger
:
'mouseenter'
,
resetSelected
:
true
}
]
},
layout
,
defaultNode
:
{
type
:
'image'
,
size
:
40
,
clipCfg
:
{
show
:
true
,
type
:
'circle'
,
r
:
20
},
labelCfg
:
{
position
:
'bottom'
,
offset
:
10
,
style
:
{
fill
:
'#333'
,
fontSize
:
11
,
fontFamily
:
'Microsoft YaHei'
,
textAlign
:
'center'
,
background
:
{
fill
:
'rgba(255, 255, 255, 0.95)'
,
padding
:
[
4
,
6
,
4
,
6
],
radius
:
4
}
}
}
},
defaultEdge
:
{
type
:
'quadratic'
,
style
:
{
stroke
:
'#5B8FF9'
,
lineWidth
:
3
,
opacity
:
0.9
,
endArrow
:
{
path
:
'M 0,0 L 12,6 L 12,-6 Z'
,
fill
:
'#5B8FF9'
}
},
labelCfg
:
{
autoRotate
:
true
,
style
:
{
fill
:
'#333'
,
fontSize
:
10
,
fontFamily
:
'Microsoft YaHei'
,
background
:
{
fill
:
'#fff'
,
padding
:
[
2
,
4
,
2
,
4
],
radius
:
2
}
}
}
},
nodeStateStyles
:
{
active
:
{
shadowColor
:
'#1459BB'
,
shadowBlur
:
15
,
stroke
:
'#1459BB'
,
lineWidth
:
3
},
inactive
:
{
opacity
:
0.3
}
},
edgeStateStyles
:
{
active
:
{
stroke
:
'#1459BB'
,
lineWidth
:
4
},
inactive
:
{
opacity
:
0.15
}
}
})
graphInstance
.
value
.
data
(
data
)
graphInstance
.
value
.
render
()
bindGraphEvents
()
}
const
data
=
processGraphData
(
props
.
graphData
);
if
(
!
data
.
nodes
||
data
.
nodes
.
length
===
0
)
return
;
const
layout
=
{
type
:
"force"
,
center
:
[
width
/
2
,
height
/
2
],
preventOverlap
:
true
,
nodeSpacing
:
80
,
linkDistance
:
250
,
nodeStrength
:
-
800
,
edgeStrength
:
0.1
,
collideStrength
:
0.8
,
alphaDecay
:
0.01
,
alphaMin
:
0.001
};
graphInstance
.
value
=
new
G6
.
Graph
({
container
:
containerRef
.
value
,
width
,
height
,
fitView
:
true
,
fitViewPadding
:
100
,
fitCenter
:
true
,
animate
:
true
,
animateCfg
:
{
duration
:
300
,
easing
:
"easeLinear"
},
minZoom
:
0.1
,
maxZoom
:
10
,
modes
:
{
default
:
[
"drag-canvas"
,
"zoom-canvas"
,
"drag-node"
,
{
type
:
"activate-relations"
,
trigger
:
"mouseenter"
,
resetSelected
:
true
}
]
},
layout
,
defaultNode
:
{
type
:
"image"
,
size
:
40
,
clipCfg
:
{
show
:
true
,
type
:
"circle"
,
r
:
20
},
labelCfg
:
{
position
:
"bottom"
,
offset
:
10
,
style
:
{
fill
:
"#333"
,
fontSize
:
11
,
fontFamily
:
"Microsoft YaHei"
,
textAlign
:
"center"
,
background
:
{
fill
:
"rgba(255, 255, 255, 0.95)"
,
padding
:
[
4
,
6
,
4
,
6
],
radius
:
4
}
}
}
// 注意:节点边框样式在 processGraphData 中单独设置,不在这里设置
},
defaultEdge
:
{
type
:
"quadratic"
,
style
:
{
stroke
:
"#5B8FF9"
,
lineWidth
:
3
,
opacity
:
0.9
,
shadowColor
:
"rgba(231, 243, 255, 1)"
,
shadowBlur
:
4
,
endArrow
:
{
path
:
"M 0,0 L 12,6 L 12,-6 Z"
,
fill
:
"#5B8FF9"
}
},
labelCfg
:
{
autoRotate
:
true
,
style
:
{
fill
:
"#333"
,
fontSize
:
10
,
fontFamily
:
"Microsoft YaHei"
,
background
:
{
fill
:
"#fff"
,
padding
:
[
2
,
4
,
2
,
4
],
radius
:
2
}
}
}
},
nodeStateStyles
:
{
active
:
{
shadowColor
:
"#1459BB"
,
shadowBlur
:
15
,
stroke
:
"#1459BB"
,
lineWidth
:
3
},
inactive
:
{
opacity
:
0.3
}
},
edgeStateStyles
:
{
active
:
{
stroke
:
"#1459BB"
,
lineWidth
:
4
},
inactive
:
{
opacity
:
0.15
}
}
});
graphInstance
.
value
.
data
(
data
);
graphInstance
.
value
.
render
();
bindGraphEvents
();
};
const
initCircularGraph
=
(
width
,
height
)
=>
{
const
data
=
processGraphData
(
props
.
graphData
)
if
(
!
data
.
nodes
||
data
.
nodes
.
length
===
0
)
return
const
centerX
=
width
/
2
const
centerY
=
height
/
2
const
radius
=
Math
.
min
(
width
,
height
)
/
2
-
120
const
otherNodes
=
data
.
nodes
.
filter
(
n
=>
!
n
.
isCenter
)
const
nodeCount
=
otherNodes
.
length
otherNodes
.
forEach
((
node
,
index
)
=>
{
const
angle
=
(
2
*
Math
.
PI
*
index
)
/
nodeCount
-
Math
.
PI
/
2
node
.
x
=
centerX
+
radius
*
Math
.
cos
(
angle
)
node
.
y
=
centerY
+
radius
*
Math
.
sin
(
angle
)
})
const
centerNode
=
data
.
nodes
.
find
(
n
=>
n
.
isCenter
)
if
(
centerNode
)
{
centerNode
.
x
=
centerX
centerNode
.
y
=
centerY
centerNode
.
fx
=
centerX
centerNode
.
fy
=
centerY
}
graphInstance
.
value
=
new
G6
.
Graph
({
container
:
containerRef
.
value
,
width
,
height
,
fitView
:
false
,
fitCenter
:
false
,
animate
:
true
,
animateCfg
:
{
duration
:
300
,
easing
:
'easeLinear'
},
minZoom
:
0.1
,
maxZoom
:
10
,
modes
:
{
default
:
[
'drag-canvas'
,
'zoom-canvas'
,
'drag-node'
,
{
type
:
'activate-relations'
,
trigger
:
'mouseenter'
,
resetSelected
:
true
}
]
},
defaultNode
:
{
type
:
'image'
,
size
:
40
,
clipCfg
:
{
show
:
true
,
type
:
'circle'
,
r
:
20
},
labelCfg
:
{
position
:
'bottom'
,
offset
:
10
,
style
:
{
fill
:
'#333'
,
fontSize
:
11
,
fontFamily
:
'Microsoft YaHei'
,
textAlign
:
'center'
,
background
:
{
fill
:
'rgba(255, 255, 255, 0.95)'
,
padding
:
[
4
,
6
,
4
,
6
],
radius
:
4
}
}
}
},
defaultEdge
:
{
type
:
'quadratic'
,
style
:
{
stroke
:
'#5B8FF9'
,
lineWidth
:
3
,
opacity
:
0.9
,
endArrow
:
{
path
:
'M 0,0 L 12,6 L 12,-6 Z'
,
fill
:
'#5B8FF9'
}
},
labelCfg
:
{
autoRotate
:
true
,
style
:
{
fill
:
'#333'
,
fontSize
:
10
,
fontFamily
:
'Microsoft YaHei'
,
background
:
{
fill
:
'#fff'
,
padding
:
[
2
,
4
,
2
,
4
],
radius
:
2
}
}
}
},
nodeStateStyles
:
{
active
:
{
shadowColor
:
'#1459BB'
,
shadowBlur
:
15
,
stroke
:
'#1459BB'
,
lineWidth
:
3
},
inactive
:
{
opacity
:
0.3
}
},
edgeStateStyles
:
{
active
:
{
stroke
:
'#1459BB'
,
lineWidth
:
4
},
inactive
:
{
opacity
:
0.15
}
}
})
graphInstance
.
value
.
data
(
data
)
graphInstance
.
value
.
render
()
bindGraphEvents
()
}
const
data
=
processGraphData
(
props
.
graphData
);
if
(
!
data
.
nodes
||
data
.
nodes
.
length
===
0
)
return
;
const
centerX
=
width
/
2
;
const
centerY
=
height
/
2
;
const
radius
=
Math
.
min
(
width
,
height
)
/
2
-
120
;
const
otherNodes
=
data
.
nodes
.
filter
(
n
=>
!
n
.
isCenter
);
const
nodeCount
=
otherNodes
.
length
;
otherNodes
.
forEach
((
node
,
index
)
=>
{
const
angle
=
(
2
*
Math
.
PI
*
index
)
/
nodeCount
-
Math
.
PI
/
2
;
node
.
x
=
centerX
+
radius
*
Math
.
cos
(
angle
);
node
.
y
=
centerY
+
radius
*
Math
.
sin
(
angle
);
});
const
centerNode
=
data
.
nodes
.
find
(
n
=>
n
.
isCenter
);
if
(
centerNode
)
{
centerNode
.
x
=
centerX
;
centerNode
.
y
=
centerY
;
centerNode
.
fx
=
centerX
;
centerNode
.
fy
=
centerY
;
}
graphInstance
.
value
=
new
G6
.
Graph
({
container
:
containerRef
.
value
,
width
,
height
,
fitView
:
false
,
fitCenter
:
false
,
animate
:
true
,
animateCfg
:
{
duration
:
300
,
easing
:
"easeLinear"
},
minZoom
:
0.1
,
maxZoom
:
10
,
modes
:
{
default
:
[
"drag-canvas"
,
"zoom-canvas"
,
"drag-node"
,
{
type
:
"activate-relations"
,
trigger
:
"mouseenter"
,
resetSelected
:
true
}
]
},
defaultNode
:
{
type
:
"image"
,
size
:
40
,
clipCfg
:
{
show
:
true
,
type
:
"circle"
,
r
:
20
},
labelCfg
:
{
position
:
"bottom"
,
offset
:
10
,
style
:
{
fill
:
"#333"
,
fontSize
:
11
,
fontFamily
:
"Microsoft YaHei"
,
textAlign
:
"center"
,
background
:
{
fill
:
"rgba(255, 255, 255, 0.95)"
,
padding
:
[
4
,
6
,
4
,
6
],
radius
:
4
}
}
}
},
defaultEdge
:
{
type
:
"quadratic"
,
style
:
{
stroke
:
"#5B8FF9"
,
lineWidth
:
3
,
opacity
:
0.9
,
endArrow
:
{
path
:
"M 0,0 L 12,6 L 12,-6 Z"
,
fill
:
"#5B8FF9"
}
},
labelCfg
:
{
autoRotate
:
true
,
style
:
{
fill
:
"#333"
,
fontSize
:
10
,
fontFamily
:
"Microsoft YaHei"
,
background
:
{
fill
:
"#fff"
,
padding
:
[
2
,
4
,
2
,
4
],
radius
:
2
}
}
}
},
nodeStateStyles
:
{
active
:
{
shadowColor
:
"#1459BB"
,
shadowBlur
:
15
,
stroke
:
"#1459BB"
,
lineWidth
:
3
},
inactive
:
{
opacity
:
0.3
}
},
edgeStateStyles
:
{
active
:
{
stroke
:
"#1459BB"
,
lineWidth
:
4
},
inactive
:
{
opacity
:
0.15
}
}
});
graphInstance
.
value
.
data
(
data
);
graphInstance
.
value
.
render
();
bindGraphEvents
();
}
;
const
initTreeGraph
=
(
width
,
height
)
=>
{
const
treeDataSource
=
convertGraphToTree
(
props
.
graphData
)
if
(
!
treeDataSource
)
return
graphInstance
.
value
=
new
G6
.
TreeGraph
({
container
:
containerRef
.
value
,
width
,
height
,
fitView
:
true
,
fitViewPadding
:
80
,
animate
:
true
,
animateCfg
:
{
duration
:
300
,
easing
:
'easeLinear'
},
minZoom
:
0.1
,
maxZoom
:
10
,
modes
:
{
default
:
[
'drag-canvas'
,
'zoom-canvas'
,
'drag-node'
,
{
type
:
'collapse-expand'
,
onChange
:
function
onChange
(
item
,
collapsed
)
{
const
data
=
item
.
getModel
()
data
.
collapsed
=
collapsed
return
true
}
}
]
},
layout
:
{
type
:
'compactBox'
,
direction
:
'LR'
,
getId
:
function
getId
(
d
)
{
return
d
.
id
},
getHeight
:
function
getHeight
()
{
return
16
},
getWidth
:
function
getWidth
()
{
return
16
},
getVGap
:
function
getVGap
()
{
return
30
},
getHGap
:
function
getHGap
()
{
return
120
}
},
defaultNode
:
{
type
:
'image'
,
size
:
40
,
clipCfg
:
{
show
:
true
,
type
:
'circle'
,
r
:
20
},
labelCfg
:
{
position
:
'right'
,
offset
:
10
,
style
:
{
fill
:
'#333'
,
fontSize
:
11
,
fontFamily
:
'Microsoft YaHei'
,
background
:
{
fill
:
'rgba(255, 255, 255, 0.95)'
,
padding
:
[
4
,
6
,
4
,
6
],
radius
:
4
}
}
}
},
defaultEdge
:
{
type
:
'cubic-horizontal'
,
style
:
{
stroke
:
'#5B8FF9'
,
lineWidth
:
3
}
},
nodeStateStyles
:
{
active
:
{
shadowColor
:
'#1459BB'
,
shadowBlur
:
15
,
stroke
:
'#1459BB'
,
lineWidth
:
3
}
}
})
graphInstance
.
value
.
data
(
treeDataSource
)
graphInstance
.
value
.
render
()
graphInstance
.
value
.
fitView
()
bindGraphEvents
()
}
const
convertGraphToTree
=
(
graphData
)
=>
{
if
(
!
graphData
||
!
graphData
.
nodes
||
graphData
.
nodes
.
length
===
0
)
{
return
null
}
const
nodes
=
graphData
.
nodes
const
links
=
graphData
.
links
||
graphData
.
edges
||
[]
const
centerNode
=
nodes
[
0
]
const
centerId
=
String
(
centerNode
.
id
||
'0'
)
const
childIdSet
=
new
Set
()
const
childrenNodes
=
[]
links
.
forEach
((
link
)
=>
{
const
source
=
String
(
link
.
source
)
const
target
=
String
(
link
.
target
)
if
(
source
===
centerId
&&
!
childIdSet
.
has
(
target
))
{
const
node
=
nodes
.
find
(
n
=>
String
(
n
.
id
)
===
target
)
if
(
node
)
{
childIdSet
.
add
(
target
)
childrenNodes
.
push
({
id
:
target
,
label
:
node
.
name
||
''
,
img
:
node
.
image
||
defaultIcon
,
size
:
node
.
symbolSize
||
40
,
name
:
node
.
name
,
image
:
node
.
image
,
isSanctioned
:
node
.
isSanctioned
})
}
}
else
if
(
target
===
centerId
&&
!
childIdSet
.
has
(
source
))
{
const
node
=
nodes
.
find
(
n
=>
String
(
n
.
id
)
===
source
)
if
(
node
)
{
childIdSet
.
add
(
source
)
childrenNodes
.
push
({
id
:
source
,
label
:
node
.
name
||
''
,
img
:
node
.
image
||
defaultIcon
,
size
:
node
.
symbolSize
||
40
,
name
:
node
.
name
,
image
:
node
.
image
,
isSanctioned
:
node
.
isSanctioned
})
}
}
})
if
(
childrenNodes
.
length
===
0
)
{
nodes
.
slice
(
1
).
forEach
((
node
)
=>
{
const
nodeId
=
String
(
node
.
id
)
if
(
!
childIdSet
.
has
(
nodeId
))
{
childIdSet
.
add
(
nodeId
)
childrenNodes
.
push
({
id
:
nodeId
,
label
:
node
.
name
||
''
,
img
:
node
.
image
||
defaultIcon
,
size
:
node
.
symbolSize
||
40
,
name
:
node
.
name
,
image
:
node
.
image
,
isSanctioned
:
node
.
isSanctioned
})
}
})
}
return
{
id
:
centerId
,
label
:
centerNode
.
name
||
''
,
img
:
centerNode
.
image
||
defaultIcon
,
size
:
centerNode
.
symbolSize
||
60
,
name
:
centerNode
.
name
,
image
:
centerNode
.
image
,
isSanctioned
:
centerNode
.
isSanctioned
,
children
:
childrenNodes
}
}
const
processGraphData
=
(
rawData
)
=>
{
if
(
!
rawData
||
!
rawData
.
nodes
||
rawData
.
nodes
.
length
===
0
)
{
return
{
nodes
:
[],
edges
:
[]
}
}
const
nodeMap
=
new
Map
()
const
nodes
=
[]
rawData
.
nodes
.
forEach
((
node
,
index
)
=>
{
const
nodeId
=
String
(
node
.
id
||
index
)
if
(
nodeMap
.
has
(
nodeId
))
{
return
}
nodeMap
.
set
(
nodeId
,
true
)
const
isCenter
=
index
===
0
const
size
=
node
.
symbolSize
||
(
isCenter
?
60
:
40
)
nodes
.
push
({
id
:
nodeId
,
label
:
node
.
name
||
''
,
img
:
node
.
image
||
defaultIcon
,
size
,
isCenter
,
clipCfg
:
{
show
:
true
,
type
:
'circle'
,
r
:
size
/
2
},
style
:
{
cursor
:
'pointer'
},
labelCfg
:
{
position
:
'bottom'
,
offset
:
12
,
style
:
{
fill
:
isCenter
?
'#1459BB'
:
'#333'
,
fontSize
:
isCenter
?
13
:
11
,
fontWeight
:
isCenter
?
'bold'
:
'normal'
,
fontFamily
:
'Microsoft YaHei'
,
textAlign
:
'center'
}
},
...
node
,
id
:
nodeId
})
})
const
edgeMap
=
new
Map
()
const
edges
=
[]
const
rawEdges
=
rawData
.
links
||
rawData
.
edges
||
[]
rawEdges
.
forEach
((
edge
,
index
)
=>
{
const
source
=
String
(
edge
.
source
)
const
target
=
String
(
edge
.
target
)
const
edgeKey
=
`
${
source
}
-
${
target
}
`
if
(
edgeMap
.
has
(
edgeKey
))
{
return
}
if
(
!
nodeMap
.
has
(
source
)
||
!
nodeMap
.
has
(
target
))
{
return
}
edgeMap
.
set
(
edgeKey
,
true
)
edges
.
push
({
id
:
`edge-
${
index
}
`
,
source
,
target
,
label
:
edge
.
name
||
''
})
})
return
{
nodes
,
edges
}
}
const
treeDataSource
=
convertGraphToTree
(
props
.
graphData
);
if
(
!
treeDataSource
)
return
;
graphInstance
.
value
=
new
G6
.
TreeGraph
({
container
:
containerRef
.
value
,
width
,
height
,
fitView
:
true
,
fitViewPadding
:
80
,
animate
:
true
,
animateCfg
:
{
duration
:
300
,
easing
:
"easeLinear"
},
minZoom
:
0.1
,
maxZoom
:
10
,
modes
:
{
default
:
[
"drag-canvas"
,
"zoom-canvas"
,
"drag-node"
,
{
type
:
"collapse-expand"
,
onChange
:
function
onChange
(
item
,
collapsed
)
{
const
data
=
item
.
getModel
();
data
.
collapsed
=
collapsed
;
return
true
;
}
}
]
},
layout
:
{
type
:
"compactBox"
,
direction
:
"LR"
,
getId
:
function
getId
(
d
)
{
return
d
.
id
;
},
getHeight
:
function
getHeight
()
{
return
16
;
},
getWidth
:
function
getWidth
()
{
return
16
;
},
getVGap
:
function
getVGap
()
{
return
30
;
},
getHGap
:
function
getHGap
()
{
return
120
;
}
},
defaultNode
:
{
type
:
"image"
,
size
:
40
,
clipCfg
:
{
show
:
true
,
type
:
"circle"
,
r
:
20
},
labelCfg
:
{
position
:
"right"
,
offset
:
10
,
style
:
{
fill
:
"#333"
,
fontSize
:
11
,
fontFamily
:
"Microsoft YaHei"
,
background
:
{
fill
:
"rgba(255, 255, 255, 0.95)"
,
padding
:
[
4
,
6
,
4
,
6
],
radius
:
4
}
}
}
},
defaultEdge
:
{
type
:
"cubic-horizontal"
,
style
:
{
stroke
:
"#5B8FF9"
,
lineWidth
:
3
}
},
nodeStateStyles
:
{
active
:
{
shadowColor
:
"#1459BB"
,
shadowBlur
:
15
,
stroke
:
"#1459BB"
,
lineWidth
:
3
}
}
});
graphInstance
.
value
.
data
(
treeDataSource
);
graphInstance
.
value
.
render
();
graphInstance
.
value
.
fitView
();
bindGraphEvents
();
};
const
convertGraphToTree
=
graphData
=>
{
if
(
!
graphData
||
!
graphData
.
nodes
||
graphData
.
nodes
.
length
===
0
)
{
return
null
;
}
const
nodes
=
graphData
.
nodes
;
const
links
=
graphData
.
links
||
graphData
.
edges
||
[];
const
centerNode
=
nodes
[
0
];
const
centerId
=
String
(
centerNode
.
id
||
"0"
);
const
childIdSet
=
new
Set
();
const
childrenNodes
=
[];
links
.
forEach
(
link
=>
{
const
source
=
String
(
link
.
source
);
const
target
=
String
(
link
.
target
);
if
(
source
===
centerId
&&
!
childIdSet
.
has
(
target
))
{
const
node
=
nodes
.
find
(
n
=>
String
(
n
.
id
)
===
target
);
if
(
node
)
{
childIdSet
.
add
(
target
);
childrenNodes
.
push
({
id
:
target
,
label
:
node
.
name
||
""
,
img
:
node
.
image
||
defaultIcon
,
size
:
node
.
symbolSize
||
40
,
name
:
node
.
name
,
image
:
node
.
image
,
isSanctioned
:
node
.
isSanctioned
});
}
}
else
if
(
target
===
centerId
&&
!
childIdSet
.
has
(
source
))
{
const
node
=
nodes
.
find
(
n
=>
String
(
n
.
id
)
===
source
);
if
(
node
)
{
childIdSet
.
add
(
source
);
childrenNodes
.
push
({
id
:
source
,
label
:
node
.
name
||
""
,
img
:
node
.
image
||
defaultIcon
,
size
:
node
.
symbolSize
||
40
,
name
:
node
.
name
,
image
:
node
.
image
,
isSanctioned
:
node
.
isSanctioned
});
}
}
});
if
(
childrenNodes
.
length
===
0
)
{
nodes
.
slice
(
1
).
forEach
(
node
=>
{
const
nodeId
=
String
(
node
.
id
);
if
(
!
childIdSet
.
has
(
nodeId
))
{
childIdSet
.
add
(
nodeId
);
childrenNodes
.
push
({
id
:
nodeId
,
label
:
node
.
name
||
""
,
img
:
node
.
image
||
defaultIcon
,
size
:
node
.
symbolSize
||
40
,
name
:
node
.
name
,
image
:
node
.
image
,
isSanctioned
:
node
.
isSanctioned
});
}
});
}
return
{
id
:
centerId
,
label
:
centerNode
.
name
||
""
,
img
:
centerNode
.
image
||
defaultIcon
,
size
:
centerNode
.
symbolSize
||
60
,
name
:
centerNode
.
name
,
image
:
centerNode
.
image
,
isSanctioned
:
centerNode
.
isSanctioned
,
children
:
childrenNodes
};
};
const
processGraphData
=
rawData
=>
{
if
(
!
rawData
||
!
rawData
.
nodes
||
rawData
.
nodes
.
length
===
0
)
{
return
{
nodes
:
[],
edges
:
[]
};
}
const
nodeMap
=
new
Map
();
const
nodes
=
[];
rawData
.
nodes
.
forEach
((
node
,
index
)
=>
{
const
nodeId
=
String
(
node
.
id
||
index
);
if
(
nodeMap
.
has
(
nodeId
))
{
return
;
}
nodeMap
.
set
(
nodeId
,
true
);
const
isCenter
=
index
===
0
;
const
size
=
node
.
symbolSize
||
(
isCenter
?
60
:
40
);
// 根据 isSanctioned 设置节点边框
const
strokeColor
=
!
node
.
isSanctioned
?
"rgba(231, 243, 255, 1)"
:
"transparent"
;
const
lineWidth
=
!
node
.
isSanctioned
?
10
:
0
;
nodes
.
push
({
id
:
nodeId
,
label
:
node
.
name
||
""
,
img
:
node
.
image
||
defaultIcon
,
size
,
isCenter
,
clipCfg
:
{
show
:
true
,
type
:
"circle"
,
r
:
size
/
2
},
style
:
{
cursor
:
"pointer"
,
stroke
:
strokeColor
,
lineWidth
:
lineWidth
},
labelCfg
:
{
position
:
"bottom"
,
offset
:
12
,
style
:
{
fill
:
isCenter
?
"#1459BB"
:
"#333"
,
fontSize
:
isCenter
?
13
:
11
,
fontWeight
:
isCenter
?
"bold"
:
"normal"
,
fontFamily
:
"Microsoft YaHei"
,
textAlign
:
"center"
}
},
...
node
,
id
:
nodeId
});
});
const
edgeMap
=
new
Map
();
const
edges
=
[];
const
rawEdges
=
rawData
.
links
||
rawData
.
edges
||
[];
rawEdges
.
forEach
((
edge
,
index
)
=>
{
const
source
=
String
(
edge
.
source
);
const
target
=
String
(
edge
.
target
);
const
edgeKey
=
`
${
source
}
-
${
target
}
`
;
if
(
edgeMap
.
has
(
edgeKey
))
{
return
;
}
if
(
!
nodeMap
.
has
(
source
)
||
!
nodeMap
.
has
(
target
))
{
return
;
}
edgeMap
.
set
(
edgeKey
,
true
);
edges
.
push
({
id
:
`edge-
${
index
}
`
,
source
,
target
,
label
:
edge
.
name
||
""
,
// 添加边的边框效果(使用 shadow 模拟)
style
:
{
stroke
:
"#5B8FF9"
,
lineWidth
:
3
,
opacity
:
0.9
,
shadowColor
:
"rgba(231, 243, 255, 1)"
,
shadowBlur
:
4
,
endArrow
:
{
path
:
"M 0,0 L 12,6 L 12,-6 Z"
,
fill
:
"#5B8FF9"
}
}
});
});
return
{
nodes
,
edges
};
};
const
bindGraphEvents
=
()
=>
{
if
(
!
graphInstance
.
value
)
return
graphInstance
.
value
.
on
(
'node:click'
,
(
evt
)
=>
{
const
node
=
evt
.
item
const
model
=
node
.
getModel
()
selectedNode
.
value
=
model
emit
(
'nodeClick'
,
model
)
})
graphInstance
.
value
.
on
(
'canvas:click'
,
()
=>
{
selectedNode
.
value
=
null
})
}
const
handleClickControlBtn
=
(
btn
)
=>
{
currentLayoutType
.
value
=
btn
emit
(
'layoutChange'
,
btn
)
initGraph
(
btn
)
}
if
(
!
graphInstance
.
value
)
return
;
graphInstance
.
value
.
on
(
"node:click"
,
evt
=>
{
const
node
=
evt
.
item
;
const
model
=
node
.
getModel
();
console
.
log
(
"点击节点model"
,
model
);
const
id
=
model
.
id
.
split
(
"-"
)[
1
];
let
desList
=
[];
getSingleSanctionEntityInfo
(
id
).
then
(
res
=>
{
console
.
log
(
"制裁实体信息"
,
res
);
if
(
res
.
data
.
sanInfoList
&&
res
.
data
.
sanInfoList
.
length
>
0
)
{
desList
=
res
.
data
.
sanInfoList
.
map
(
item
=>
item
.
sanReason
);
}
});
selectedNode
.
value
=
{
...
model
,
description
:
desList
};
emit
(
"nodeClick"
,
model
);
});
graphInstance
.
value
.
on
(
"canvas:click"
,
()
=>
{
selectedNode
.
value
=
null
;
});
};
const
handleClickControlBtn
=
btn
=>
{
currentLayoutType
.
value
=
btn
;
emit
(
"layoutChange"
,
btn
);
initGraph
(
btn
);
};
const
destroyGraph
=
()
=>
{
if
(
graphInstance
.
value
)
{
graphInstance
.
value
.
destroy
()
graphInstance
.
value
=
null
}
}
if
(
graphInstance
.
value
)
{
graphInstance
.
value
.
destroy
();
graphInstance
.
value
=
null
;
}
}
;
const
handleResize
=
()
=>
{
if
(
graphInstance
.
value
&&
containerRef
.
value
)
{
const
width
=
containerRef
.
value
.
offsetWidth
const
height
=
containerRef
.
value
.
offsetHeight
graphInstance
.
value
.
changeSize
(
width
,
height
)
graphInstance
.
value
.
fitView
()
}
}
if
(
graphInstance
.
value
&&
containerRef
.
value
)
{
const
width
=
containerRef
.
value
.
offsetWidth
;
const
height
=
containerRef
.
value
.
offsetHeight
;
graphInstance
.
value
.
changeSize
(
width
,
height
);
graphInstance
.
value
.
fitView
();
}
}
;
watch
(
()
=>
props
.
graphData
,
()
=>
{
initGraph
(
currentLayoutType
.
value
)
},
{
deep
:
true
}
)
()
=>
props
.
graphData
,
()
=>
{
initGraph
(
currentLayoutType
.
value
);
},
{
deep
:
true
}
)
;
watch
(
()
=>
props
.
treeData
,
()
=>
{
if
(
currentLayoutType
.
value
===
2
)
{
initGraph
(
2
)
}
},
{
deep
:
true
}
)
()
=>
props
.
treeData
,
()
=>
{
if
(
currentLayoutType
.
value
===
2
)
{
initGraph
(
2
);
}
},
{
deep
:
true
}
)
;
watch
(
()
=>
props
.
controlActive
,
(
newVal
)
=>
{
if
(
newVal
!==
currentLayoutType
.
value
)
{
handleClickControlBtn
(
newVal
)
}
}
)
()
=>
props
.
controlActive
,
newVal
=>
{
if
(
newVal
!==
currentLayoutType
.
value
)
{
handleClickControlBtn
(
newVal
);
}
}
)
;
onMounted
(()
=>
{
initGraph
(
1
)
window
.
addEventListener
(
'resize'
,
handleResize
)
})
initGraph
(
1
);
window
.
addEventListener
(
"resize"
,
handleResize
);
})
;
onUnmounted
(()
=>
{
window
.
removeEventListener
(
'resize'
,
handleResize
)
destroyGraph
()
})
window
.
removeEventListener
(
"resize"
,
handleResize
);
destroyGraph
();
})
;
defineExpose
({
refresh
:
()
=>
initGraph
(
currentLayoutType
.
value
),
changeLayout
:
(
type
)
=>
handleClickControlBtn
(
type
),
getGraph
:
()
=>
graphInstance
.
value
})
refresh
:
()
=>
initGraph
(
currentLayoutType
.
value
),
changeLayout
:
type
=>
handleClickControlBtn
(
type
),
getGraph
:
()
=>
graphInstance
.
value
})
;
</
script
>
<
style
lang=
"scss"
scoped
>
.relation-graph-wrapper
{
position
:
relative
;
width
:
100%
;
height
:
100%
;
position
:
relative
;
width
:
100%
;
height
:
100%
;
}
.graph-container
{
width
:
100%
;
height
:
100%
;
width
:
100%
;
height
:
100%
;
}
.graph-controls
{
position
:
absolute
;
top
:
16px
;
right
:
16px
;
display
:
flex
;
gap
:
8px
;
z-index
:
10
;
.control-btn
{
width
:
32px
;
height
:
32px
;
border-radius
:
4px
;
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
background
:
rgba
(
255
,
255
,
255
,
1
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
cursor
:
pointer
;
img
{
width
:
16px
;
height
:
16px
;
}
&
:hover
{
border-color
:
rgba
(
5
,
95
,
194
,
0
.5
);
}
}
.control-btn-active
{
border-color
:
rgba
(
5
,
95
,
194
,
1
);
background
:
rgba
(
231
,
243
,
255
,
1
);
}
position
:
absolute
;
top
:
16px
;
right
:
16px
;
display
:
flex
;
gap
:
8px
;
z-index
:
10
;
.control-btn
{
width
:
32px
;
height
:
32px
;
border-radius
:
4px
;
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
background
:
rgba
(
255
,
255
,
255
,
1
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
cursor
:
pointer
;
img
{
width
:
16px
;
height
:
16px
;
}
&
:hover
{
border-color
:
rgba
(
5
,
95
,
194
,
0
.5
);
}
}
.control-btn-active
{
border-color
:
rgba
(
5
,
95
,
194
,
1
);
background
:
rgba
(
231
,
243
,
255
,
1
);
}
}
.node-popup
{
position
:
absolute
;
bottom
:
16px
;
left
:
16px
;
width
:
320px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
border-radius
:
8px
;
box-shadow
:
0px
4px
16px
rgba
(
0
,
0
,
0
,
0
.1
);
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
z-index
:
20
;
.popup-header
{
display
:
flex
;
align-items
:
center
;
padding
:
12px
16px
;
border-bottom
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
.popup-icon
{
width
:
32px
;
height
:
32px
;
margin-right
:
8px
;
border-radius
:
50%
;
object-fit
:
cover
;
}
.popup-title
{
flex
:
1
;
font-size
:
16px
;
font-weight
:
700
;
font-family
:
"Microsoft YaHei"
;
color
:
rgba
(
59
,
65
,
75
,
1
);
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
}
.close-icon
{
cursor
:
pointer
;
color
:
rgba
(
132
,
136
,
142
,
1
);
font-size
:
16px
;
&
:hover
{
color
:
rgba
(
5
,
95
,
194
,
1
);
}
}
}
.popup-body
{
padding
:
12px
16px
;
.tag-row
{
display
:
flex
;
align-items
:
center
;
margin-bottom
:
8px
;
.red-dot
{
width
:
6px
;
height
:
6px
;
border-radius
:
50%
;
background
:
rgba
(
245
,
63
,
63
,
1
);
margin-right
:
8px
;
}
.red-text
{
font-size
:
14px
;
font-family
:
"Microsoft YaHei"
;
color
:
rgba
(
245
,
63
,
63
,
1
);
}
}
.desc
{
font-size
:
14px
;
font-family
:
"Microsoft YaHei"
;
line-height
:
22px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
margin
:
0
;
}
}
position
:
absolute
;
bottom
:
16px
;
left
:
16px
;
width
:
320px
;
background
:
rgba
(
255
,
255
,
255
,
1
);
border-radius
:
8px
;
box-shadow
:
0px
4px
16px
rgba
(
0
,
0
,
0
,
0
.1
);
border
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
z-index
:
20
;
.popup-header
{
display
:
flex
;
align-items
:
center
;
padding
:
12px
16px
;
border-bottom
:
1px
solid
rgba
(
234
,
236
,
238
,
1
);
.popup-icon
{
width
:
32px
;
height
:
32px
;
margin-right
:
8px
;
border-radius
:
50%
;
object-fit
:
cover
;
}
.popup-title
{
flex
:
1
;
font-size
:
16px
;
font-weight
:
700
;
font-family
:
"Microsoft YaHei"
;
color
:
rgba
(
59
,
65
,
75
,
1
);
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
}
.close-icon
{
cursor
:
pointer
;
color
:
rgba
(
132
,
136
,
142
,
1
);
font-size
:
16px
;
&
:hover
{
color
:
rgba
(
5
,
95
,
194
,
1
);
}
}
}
.popup-body
{
padding
:
12px
16px
;
.tag-row
{
display
:
flex
;
align-items
:
center
;
margin-bottom
:
8px
;
.red-dot
{
width
:
6px
;
height
:
6px
;
border-radius
:
50%
;
background
:
rgba
(
245
,
63
,
63
,
1
);
margin-right
:
8px
;
}
.red-text
{
font-size
:
14px
;
font-family
:
"Microsoft YaHei"
;
color
:
rgba
(
245
,
63
,
63
,
1
);
}
}
.desc
{
font-size
:
14px
;
font-family
:
"Microsoft YaHei"
;
line-height
:
22px
;
color
:
rgba
(
95
,
101
,
108
,
1
);
margin
:
0
;
}
}
}
</
style
>
\ No newline at end of file
</
style
>
src/views/exportControl/v2.0SingleSanction/components/deepMining/index.vue
浏览文件 @
d85c3e2c
...
...
@@ -130,7 +130,7 @@ import companyActive from "./assets/company-active.png";
import
{
getSingleSanctionEntityList
,
getSingleSanctionEntitySupplyChain
,
getSingleSanctionEntityEquity
getSingleSanctionEntityEquity
,
}
from
"@/api/exportControlV2.0"
;
import
RelationGraph
from
"./components/RelationGraph.vue"
;
import
AnalysisBox
from
"@/components/base/BoxBackground/AnalysisBox.vue"
;
...
...
@@ -888,9 +888,11 @@ onMounted(async () => {
margin-left
:
16px
;
.toggle-btn
{
width
:
108px
;
height
:
100%
;
padding
:
4px
16px
;
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
cursor
:
pointer
;
transition
:
all
0
.3s
;
...
...
src/views/exportControl/v2.0SingleSanction/components/sanctionsOverview/index.vue
浏览文件 @
d85c3e2c
...
...
@@ -90,6 +90,37 @@
<div
class=
"right"
v-loading=
"isLoading"
>
<AnalysisBox
title=
"制裁清单"
:showAllBtn=
"false"
>
<div
class=
"right-title"
>
<div
class=
"filter-row"
>
<div
class=
"filter-left"
>
<el-select
v-model=
"filterEntity"
placeholder=
"受制裁实体"
style=
"width: 184px"
>
<el-option
label=
"受制裁实体"
value=
"2"
/>
<el-option
label=
"受制裁地址"
value=
"7"
/>
<el-option
label=
"受制裁个人"
value=
"1"
/>
</el-select>
</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-option
label=
"全部领域"
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=
"搜索实体"
style=
"width: 150px"
:suffix-icon=
"Search"
/>
</div>
</div>
<div
class=
"stats-row"
>
<div
class=
"tabs"
>
<div
class=
"tab-btn"
:class=
"
{ active: activeTab === 'add' }" @click="activeTab = 'add'">
...
...
@@ -120,35 +151,6 @@
</div>
</div>
</div>
<div
class=
"filter-row"
>
<div
class=
"filter-left"
>
<!--
<el-select
v-model=
"filterEntity"
placeholder=
"受制裁实体"
style=
"width: 184px"
>
<el-option
label=
"受制裁实体"
value=
"1"
/>
</el-select>
-->
</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-option
label=
"全部领域"
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=
"搜索实体"
style=
"width: 150px"
:suffix-icon=
"Search"
/>
</div>
</div>
</div>
<div
class=
"right-content"
>
<div
class=
"sanction-group-list"
>
...
...
@@ -286,7 +288,8 @@ const getSanctionOverviewList = async () => {
isOnlyCn
:
onlyChina
.
value
,
domainId
:
filterField
.
value
||
undefined
,
searchText
:
searchKeyword
.
value
||
undefined
,
searchType
:
searchType
.
value
searchType
:
searchType
.
value
,
entityTypeId
:
filterEntity
.
value
||
undefined
});
isLoading
.
value
=
false
;
if
(
res
.
code
===
200
)
{
...
...
@@ -446,7 +449,7 @@ const formattedData = computed(() => {
};
});
const
filterEntity
=
ref
(
""
);
const
filterEntity
=
ref
(
"
2
"
);
const
onlyChina
=
ref
(
false
);
const
filterField
=
ref
(
""
);
const
searchKeyword
=
ref
(
""
);
...
...
@@ -454,7 +457,7 @@ const activeTab = ref("add");
const
searchType
=
computed
(()
=>
activeTab
.
value
);
// 监听筛选条件变化
watch
([
onlyChina
,
filterField
,
activeTab
],
()
=>
{
watch
([
onlyChina
,
filterField
,
activeTab
,
filterEntity
],
()
=>
{
getSanctionOverviewList
();
});
...
...
@@ -865,9 +868,9 @@ onMounted(() => {
.filter-row
{
display
:
flex
;
justify-content
:
right
;
justify-content
:
space-between
;
align-items
:
center
;
//
margin-bottom: 20px;
margin-bottom
:
20px
;
:deep
(
.el-input__inner
)
{
font-size
:
16px
;
...
...
@@ -897,7 +900,7 @@ onMounted(() => {
.stats-row
{
display
:
flex
;
margin-bottom
:
20px
;
//
margin-bottom: 20px;
justify-content
:
space-between
;
align-items
:
center
;
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论