Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
f04b1b70
提交
f04b1b70
authored
3月 07, 2026
作者:
刘宇琪
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
refactor -刘宇琪 实体清单-深度挖掘-echarts绘图重构为g6绘图
上级
80718eb3
显示空白字符变更
内嵌
并排
正在显示
3 个修改的文件
包含
946 行增加
和
691 行删除
+946
-691
index.vue
src/views/exportControl/index.vue
+1
-1
RelationGraph.vue
...nction/components/deepMining/components/RelationGraph.vue
+801
-0
index.vue
...ontrol/v2.0SingleSanction/components/deepMining/index.vue
+144
-690
没有找到文件。
src/views/exportControl/index.vue
浏览文件 @
f04b1b70
...
...
@@ -598,7 +598,7 @@
</template>
<
script
setup
>
import
NewsList
from
"@/components/
NewsList/NewsList
.vue"
;
import
NewsList
from
"@/components/
base/newsList/index
.vue"
;
import
RiskSignal
from
"@/components/RiskSignal/RiskSignal.vue"
;
import
{
onMounted
,
ref
,
computed
,
reactive
,
shallowRef
,
watch
,
nextTick
}
from
"vue"
;
import
{
useContainerScroll
}
from
"@/hooks/useScrollShow"
;
...
...
src/views/exportControl/v2.0SingleSanction/components/deepMining/components/RelationGraph.vue
0 → 100644
浏览文件 @
f04b1b70
<
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>
</
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'
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
,
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
()
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
)
}
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
>
\ No newline at end of file
src/views/exportControl/v2.0SingleSanction/components/deepMining/index.vue
浏览文件 @
f04b1b70
...
...
@@ -9,63 +9,6 @@
</div>
<div
class=
"main"
v-if=
"activeIndex === 0"
>
<div
class=
"left"
>
<!--
<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=
"left-main"
>
<div
class=
"filter-bar"
>
<el-select
v-model=
"searchDomain"
placeholder=
"全部领域"
class=
"domain-select"
>
<el-option
label=
"全部领域"
value=
""
/>
<el-option
label=
"人工智能"
value=
"1"
/>
<el-option
label=
"生物科技"
value=
"2"
/>
<el-option
label=
"新一代信息技术"
value=
"3"
/>
<el-option
label=
"量子科技"
value=
"4"
/>
<el-option
label=
"新能源"
value=
"5"
/>
<el-option
label=
"集成电路"
value=
"6"
/>
<el-option
label=
"海洋"
value=
"7"
/>
<el-option
label=
"先进制造"
value=
"8"
/>
<el-option
label=
"新材料"
value=
"9"
/>
<el-option
label=
"航空航天"
value=
"10"
/>
<el-option
label=
"深海"
value=
"11"
/>
<el-option
label=
"极地"
value=
"12"
/>
<el-option
label=
"太空"
value=
"13"
/>
<el-option
label=
"核"
value=
"14"
/>
</el-select>
<el-input
v-model=
"searchText"
placeholder=
"搜索实体"
class=
"search-input"
>
<template
#
suffix
>
<el-icon
class=
"el-input__icon"
><Search
/></el-icon>
</
template
>
</el-input>
</div>
<div
class=
"entity-tree custom-scrollbar"
>
<div
class=
"tree-group"
v-for=
"group in entityList"
:key=
"group.id"
>
<div
class=
"group-header"
@
click=
"toggleGroup(group)"
>
<el-icon
class=
"arrow-icon"
:class=
"{ expanded: group.expanded }"
>
<CaretRight
/>
</el-icon>
<span
class=
"group-name"
>
{{ group.name }}
</span>
<span
class=
"group-count"
>
{{ group.count }}家
</span>
</div>
<div
class=
"group-children custom-scrollbar"
v-show=
"group.expanded"
>
<div
class=
"entity-item"
v-for=
"item in group.children"
:key=
"item.id"
:class=
"{ active: activeEntityId === item.id }"
@
click=
"selectEntity(item)"
>
<div
class=
"item-icon"
>
<img
:src=
"defaultTitle"
alt=
""
class=
"item-img"
/>
</div>
<span
class=
"item-name"
>
{{ item.name }}
</span>
</div>
</div>
</div>
</div>
</div>
-->
<AnalysisBox
title=
"本次制裁实体清单列表"
>
<div
class=
"left-main"
>
<div
class=
"filter-bar"
>
...
...
@@ -134,26 +77,6 @@
<div
class=
"rule-checkbox"
v-if=
"rightActiveTab === 'equity'"
>
<el-checkbox
v-model=
"is50PercentRule"
size=
"large"
>
50%规则涉及实体
</el-checkbox>
</div>
<!-- <el-select v-model="filterType" placeholder="全部类型" class="header-select">
<el-option label="全部类型" value="" />
</el-select>
<el-select v-model="filterDomain" placeholder="全部领域" class="header-select last-select">
<el-option label="全部领域" value="" />
<el-option label="人工智能" value="1" />
<el-option label="生物科技" value="2" />
<el-option label="新一代信息技术" value="3" />
<el-option label="量子科技" value="4" />
<el-option label="新能源" value="5" />
<el-option label="集成电路" value="6" />
<el-option label="海洋" value="7" />
<el-option label="先进制造" value="8" />
<el-option label="新材料" value="9" />
<el-option label="航空航天" value="10" />
<el-option label="深海" value="11" />
<el-option label="极地" value="12" />
<el-option label="太空" value="13" />
<el-option label="核" value="14" />
</el-select> -->
<div
class=
"btn"
>
<img
src=
"../../assets/数据库按钮.png"
alt=
""
/>
<img
src=
"../../assets/下载按钮.png"
alt=
""
/>
...
...
@@ -162,50 +85,14 @@
</div>
</div>
<div
class=
"right-echarts"
>
<div
class=
"chart-wrapper"
>
<div
class=
"chart-controls"
>
<div
class=
"control-btn"
:class=
"{ controlBtnActive: controlActive === 1 }"
@
click=
"handleClickControlBtn(1)"
>
<img
:src=
"echartsIcon01"
/>
</div>
<div
class=
"control-btn"
:class=
"{ controlBtnActive: controlActive === 2 }"
@
click=
"handleClickControlBtn(2)"
>
<img
:src=
"echartsIcon02"
/>
</div>
<div
class=
"control-btn"
:class=
"{ controlBtnActive: controlActive === 3 }"
@
click=
"handleClickControlBtn(3)"
>
<img
:src=
"echartsIcon03"
/>
</div>
</div>
<div
class=
"chart-legend"
>
<div
class=
"legend-item"
><span
class=
"dot blue"
></span>
已被制裁实体
</div>
<div
class=
"legend-item"
><span
class=
"dot grey"
></span>
未被制裁实体
</div>
</div>
<div
ref=
"chartRef"
id=
"chartbox"
class=
"chart-container"
></div>
<div
class=
"node-popup"
v-if=
"selectedNode"
>
<div
class=
"popup-header"
>
<img
:src=
"defaultTitle"
class=
"popup-icon"
/>
<span
class=
"popup-title"
>
{{ selectedNode.name.replace(/\n/g, "") }}
</span>
<el-icon
class=
"close-icon"
@
click=
"selectedNode = null"
>
<Close
/>
</el-icon>
</div>
<div
class=
"popup-body"
>
<div
class=
"tag-row"
>
<span
class=
"red-dot"
></span>
<span
class=
"red-text"
>
<!-- 2025年7月15日 《实体清单》 -->
暂无数据
</span>
</div>
<div
class=
"desc"
>
<!-- 因获取和试图获取美国原产物品以支持中国军事和国防相关空间领域活动以及中国量子技术能力而被列入。 -->
暂无数据
</div>
</div>
</div>
</div>
<RelationGraph
ref=
"relationGraphRef"
:graph-data=
"graphData"
:tree-data=
"treeData"
:control-active=
"controlActive"
@
node-click=
"handleNodeClick"
@
layout-change=
"handleLayoutChange"
/>
</div>
</div>
</div>
...
...
@@ -215,16 +102,12 @@
<
script
setup
>
import
{
ref
,
onMounted
,
nextTick
,
watch
,
onUnmounted
}
from
"vue"
;
import
{
debounce
}
from
"lodash"
;
import
*
as
echarts
from
"echarts"
;
import
{
Search
,
CaretRight
,
Close
}
from
"@element-plus/icons-vue"
;
import
defaultTitle
from
"../../assets/default-icon2.png"
;
import
icon01
from
"./assets/icon01.png"
;
import
icon02
from
"./assets/icon02.png"
;
import
icon01Active
from
"./assets/icon01-active.png"
;
import
icon02Active
from
"./assets/icon02-active.png"
;
import
echartsIcon01
from
"./assets/echartsIcon01.png"
;
import
echartsIcon02
from
"./assets/echartsIcon02.png"
;
import
echartsIcon03
from
"./assets/echartsIcon03.png"
;
import
company
from
"./assets/company.png"
;
import
companyActive
from
"./assets/company-active.png"
;
import
{
...
...
@@ -232,137 +115,10 @@ import {
getSingleSanctionEntitySupplyChain
,
getSingleSanctionEntityEquity
}
from
"@/api/exportControlV2.0"
;
import
getTreeChart
from
"./utils/treeChart"
;
import
setChart
from
"@/utils/setChart"
;
const
controlActive
=
ref
(
1
);
const
treeData
=
ref
([{}]);
const
handleClickControlBtn
=
btn
=>
{
controlActive
.
value
=
btn
;
if
(
btn
===
1
||
btn
===
3
)
{
isInChart
.
value
=
true
;
initChart
();
}
else
if
(
btn
===
2
)
{
isInChart
.
value
=
false
;
if
(
chartInstance
.
value
)
{
chartInstance
.
value
.
dispose
();
}
console
.
log
(
"treeData"
,
treeData
.
value
);
let
treeChart
=
getTreeChart
(
treeData
.
value
);
setChart
(
treeChart
,
"chartbox"
);
}
};
const
isInChart
=
ref
(
false
);
const
handleMouseEnter
=
()
=>
{
if
(
controlActive
.
value
!==
2
)
{
isInChart
.
value
=
true
;
}
else
{
isInChart
.
value
=
false
;
}
};
const
handleMouseLeave
=
()
=>
{
isInChart
.
value
=
false
;
};
// 单次制裁-深度挖掘-制裁实体股权信息-列表
const
singleSanctionEntityEquityData
=
ref
(
null
);
// 单次制裁-深度挖掘-制裁实体股权信息-请求
const
getSingleSanctionEntityEquityRequest
=
async
()
=>
{
try
{
const
res
=
await
getSingleSanctionEntityEquity
({
orgId
:
activeEntityId
.
value
,
rule
:
is50PercentRule
.
value
});
if
(
res
.
code
===
200
)
{
singleSanctionEntityEquityData
.
value
=
res
.
data
||
null
;
initChart
();
}
}
catch
(
error
)
{
console
.
log
(
error
);
}
};
// 单次制裁-深度挖掘-制裁实体供应链信息-列表
const
singleSanctionEntitySupplyChainData
=
ref
(
null
);
// 单次制裁-深度挖掘-制裁实体供应链信息-请求
const
getSingleSanctionEntitySupplyChainRequest
=
async
()
=>
{
try
{
const
res
=
await
getSingleSanctionEntitySupplyChain
({
orgId
:
activeEntityId
.
value
});
if
(
res
.
code
===
200
)
{
singleSanctionEntitySupplyChainData
.
value
=
res
.
data
||
null
;
initChart
();
treeData
.
value
[
0
].
id
=
res
.
data
.
orgId
;
treeData
.
value
[
0
].
name
=
res
.
data
.
orgName
;
treeData
.
value
[
0
].
symbol
=
"image://"
+
companyActive
;
treeData
.
value
[
0
].
symbolSize
=
50
;
treeData
.
value
[
0
].
value
=
10
;
treeData
.
value
[
0
].
children
=
res
.
data
.
parentOrgList
.
map
(
item
=>
{
return
{
id
:
item
.
id
,
name
:
item
.
name
,
symbolSize
:
30
,
value
:
10
,
symbol
:
`image://
${
company
}
`
};
});
console
.
log
(
"treeData0"
,
treeData
.
value
);
}
}
catch
(
error
)
{
console
.
log
(
error
);
}
};
// 单次制裁-深度挖掘-本次制裁实体清单列表
const
singleSanctionEntityList
=
ref
([]);
// 单次制裁-深度挖掘-本次制裁实体清单列表-请求
const
getSingleSanctionEntityListRequest
=
async
()
=>
{
try
{
const
res
=
await
getSingleSanctionEntityList
({
sanRecordId
:
sanRecordId
.
value
,
isOnlyCn
:
false
,
domainId
:
searchDomain
.
value
||
undefined
,
searchText
:
searchText
.
value
||
undefined
});
if
(
res
.
code
===
200
)
{
entityList
.
value
=
(
res
.
data
||
[]).
map
((
group
,
index
)
=>
({
id
:
`group-
${
index
}
`
,
name
:
group
.
orgType
,
count
:
group
.
orgInfoList
?
group
.
orgInfoList
.
length
:
0
,
expanded
:
index
===
0
,
// 默认展开第一个分组
children
:
(
group
.
orgInfoList
||
[]).
map
(
org
=>
({
id
:
org
.
id
,
name
:
org
.
orgNameZh
}))
}));
// 如果有数据,且当前没有选中的实体,默认选中第一个分组的第一个实体
if
(
entityList
.
value
.
length
>
0
&&
entityList
.
value
[
0
].
children
&&
entityList
.
value
[
0
].
children
.
length
>
0
)
{
const
firstEntity
=
entityList
.
value
[
0
].
children
[
0
];
if
(
!
activeEntityId
.
value
)
{
activeEntityId
.
value
=
firstEntity
.
id
;
currentEntityName
.
value
=
firstEntity
.
name
;
}
}
}
}
catch
(
error
)
{
console
.
log
(
error
);
}
};
import
RelationGraph
from
'./components/RelationGraph.vue'
;
import
AnalysisBox
from
'@/components/base/boxBackground/analysisBox.vue'
;
const
sanRecordId
=
ref
(
""
);
const
getUrlParams
=
()
=>
{
const
urlParams
=
new
URLSearchParams
(
window
.
location
.
search
);
sanRecordId
.
value
=
urlParams
.
get
(
"id"
)
||
""
;
};
// const activeTab = ref(["实体穿透分析", "重点实体识别"]);
const
activeTab
=
ref
([
"实体穿透分析"
]);
const
activeIndex
=
ref
(
0
);
const
rightActiveTab
=
ref
(
"supplyChain"
);
...
...
@@ -376,6 +132,22 @@ const currentEntityName = ref("");
const
is50PercentRule
=
ref
(
false
);
const
entityList
=
ref
([]);
const
controlActive
=
ref
(
1
);
const
isInChart
=
ref
(
false
);
const
relationGraphRef
=
ref
(
null
);
const
graphData
=
ref
({
nodes
:
[],
links
:
[]
});
const
treeData
=
ref
(
null
);
const
selectedNode
=
ref
(
null
);
const
singleSanctionEntityEquityData
=
ref
(
null
);
const
singleSanctionEntitySupplyChainData
=
ref
(
null
);
const
singleSanctionEntityList
=
ref
([]);
const
getUrlParams
=
()
=>
{
const
urlParams
=
new
URLSearchParams
(
window
.
location
.
search
);
sanRecordId
.
value
=
urlParams
.
get
(
"id"
)
||
""
;
};
const
toggleGroup
=
group
=>
{
group
.
expanded
=
!
group
.
expanded
;
...
...
@@ -386,512 +158,194 @@ const selectEntity = item => {
currentEntityName
.
value
=
item
.
name
;
};
const
chartRef
=
ref
(
null
);
const
chartInstance
=
ref
(
null
);
const
selectedNode
=
ref
(
null
);
const
initChart
=
()
=>
{
if
(
!
chartRef
.
value
)
return
;
if
(
chartInstance
.
value
)
{
chartInstance
.
value
.
dispose
();
}
chartInstance
.
value
=
echarts
.
init
(
chartRef
.
value
);
let
option
=
{};
if
(
rightActiveTab
.
value
===
"supplyChain"
)
{
option
=
getSupplyChainOption
();
const
handleMouseEnter
=
()
=>
{
if
(
controlActive
.
value
!==
2
)
{
isInChart
.
value
=
true
;
}
else
{
option
=
getEquityOption
()
;
isInChart
.
value
=
false
;
}
};
chartInstance
.
value
.
setOption
(
option
);
const
handleMouseLeave
=
()
=>
{
isInChart
.
value
=
false
;
};
chartInstance
.
value
.
on
(
"click"
,
params
=>
{
if
(
params
.
dataType
===
"node"
)
{
selectedNode
.
value
=
params
.
data
;
}
else
{
selectedNode
.
value
=
null
;
}
});
const
handleNodeClick
=
(
node
)
=>
{
selectedNode
.
value
=
node
;
};
chartInstance
.
value
.
getZr
().
on
(
"click"
,
params
=>
{
if
(
!
params
.
target
)
{
selectedNode
.
value
=
null
;
const
handleLayoutChange
=
(
type
)
=>
{
controlActive
.
value
=
type
;
if
(
type
!==
2
)
{
isInChart
.
value
=
true
;
}
else
{
isInChart
.
value
=
false
;
}
});
};
const
getSupplyChainOption
=
()
=>
{
if
(
!
singleSanctionEntitySupplyChainData
.
value
)
return
{};
const
data
=
singleSanctionEntitySupplyChainData
.
value
;
const
updateGraphData
=
()
=>
{
const
data
=
rightActiveTab
.
value
===
'supplyChain'
?
singleSanctionEntitySupplyChainData
.
value
:
singleSanctionEntityEquityData
.
value
;
if
(
!
data
)
return
;
const
nodes
=
[];
const
links
=
[];
const
centerX
=
550
;
const
centerY
=
400
;
// 中心节点
nodes
.
push
({
id
:
"0"
,
name
:
data
.
orgName
,
category
:
0
,
// 强制为制裁中
symbol
:
"image://"
+
companyActive
,
// 强制使用制裁中图标
x
:
centerX
,
y
:
centerY
,
symbolSize
:
50
,
label
:
{
fontSize
:
16
,
fontWeight
:
"bold"
,
color
:
"#055FC2"
,
// 使用制裁蓝,在图标下方更清晰
position
:
"bottom"
,
distance
:
10
,
width
:
150
,
overflow
:
"break"
}
image
:
companyActive
,
symbolSize
:
60
,
isSanctioned
:
true
});
// 父级节点 (供应商)
const
parentList
=
data
.
parentOrgList
||
[];
parentList
.
forEach
((
item
,
index
)
=>
{
// 使用 (index + 0.5) 使节点在半圆弧内居中分布
const
angle
=
-
Math
.
PI
+
((
index
+
0.5
)
/
parentList
.
length
)
*
Math
.
PI
;
// 交错半径:偶数索引使用 340,奇数索引使用 440,拉开垂直距离
const
radius
=
index
%
2
===
0
?
340
:
440
;
const
x
=
centerX
+
radius
*
Math
.
cos
(
angle
);
const
y
=
centerY
+
radius
*
Math
.
sin
(
angle
);
// 动态计算标签位置:根据余弦值判断左右,根据正弦值判断上下
let
position
=
"right"
;
let
align
=
"left"
;
const
cosA
=
Math
.
cos
(
angle
);
const
sinA
=
Math
.
sin
(
angle
);
if
(
Math
.
abs
(
cosA
)
<
0.3
)
{
// 顶部区域
position
=
"top"
;
align
=
"center"
;
}
else
if
(
cosA
<
0
)
{
// 左侧区域
position
=
"left"
;
align
=
"right"
;
}
nodes
.
push
({
id
:
`p-
${
item
.
id
}
`
,
id
:
`p-
${
item
.
id
||
index
}
`
,
name
:
item
.
name
,
category
:
item
.
isSanctioned
?
0
:
1
,
symbol
:
"image://"
+
(
item
.
isSanctioned
?
companyActive
:
company
),
x
:
x
,
y
:
y
,
isSanctioned
:
item
.
isSanctioned
,
label
:
{
position
:
position
,
align
:
align
,
distance
:
8
,
width
:
110
,
overflow
:
"break"
,
lineHeight
:
14
,
fontSize
:
11
}
image
:
item
.
isSanctioned
?
companyActive
:
company
,
symbolSize
:
40
,
isSanctioned
:
item
.
isSanctioned
});
links
.
push
({
source
:
`p-
${
item
.
id
}
`
,
source
:
`p-
${
item
.
id
||
index
}
`
,
target
:
"0"
,
value
:
"供应商"
,
isSanctioned
:
item
.
isSanctioned
&&
data
.
isSanctioned
name
:
rightActiveTab
.
value
===
'supplyChain'
?
"供应商"
:
(
item
.
type
||
"持股"
)
});
});
// 子级节点 (客户)
const
childList
=
data
.
childrenOrgList
||
[];
childList
.
forEach
((
item
,
index
)
=>
{
const
angle
=
((
index
+
0.5
)
/
childList
.
length
)
*
Math
.
PI
;
// 交错半径
const
radius
=
index
%
2
===
0
?
340
:
440
;
const
x
=
centerX
+
radius
*
Math
.
cos
(
angle
);
const
y
=
centerY
+
radius
*
Math
.
sin
(
angle
);
// 动态计算标签位置
let
position
=
"right"
;
let
align
=
"left"
;
const
cosA
=
Math
.
cos
(
angle
);
const
sinA
=
Math
.
sin
(
angle
);
if
(
Math
.
abs
(
cosA
)
<
0.3
)
{
// 底部区域
position
=
"bottom"
;
align
=
"center"
;
}
else
if
(
cosA
<
0
)
{
// 左侧区域
position
=
"left"
;
align
=
"right"
;
}
nodes
.
push
({
id
:
`c-
${
item
.
id
}
`
,
id
:
`c-
${
item
.
id
||
index
}
`
,
name
:
item
.
name
,
category
:
item
.
isSanctioned
?
0
:
1
,
symbol
:
"image://"
+
(
item
.
isSanctioned
?
companyActive
:
company
),
x
:
x
,
y
:
y
,
isSanctioned
:
item
.
isSanctioned
,
label
:
{
position
:
position
,
align
:
align
,
distance
:
8
,
width
:
110
,
overflow
:
"break"
,
lineHeight
:
14
,
fontSize
:
11
}
image
:
item
.
isSanctioned
?
companyActive
:
company
,
symbolSize
:
40
,
isSanctioned
:
item
.
isSanctioned
});
links
.
push
({
source
:
"0"
,
target
:
`c-
${
item
.
id
}
`
,
value
:
"客户"
,
isSanctioned
:
item
.
isSanctioned
&&
data
.
isSanctioned
target
:
`c-
${
item
.
id
||
index
}
`
,
name
:
rightActiveTab
.
value
===
'supplyChain'
?
"客户"
:
(
item
.
type
||
"投资"
)
});
});
return
{
tooltip
:
{
show
:
true
,
formatter
:
params
=>
{
if
(
params
.
dataType
===
"node"
)
{
return
`<div style="padding: 8px; max-width: 300px; white-space: normal; word-break: break-all;">
${
params
.
data
.
name
}
</div>`
;
}
return
""
;
}
},
series
:
[
{
type
:
"graph"
,
layout
:
"none"
,
symbolSize
:
36
,
roam
:
true
,
label
:
{
show
:
true
,
formatter
:
"{b}"
,
fontSize
:
12
,
hideOverlap
:
true
},
edgeSymbol
:
[
"none"
,
"arrow"
],
edgeSymbolSize
:
[
4
,
8
],
edgeLabel
:
{
position
:
"middle"
,
offset
:
[
0
,
13
],
fontSize
:
12
,
fontWeight
:
400
,
fontFamily
:
"Microsoft YaHei"
,
lineHeight
:
16
,
show
:
true
,
formatter
:
"{c}"
,
color
:
"rgba(170, 173, 177, 1)"
,
backgroundColor
:
"rgba(234, 236, 238, 1)"
,
padding
:
[
4
,
8
],
borderRadius
:
20
},
data
:
nodes
.
map
(
node
=>
({
...
node
,
label
:
{
color
:
node
.
category
===
0
?
"#055FC2"
:
"#5F656C"
,
...
node
.
label
}
})),
links
:
links
.
map
(
link
=>
{
return
{
...
link
,
lineStyle
:
{
color
:
link
.
isSanctioned
?
"rgba(100, 180, 255, 1)"
:
"rgb(180, 181, 182)"
,
width
:
1
,
curveness
:
0
},
label
:
link
.
isSanctioned
?
{
show
:
true
,
formatter
:
"{c}"
,
backgroundColor
:
"rgba(231, 243, 255, 1)"
,
color
:
"rgba(50, 150, 250, 1)"
,
borderRadius
:
20
,
padding
:
[
4
,
8
],
fontSize
:
12
,
fontWeight
:
400
,
fontFamily
:
"Microsoft YaHei"
,
lineHeight
:
16
}
:
undefined
};
})
}
]
};
graphData
.
value
=
{
nodes
,
links
};
};
const
getEquityOption
=
()
=>
{
if
(
!
singleSanctionEntityEquityData
.
value
)
return
{};
const
data
=
singleSanctionEntityEquityData
.
value
;
const
updateTreeData
=
(
data
)
=>
{
if
(
!
data
)
return
;
const
nodes
=
[];
const
links
=
[];
const
centerX
=
550
;
const
centerY
=
400
;
// 中心节点
nodes
.
push
({
id
:
"0"
,
treeData
.
value
=
{
id
:
data
.
orgId
,
name
:
data
.
orgName
,
category
:
0
,
// 强制为制裁中
symbol
:
"image://"
+
companyActive
,
// 强制使用制裁中图标
x
:
centerX
,
y
:
centerY
,
image
:
companyActive
,
symbolSize
:
50
,
label
:
{
fontSize
:
16
,
fontWeight
:
"bold"
,
color
:
"#055FC2"
,
// 使用制裁蓝,在图标下方更清晰
position
:
"bottom"
,
distance
:
10
,
width
:
150
,
overflow
:
"break"
}
});
// 父级节点 (股东)
const
parentList
=
data
.
parentOrgList
||
[];
parentList
.
forEach
((
item
,
index
)
=>
{
// 在顶部水平排列
const
total
=
parentList
.
length
;
const
gap
=
200
;
const
startX
=
centerX
-
((
total
-
1
)
*
gap
)
/
2
;
const
x
=
startX
+
index
*
gap
;
const
y
=
centerY
-
250
;
// 向上偏移
nodes
.
push
({
id
:
`p-
${
index
}
`
,
children
:
(
data
.
parentOrgList
||
[]).
map
((
item
,
index
)
=>
({
id
:
item
.
id
||
`p-
${
index
}
`
,
name
:
item
.
name
,
category
:
item
.
isSanctioned
?
0
:
1
,
symbol
:
"image://"
+
(
item
.
isSanctioned
?
companyActive
:
company
),
x
:
x
,
y
:
y
,
isSanctioned
:
item
.
isSanctioned
,
label
:
{
position
:
"top"
,
distance
:
8
,
width
:
110
,
overflow
:
"break"
,
lineHeight
:
14
,
fontSize
:
11
}
});
image
:
item
.
isSanctioned
?
companyActive
:
company
,
symbolSize
:
30
}))
};
};
links
.
push
({
source
:
`p-
${
index
}
`
,
target
:
"0"
,
value
:
item
.
type
||
"持股"
,
// 使用 type 字段
isSanctioned
:
item
.
isSanctioned
// 中心节点强制制裁,高亮取决于对方
});
const
getSingleSanctionEntityEquityRequest
=
async
()
=>
{
try
{
const
res
=
await
getSingleSanctionEntityEquity
({
orgId
:
activeEntityId
.
value
,
rule
:
is50PercentRule
.
value
});
// 子级节点 (对外投资)
const
childList
=
data
.
childrenOrgList
||
[];
childList
.
forEach
((
item
,
index
)
=>
{
// 在底部水平排列
const
total
=
childList
.
length
;
const
gap
=
240
;
// 稍微拉开一点间距
const
startX
=
centerX
-
((
total
-
1
)
*
gap
)
/
2
;
const
x
=
startX
+
index
*
gap
;
const
y
=
centerY
+
250
;
// 向下偏移
nodes
.
push
({
id
:
`c-
${
index
}
`
,
name
:
item
.
name
,
category
:
item
.
isSanctioned
?
0
:
1
,
symbol
:
"image://"
+
(
item
.
isSanctioned
?
companyActive
:
company
),
x
:
x
,
y
:
y
,
isSanctioned
:
item
.
isSanctioned
,
label
:
{
position
:
"bottom"
,
distance
:
8
,
width
:
110
,
overflow
:
"break"
,
lineHeight
:
14
,
fontSize
:
11
if
(
res
.
code
===
200
)
{
singleSanctionEntityEquityData
.
value
=
res
.
data
||
null
;
updateGraphData
();
}
});
}
catch
(
error
)
{
console
.
log
(
error
);
}
};
links
.
push
({
source
:
"0"
,
target
:
`c-
${
index
}
`
,
value
:
item
.
type
||
"持股"
,
// 使用 type 字段
isSanctioned
:
item
.
isSanctioned
// 中心节点强制制裁,高亮取决于对方
});
const
getSingleSanctionEntitySupplyChainRequest
=
async
()
=>
{
try
{
const
res
=
await
getSingleSanctionEntitySupplyChain
({
orgId
:
activeEntityId
.
value
});
return
{
tooltip
:
{
show
:
true
,
formatter
:
params
=>
{
if
(
params
.
dataType
===
"node"
)
{
return
`<div style="padding: 8px; max-width: 300px; white-space: normal; word-break: break-all;">
${
params
.
data
.
name
}
</div>`
;
}
return
""
;
}
},
series
:
[
{
type
:
"graph"
,
layout
:
"none"
,
symbolSize
:
36
,
roam
:
true
,
label
:
{
show
:
true
,
formatter
:
"{b}"
,
fontSize
:
12
,
hideOverlap
:
true
},
edgeSymbol
:
[
"none"
,
"arrow"
],
edgeSymbolSize
:
[
4
,
8
],
edgeLabel
:
{
position
:
"middle"
,
offset
:
[
0
,
13
],
fontSize
:
12
,
fontWeight
:
400
,
fontFamily
:
"Microsoft YaHei"
,
lineHeight
:
16
,
show
:
true
,
formatter
:
"{c}"
,
color
:
"rgba(170, 173, 177, 1)"
,
backgroundColor
:
"rgba(234, 236, 238, 1)"
,
padding
:
[
4
,
8
],
borderRadius
:
20
},
data
:
nodes
.
map
(
node
=>
({
...
node
,
label
:
{
color
:
node
.
category
===
0
?
"#055FC2"
:
"#5F656C"
,
...
node
.
label
}
})),
links
:
links
.
map
(
link
=>
{
return
{
...
link
,
lineStyle
:
{
color
:
link
.
isSanctioned
?
"rgba(100, 180, 255, 1)"
:
"rgb(180, 181, 182)"
,
width
:
1
,
curveness
:
0
},
label
:
link
.
isSanctioned
?
{
show
:
true
,
formatter
:
"{c}"
,
backgroundColor
:
"rgba(231, 243, 255, 1)"
,
color
:
"rgba(50, 150, 250, 1)"
,
borderRadius
:
20
,
padding
:
[
4
,
8
],
fontSize
:
12
,
fontWeight
:
400
,
fontFamily
:
"Microsoft YaHei"
,
lineHeight
:
16
}
:
undefined
};
})
if
(
res
.
code
===
200
)
{
singleSanctionEntitySupplyChainData
.
value
=
res
.
data
||
null
;
updateGraphData
();
updateTreeData
(
res
.
data
);
}
}
catch
(
error
)
{
console
.
log
(
error
);
}
]
};
};
const
debouncedGetList
=
debounce
(()
=>
{
getSingleSanctionEntityListRequest
();
},
1000
);
watch
(
searchText
,
()
=>
{
debouncedGetList
();
});
watch
(
searchDomain
,
()
=>
{
getSingleSanctionEntityListRequest
();
});
const
getSingleSanctionEntityListRequest
=
async
()
=>
{
try
{
const
res
=
await
getSingleSanctionEntityList
({
sanRecordId
:
sanRecordId
.
value
,
isOnlyCn
:
false
,
domainId
:
searchDomain
.
value
||
undefined
,
searchText
:
searchText
.
value
||
undefined
});
if
(
res
.
code
===
200
)
{
entityList
.
value
=
(
res
.
data
||
[]).
map
((
group
,
index
)
=>
({
id
:
`group-
${
index
}
`
,
name
:
group
.
orgType
,
count
:
group
.
orgInfoList
?
group
.
orgInfoList
.
length
:
0
,
expanded
:
index
===
0
,
children
:
(
group
.
orgInfoList
||
[]).
map
(
org
=>
({
id
:
org
.
id
,
name
:
org
.
orgNameZh
}))
}));
watch
(
activeIndex
,
val
=>
{
if
(
val
===
0
)
{
nextTick
(()
=>
{
if
(
activeEntityId
.
value
)
{
if
(
rightActiveTab
.
value
===
"supplyChain"
)
{
getSingleSanctionEntitySupplyChainRequest
();
}
else
{
getSingleSanctionEntityEquityRequest
();
}
if
(
entityList
.
value
.
length
>
0
&&
entityList
.
value
[
0
].
children
&&
entityList
.
value
[
0
].
children
.
length
>
0
)
{
const
firstEntity
=
entityList
.
value
[
0
].
children
[
0
];
if
(
!
activeEntityId
.
value
)
{
activeEntityId
.
value
=
firstEntity
.
id
;
currentEntityName
.
value
=
firstEntity
.
name
;
}
initChart
();
});
}
});
watch
(
activeEntityId
,
val
=>
{
if
(
val
)
{
if
(
rightActiveTab
.
value
===
"supplyChain"
)
{
getSingleSanctionEntitySupplyChainRequest
();
}
else
{
getSingleSanctionEntityEquityRequest
();
}
initChart
();
}
catch
(
error
)
{
console
.
log
(
error
);
}
}
)
;
};
watch
(
is50PercentRule
,
()
=>
{
if
(
rightActiveTab
.
value
===
"equity"
&&
activeEntityId
.
value
)
{
getSingleSanctionEntityEquityRequest
();
watch
(
rightActiveTab
,
async
(
newTab
)
=>
{
if
(
newTab
===
'supplyChain'
)
{
await
getSingleSanctionEntitySupplyChainRequest
();
}
else
{
await
getSingleSanctionEntityEquityRequest
();
}
});
watch
(
rightActiveTab
,
val
=>
{
if
(
activeEntityId
.
value
)
{
if
(
val
===
"supplyChain"
)
{
getSingleSanctionEntitySupplyChainRequest
();
watch
(
activeEntityId
,
async
(
newId
)
=>
{
if
(
newId
)
{
if
(
rightActiveTab
.
value
===
'supplyChain'
)
{
await
getSingleSanctionEntitySupplyChainRequest
();
}
else
{
getSingleSanctionEntityEquityRequest
();
await
getSingleSanctionEntityEquityRequest
();
}
}
nextTick
(()
=>
{
initChart
();
});
},
{
immediate
:
true
}
);
onMounted
(()
=>
{
// 获取URL参数
getUrlParams
();
// 单次制裁-深度挖掘-本次制裁实体清单列表-请求
getSingleSanctionEntityListRequest
();
window
.
addEventListener
(
"resize"
,
handleResize
);
});
onUnmounted
(()
=>
{
if
(
chartInstance
.
value
)
{
chartInstance
.
value
.
dispose
();
}
if
(
debouncedGetList
&&
debouncedGetList
.
cancel
)
{
debouncedGetList
.
cancel
();
watch
(
is50PercentRule
,
async
()
=>
{
if
(
rightActiveTab
.
value
===
'equity'
)
{
await
getSingleSanctionEntityEquityRequest
();
}
window
.
removeEventListener
(
"resize"
,
handleResize
);
});
const
handleResize
=
()
=>
{
if
(
chartInstance
.
value
)
{
chartInstance
.
value
.
resize
();
}
};
onMounted
(
async
()
=>
{
getUrlParams
();
await
getSingleSanctionEntityListRequest
();
});
</
script
>
<
style
scoped
lang=
"scss"
>
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论