提交 26ccefc7 authored 作者: 张烨's avatar 张烨

fix:政令-受影响实体板块-鱼骨图增加缩放和移动的功能

上级 123be9b5
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<div class="box1"> <div class="box1">
<AnalysisBox title="相关政令" :showAllBtn="false"> <AnalysisBox title="相关政令" :showAllBtn="false">
<div class="box1-main"> <div class="box1-main">
<el-empty v-if="siderList.length===0" style="padding-top: 30%" description="暂无数据" :image-size="100" /> <el-empty v-if="!siderList?.length" style="padding-top: 40%;" description="暂无数据" :image-size="100" />
<el-scrollbar height="100%" always> <el-scrollbar height="100%" always>
<div class="left-item" :class="{ 'item-active': false }" v-for="(item, index) in siderList" :key="index" @click="handleClickDecree(item)"> <div class="left-item" :class="{ 'item-active': false }" v-for="(item, index) in siderList" :key="index" @click="handleClickDecree(item)">
<div class="item-head"> <div class="item-head">
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
</div> </div>
<div class="box2"> <div class="box2">
<AnalysisBox title="政令关系挖掘" :showAllBtn="false"> <AnalysisBox title="政令关系挖掘" :showAllBtn="false">
<el-empty v-if="!siderList?.length" style="padding-top: 20%;" description="暂无数据" :image-size="100" />
<div class="box2-main"> <div class="box2-main">
<div ref="containerRef" class="graph-container"></div> <div ref="containerRef" class="graph-container"></div>
</div> </div>
......
...@@ -203,12 +203,12 @@ const mainHeaderBtnList = ref([ ...@@ -203,12 +203,12 @@ const mainHeaderBtnList = ref([
name: "深度挖掘", name: "深度挖掘",
path: "/decreeLayout/deepDig" path: "/decreeLayout/deepDig"
}, },
// { {
// icon: icon3, icon: icon3,
// activeIcon: icon3Active, activeIcon: icon3Active,
// name: "影响分析", name: "影响分析",
// path: "/decreeLayout/influence" path: "/decreeLayout/influence"
// }, },
]); ]);
const activeTitle = ref("政令概况"); const activeTitle = ref("政令概况");
......
<template>
<div class="relation-graph-wrapper">
<div class="graph-controls">
<!-- 这项政令标志着中美AI竞争进入一个新阶段,其核心特征是 “精准封锁”与“体系输出”相结合。它短期内无疑会给中国AI产业链带来压力,但长期看,这场竞争更可能是一场围绕技术路线、生态系统和治理规则的持久战。 -->
<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>
</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/images/echartsicon01.png'
import echartsIcon02 from './assets/images/echartsicon02.png'
import echartsIcon03 from './assets/images/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 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 width = containerRef.value.offsetWidth || 800
const height = containerRef.value.offsetHeight || 600
switch (layoutType) {
case 1:
initNormalGraph(layoutType, width, height)
break
case 2:
initTreeGraph(width, height)
break
case 3:
initCircularGraph(width, height)
break
}
})
}
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 || echartsIcon03,
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 || echartsIcon03,
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 || echartsIcon03,
size: node.symbolSize || 40,
name: node.name,
image: node.image,
isSanctioned: node.isSanctioned
})
}
})
}
return {
id: centerId,
label: centerNode.name || '',
img: centerNode.image || echartsIcon03,
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 || echartsIcon03,
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()
emit('nodeClick', model)
})
graphInstance.value.on('canvas:click', () => {
})
}
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)
}
)
watch(
() => props.treeData,
() => {
if (currentLayoutType.value === 2) {
initGraph(2)
}
}
)
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);
}
}
</style>
\ No newline at end of file
<template>
<div class="view-box">
<div class="icon-left">
<img src="../../assets/icons/ai.png" alt="">
</div>
<div class="tips-content">{{ props.tips }}</div>
<div class="icon-right">
<img src="../../assets/icons/right.png" alt="">
</div>
</div>
</template>
<script setup lang="ts" name="AiTips">
const props = defineProps({
tips: {
type: String,
default: ''
}
});
</script>
<style scoped lang="scss">
.view-box {
width: 100%;
display: flex;
align-items: center;
padding: 7px 12px;
border: 1px solid rgba(231, 243, 255, 1);
border-radius: 4px;
background: rgba(246, 250, 255, 1);
.icon-left {
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.tips-content {
color: rgb(5, 95, 194);
font-size: 16px;
font-weight: 400;
line-height: 24px;
margin-left: 13px;
flex: 1;
}
.icon-right {
width: 24px;
height: 24px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
}
</style>
\ No newline at end of file
<template> <template>
<div class="view-box"> <div class="view-box">
<div class="right-main"> <el-empty v-if="!dataList?.length" style="padding-top: 15%;" description="暂无数据" :image-size="100" />
<div class="right-main-content"> <div v-if="dataList.length" class="main-content-main">
<div class="hintWrap"> <div class="main-mask"
<div class="icon1"></div> @wheel.prevent="handleWheel"
<div class="title"> @mousedown="handleMouseDown"
这项政令标志着中美AI竞争进入一个新阶段,其核心特征是 “精准封锁”与“体系输出”相结合。它短期内无疑会给中国AI产业链带来压力,但长期看,这场竞争更可能是一场围绕技术路线、生态系统和治理规则的持久战。 @mouseup="handleMouseUp"
</div> @mouseleave="handleMouseUp"
<div class="icon2Wrap"> @mousemove="handleMouseMove"
<div class="icon2"></div> ></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> </div>
<div class="right-main-content-main"> <!-- 奇数索引的数据组放在上方 -->
<div class="fishbone-wrapper"> <div v-for="(causeGroup, groupIndex) in onFilterData(1)" :key="groupIndex"
<div class="fishbone-scroll-container" ref="scrollContainerRef"> class="top-bone" :style="{ left: groupIndex * 400 + 510 + 'px', height: (causeGroup.causes?.length) * 22 + 100 + 'px' }">
<div class="fishbone" v-if="dataList.length > 0"> <div class="left-bone">
<div class="main-line" :style="{ width: dataList.length * 200 + 300 + 'px' }"> <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="main-line-text" v-for="(item, index) in dataList" :key="'label-' + index" <div class="text" :title="item.name">{{ item.name }}</div>
:class="{ <div class="line"></div>
'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="'top-' + groupIndex"
:class="getTopBoneClass(groupIndex)" :style="{ left: groupIndex * 400 + 420 + 'px' }">
<div class="left-bone">
<div class="left-bone-item" v-for="(item, index) in getLeftItems(causeGroup.causes)" :key="'left-' + index">
<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, index) in getRightItems(causeGroup.causes)" :key="'right-' + index">
<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="'bottom-' + groupIndex"
:class="getBottomBoneClass(groupIndex)" :style="{ left: groupIndex * 400 + 220 + 'px' }">
<div class="left-bone">
<div class="left-bone-item" v-for="(item, index) in getLeftItems(causeGroup.causes)" :key="'left-bottom-' + index">
<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, index) in getRightItems(causeGroup.causes)" :key="'right-bottom-' + index">
<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 v-else
style="display: flex; justify-content: center; align-items: center; height: 200px; width: 100%">
<el-empty description="暂无相关数据" />
</div>
</div> </div>
</div> </div>
</div> <div class="right-bone">
<div class="right-main-content-footer"> <div class="right-bone-item" v-for="item in getRightItems(causeGroup.causes)" :key="item.id">
<div class="footer-item footer-item1"> <div class="line"></div>
<div class="footer-item-bottom"> <img :src="defaultIcon2 || item.picture" alt="" class="company-icon" />
<div class="icon"> <div class="text" :title="item.name">{{ item.name }}</div>
<img :src="noticeIcon" alt="" />
</div>
<div class="text">
{{
`中国企业${cnEntityOnChainData.upstreamInternalCount ||
0}家(${formatRate(cnEntityOnChainData.upstreamInternalRate)}%),受制裁${cnEntityOnChainData.upstreamEntityCount
|| 0}家(${formatRate(cnEntityOnChainData.upstreamEntityRate)}%)`
}}
</div>
</div> </div>
<div class="footer-item-top">{{ "上游" }}</div>
</div> </div>
<div class="footer-item footer-item2"> </div>
<div class="footer-item-bottom"> <!-- 偶数索引的数据组放在下方 -->
<div class="icon"> <div v-for="(causeGroup, groupIndex) in onFilterData(0)" :key="groupIndex"
<img :src="noticeIcon" alt="" /> class="bottom-bone" :style="{ left: groupIndex * 400 + 310 + 'px', height: (causeGroup.causes?.length) * 22 + 100 + 'px' }">
</div> <div class="left-bone">
<div class="text"> <div class="left-bone-item" v-for="item in getRightItems(causeGroup.causes)" :key="item.id">
{{ <img :src="defaultIcon2 || item.picture" alt="" class="company-icon" />
`中国企业${cnEntityOnChainData.midstreamInternalCount || <div class="text" :title="item.name">{{ item.name }}</div>
0}家(${formatRate(cnEntityOnChainData.midstreamInternalRate)}%),受制裁${cnEntityOnChainData.midstreamEntityCount <div class="line"></div>
|| 0}家(${formatRate(cnEntityOnChainData.midstreamEntityRate)}%)`
}}
</div>
</div> </div>
<div class="footer-item-top">{{ "中游" }}</div>
</div> </div>
<div class="footer-item footer-item3"> <div class="right-bone">
<div class="footer-item-bottom"> <div class="right-bone-item" v-for="item in getLeftItems(causeGroup.causes)" :key="item.id">
<div class="icon"> <div class="line"></div>
<img :src="noticeIcon" alt="" /> <img :src="defaultIcon2 || item.picture" alt="" class="company-icon" />
</div> <div class="text" :title="item.name">{{ item.name }}</div>
<div class="text">
{{
`中国企业${cnEntityOnChainData.downstreamInternalCount ||
0}家(${formatRate(cnEntityOnChainData.downstreamInternalRate)}%),受制裁${cnEntityOnChainData.downstreamEntityCount
|| 0}家(${formatRate(cnEntityOnChainData.downstreamEntityRate)}%)`
}}
</div>
</div> </div>
<div class="footer-item-top">{{ "下游" }}</div>
</div> </div>
</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>
</template> </template>
...@@ -129,6 +115,41 @@ import defaultIcon2 from "@/assets/icons/default-icon2.png"; ...@@ -129,6 +115,41 @@ import defaultIcon2 from "@/assets/icons/default-icon2.png";
import noticeIcon from "./assets/images/notice-icon.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 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
}
// 实体清单-深度挖掘-产业链中国企业实体信息查询 // 实体清单-深度挖掘-产业链中国企业实体信息查询
const cnEntityOnChainData = ref({}); const cnEntityOnChainData = ref({});
const getCnEntityOnChainData = async () => { const getCnEntityOnChainData = async () => {
...@@ -173,16 +194,6 @@ const getRightItems = items => { ...@@ -173,16 +194,6 @@ const getRightItems = items => {
const midpoint = Math.ceil(items.length / 2); const midpoint = Math.ceil(items.length / 2);
return items.slice(midpoint); return items.slice(midpoint);
}; };
// 获取上方鱼骨图位置类名
const getTopBoneClass = index => {
const positions = ["top-bone", "top-bone1", "top-bone2"];
return positions[index % 3] || "top-bone";
};
// 获取下方鱼骨图位置类名
const getBottomBoneClass = index => {
const positions = ["bottom-bone", "bottom-bone1", "bottom-bone2"];
return positions[index % 3] || "bottom-bone";
};
const getFishboneData = async () => { const getFishboneData = async () => {
const currentSanction = sanctionList.value.find(item => item.id === currentSanctionId.value); const currentSanction = sanctionList.value.find(item => item.id === currentSanctionId.value);
const date = currentSanction ? currentSanction.date : ''; const date = currentSanction ? currentSanction.date : '';
...@@ -305,424 +316,316 @@ onMounted(() => { ...@@ -305,424 +316,316 @@ onMounted(() => {
.view-box { .view-box {
width: 100%; width: 100%;
height: 100%; height: 100%;
} display: flex;
.right-main { flex-direction: column;
height: 100%;
padding: 11px 16px 20px;
.right-main-content { .main-content-main {
height: 100%; position: relative;
height: 20px;
flex: auto;
display: flex; display: flex;
flex-direction: column; align-items: center;
justify-content: center;
.hintWrap { overflow: hidden;
display: flex; .main-mask {
align-items: center; position: absolute;
padding: 7px 12px; top: 0;
border: 1px solid rgba(231, 243, 255, 1); left: 0;
border-radius: 4px; width: 100%;
background: rgba(246, 250, 255, 1); height: 100%;
margin-bottom: 9px; z-index: 3;
}
.icon1 {
width: 19px;
height: 20px;
background-image: url("../assets/icons/ai.png");
background-size: 100% 100%;
flex-shrink: 0;
}
.title { .fishbone-container {
color: rgb(5, 95, 194); position: relative;
font-size: 16px;
font-weight: 400;
line-height: 24px;
margin-left: 13px;
flex: 1;
}
.icon2Wrap { .main-line {
width: 24px; height: 3px;
height: 24px; background: rgb(230, 231, 232);
background-color: rgba(231, 243, 255, 1);
display: flex; display: flex;
justify-content: center; justify-content: space-between;
align-items: center; align-items: center;
border-radius: 12px;
margin-left: 20px; // 添加中间的文字块
flex-shrink: 0; .main-line-text {
position: absolute;
.icon2 { // top: -14px;
width: 24px; font-size: 16px;
height: 24px; color: #055FC2;
background-image: url("../assets/icons/right.png"); font-weight: bold;
background-size: 100% 100%; background-color: #f7f8f9;
padding: 0 10px;
z-index: 2;
// 箭头背景
height: 32px;
line-height: 32px;
width: 160px;
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);
}
} }
} }
} }
.right-main-content-main { .company-icon {
flex: 1; width: 16px;
position: relative; height: 16px;
overflow: hidden; margin: 0 4px;
object-fit: contain;
}
.fishbone-wrapper { .top-bone {
position: relative; position: absolute;
width: 100%; bottom: 0px;
width: 3px;
background: rgb(230, 231, 232);
transform-origin: bottom center;
transform: skew(30deg);
z-index: 1;
.left-bone {
color: #777;
position: absolute;
top: -20px;
right: 0;
width: 180px;
height: 100%; height: 100%;
}
.fishbone-scroll-container {
display: flex; display: flex;
align-items: center; flex-direction: column;
width: 100%; justify-content: flex-end;
height: 100%;
overflow-x: auto;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: rgba(144, 202, 249, 0.5) transparent;
&::-webkit-scrollbar { .left-bone-item {
height: 6px; transform: skew(-30deg);
} height: 40px;
margin: 4px 0;
display: flex;
justify-content: flex-end;
align-items: center;
&::-webkit-scrollbar-track { .text {
background: transparent; margin-left: 4px;
} height: 25px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&::-webkit-scrollbar-thumb { .line {
background-color: rgba(144, 202, 249, 0.5); margin-left: 7px;
border-radius: 3px; width: 40px;
height: 2px;
background: rgb(230, 231, 232);
}
} }
} }
.fishbone { .right-bone {
position: relative; color: #777;
width: fit-content; position: absolute;
top: -44px;
left: 0;
width: 180px;
height: 100%; height: 100%;
margin-top: 40px; display: flex;
min-width: 100%; flex-direction: column;
padding-left: 275px; justify-content: flex-end;
margin-left: 40px;
.right-bone-item {
.main-line { transform: skew(-30deg);
margin-top: 280px; height: 40px;
width: 1888px; margin: 4px 0;
height: 3px;
background: rgb(230, 231, 232);
display: flex; display: flex;
justify-content: space-between; justify-content: flex-start;
align-items: center; align-items: center;
padding: 0 100px;
.line {
// 虚线 margin-right: 7px;
&::after { width: 30px;
content: ''; height: 2px;
position: absolute; background: rgb(230, 231, 232);
top: 0;
left: 0;
width: 100%;
height: 100%;
} }
// 添加中间的文字块 .text {
.main-line-text { max-width: 100px;
position: absolute; margin-right: 4px;
// top: -14px; height: 25px;
font-size: 16px; line-height: 25px;
color: #055FC2; overflow: hidden;
font-weight: bold; text-overflow: ellipsis;
background-color: #f7f8f9; white-space: nowrap;
padding: 0 10px;
z-index: 2;
// 箭头背景
height: 32px;
line-height: 32px;
width: 160px;
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 { .bottom-bone {
width: 16px; position: absolute;
height: 16px; top: 0px;
margin: 0 4px; width: 3px;
object-fit: contain; background: rgb(230, 231, 232);
} transform-origin: top center;
transform: skew(-30deg);
.top-bone { z-index: 1;
.left-bone {
color: #777;
position: absolute; position: absolute;
top: 20px; bottom: -44px;
right: 200px; right: 0;
width: 3px; width: 180px;
height: 260px; height: 100%;
background: rgb(230, 231, 232); display: flex;
transform: skew(30deg); flex-direction: column;
z-index: 1; justify-content: flex-start;
.left-bone { .left-bone-item {
color: #777; transform: skew(30deg);
position: absolute; height: 40px;
top: 0; margin: 4px 0;
left: -150px; display: flex;
width: 150px; justify-content: flex-end;
height: 50px; align-items: center;
// overflow: hidden; .text {
.left-bone-item { margin-left: 4px;
transform: skew(-30deg); height: 25px;
height: 45px; max-width: 130px;
margin-bottom: 2px; line-height: 25px;
margin-top: 2px; overflow: hidden;
display: flex; text-overflow: ellipsis;
justify-content: flex-end; white-space: nowrap;
align-items: center;
.text {
margin-left: 4px;
height: 25px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
width: 40px;
height: 2px;
background: rgb(230, 231, 232);
}
} }
}
.right-bone { .line {
color: #777; margin-left: 7px;
position: absolute; width: 40px;
top: 0; height: 2px;
right: -150px; background: rgb(230, 231, 232);
width: 150px;
height: 210px;
overflow: hidden;
.right-bone-item {
transform: skew(-30deg);
height: 39px;
margin-bottom: 2px;
margin-top: 2px;
display: flex;
justify-content: flex-start;
align-items: center;
.line {
margin-right: 7px;
width: 30px;
height: 2px;
background: rgb(230, 231, 232);
}
.text {
max-width: 100px;
margin-right: 4px;
height: 25px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
} }
} }
} }
.top-bone1 { .right-bone {
@extend .top-bone; color: #777;
right: 500px;
}
.top-bone2 {
@extend .top-bone;
right: 800px;
}
.bottom-bone {
position: absolute; position: absolute;
top: 280px; bottom: -20px;
right: 360px; left: 0;
width: 3px; width: 180px;
height: 260px; height: 100%;
background: rgb(230, 231, 232); display: flex;
transform: skew(-30deg); flex-direction: column;
z-index: 1; justify-content: flex-start;
.left-bone { .right-bone-item {
color: #777; transform: skew(30deg);
position: absolute; height: 40px;
top: 50px; margin: 4px 0;
left: -150px; display: flex;
width: 150px; justify-content: flex-start;
height: 260px; align-items: center;
.left-bone-item { .line {
transform: skew(30deg); margin-right: 7px;
height: 39px; width: 30px;
margin-bottom: 2px; height: 2px;
margin-top: 2px; background: rgb(230, 231, 232);
display: flex;
justify-content: flex-end;
align-items: center;
.text {
margin-left: 4px;
height: 25px;
max-width: 130px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
width: 40px;
height: 2px;
background: rgb(230, 231, 232);
}
} }
}
.right-bone { .text {
color: #777; max-width: 100px;
position: absolute; margin-right: 4px;
top: 50px; height: 25px;
right: -150px; line-height: 25px;
width: 150px; overflow: hidden;
height: 260px; text-overflow: ellipsis;
white-space: nowrap;
.right-bone-item {
transform: skew(30deg);
height: 35px;
margin-bottom: 2px;
margin-top: 2px;
display: flex;
justify-content: flex-start;
align-items: center;
.line {
margin-right: 7px;
width: 30px;
height: 2px;
background: rgb(230, 231, 232);
}
.text {
max-width: 100px;
margin-right: 4px;
height: 25px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
} }
} }
} }
.bottom-bone1 {
@extend .bottom-bone;
right: 660px;
}
.bottom-bone2 {
@extend .bottom-bone;
right: 960px;
}
} }
}
.main-content-footer {
margin-top: 16px;
display: flex;
justify-content: space-between;
.right-main-content-footer { .footer-item {
margin-top: 16px; flex: 1;
display: flex; display: flex;
justify-content: space-between; flex-direction: column;
justify-content: flex-end;
}
.footer-item { .footer-item {
flex: 1; .footer-item-top {
display: flex; height: 28px;
flex-direction: column; text-align: center;
justify-content: flex-end; line-height: 28px;
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
margin-top: 10px;
position: relative;
z-index: 1;
} }
.footer-item-bottom {
display: flex;
justify-content: center;
align-items: center;
.footer-item { .text {
.footer-item-top { color: rgba(206, 79, 81, 1);
height: 28px; font-size: 14px;
text-align: center; line-height: 14px;
line-height: 28px; margin-left: 6px;
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
margin-top: 10px;
position: relative;
z-index: 1;
} }
.footer-item-bottom { .icon {
display: flex; width: 16px;
justify-content: center; height: 16px;
align-items: center; font-size: 0;
.text { img {
color: rgba(206, 79, 81, 1); width: 100%;
font-size: 14px; height: 100%;
line-height: 14px;
margin-left: 6px;
}
.icon {
width: 16px;
height: 16px;
font-size: 0;
img {
width: 100%;
height: 100%;
}
} }
} }
} }
}
.footer-item1 { .footer-item1 {
color: rgba(22, 119, 255, 1); color: rgba(22, 119, 255, 1);
.footer-item-top { .footer-item-top {
background: rgba(231, 243, 255, 1); background: rgba(231, 243, 255, 1);
}
} }
.footer-item2 { }
color: rgba(19, 168, 168, 1); .footer-item2 {
.footer-item-top { color: rgba(19, 168, 168, 1);
background: rgba(225, 255, 251, 1); .footer-item-top {
} background: rgba(225, 255, 251, 1);
} }
.footer-item3 { }
color: rgba(146, 84, 222, 1); .footer-item3 {
.footer-item-top { color: rgba(146, 84, 222, 1);
background: rgba(246, 235, 255, 1); .footer-item-top {
} background: rgba(246, 235, 255, 1);
} }
} }
} }
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
</div> </div>
<div class="data-title">实体名称</div> <div class="data-title">实体名称</div>
<div style="height: 20px; flex: auto;"> <div style="height: 20px; flex: auto;">
<el-empty v-if="showCompanyList.length === 0" style="padding-top: 30%" description="暂无数据" :image-size="100" /> <el-empty v-if="!showCompanyList?.length" style="padding-top: 35%;" description="暂无数据" :image-size="100" />
<el-scrollbar height="100%" always> <el-scrollbar height="100%" always>
<div class="list-data"> <div class="list-data">
<div class="list-item" v-for="item in showCompanyList" :key="item.id" :class="{ 'item-active': activeEntityId === item.id }" @click="handleToCompanyDetail(item)"> <div class="list-item" v-for="item in showCompanyList" :key="item.id" :class="{ 'item-active': activeEntityId === item.id }" @click="handleToCompanyDetail(item)">
...@@ -75,18 +75,14 @@ ...@@ -75,18 +75,14 @@
</div> </div>
</div> </div>
</template> </template>
<div class="box2-main" v-if="contentType==1"> <div class="box2-main">
<ChartChain /> <AiTips :tips="tips" />
</div> <div class="graph-box" v-if="contentType==1">
<div class="box2-main" v-if="contentType==2"> <ChartChain />
<!-- <ChartRelation </div>
:graph-data="graphData" <div class="graph-box" v-if="contentType==2">
:tree-data="treeData" <GraphChart :nodes="testData.nodes" :links="testData.links" layoutType="force" />
:control-active="1" </div>
@node-click="handleNodeClick"
@layout-change="handleLayoutChange"
/> -->
<GraphChart :nodes="testData.nodes" :links="testData.links" layoutType="force" />
</div> </div>
</AnalysisBox> </AnalysisBox>
</div> </div>
...@@ -101,9 +97,9 @@ import getBarChart from "./utils/barChart"; ...@@ -101,9 +97,9 @@ import getBarChart from "./utils/barChart";
import { getDecreeIndustry, getDecreehylyList, getDecreeCompany } from "@/api/decree/influence"; import { getDecreeIndustry, getDecreehylyList, getDecreeCompany } from "@/api/decree/influence";
import { getCnEntityOnChain, getChainInfoByDomainId } from "@/api/exportControl"; import { getCnEntityOnChain, getChainInfoByDomainId } from "@/api/exportControl";
import { getSingleSanctionEntitySupplyChain } from "@/api/exportControlV2.0"; import { getSingleSanctionEntitySupplyChain } from "@/api/exportControlV2.0";
import ChartChain from "./ChartChain.vue"; import ChartChain from "./com/ChartChain.vue";
import AiTips from "./com/AiTips.vue";
import GraphChart from "@/components/base/GraphChart/index.vue"; import GraphChart from "@/components/base/GraphChart/index.vue";
import ChartRelation from "./ChartRelation.vue";
import defaultIcon2 from "@/assets/icons/default-icon2.png"; import defaultIcon2 from "@/assets/icons/default-icon2.png";
import noticeIcon from "./assets/images/notice-icon.png"; import noticeIcon from "./assets/images/notice-icon.png";
import icon422 from "./assets/images/icon422.png"; import icon422 from "./assets/images/icon422.png";
...@@ -113,191 +109,65 @@ import icon1621 from "./assets/images/icon1621.png"; ...@@ -113,191 +109,65 @@ import icon1621 from "./assets/images/icon1621.png";
import company from "./assets/images/company.png"; import company from "./assets/images/company.png";
import companyActive from "./assets/images/company-active.png"; import companyActive from "./assets/images/company-active.png";
const tips = "这项政令标志着中美AI竞争进入一个新阶段,其核心特征是 “精准封锁”与“体系输出”相结合。它短期内无疑会给中国AI产业链带来压力,但长期看,这场竞争更可能是一场围绕技术路线、生态系统和治理规则的持久战。"
// 关系图数据 // 关系图数据
const testData = { const testData = {
// 节点数据 // 节点数据
nodes: [ nodes: [
{ { id: 0, name: "泰丰先行", symbolSize: 60, symbol: `image://${company}`, x:0, y:0 },
id: 0, { id: 1, name: "国轩高科", symbolSize: 40, symbol: `image://${company}` },
name: "泰丰先行", { id: 2, name: "智方纳米", symbolSize: 40, symbol: `image://${company}` },
// category: 0, { id: 3, name: "香百科技", symbolSize: 40, symbol: `image://${company}` },
symbolSize: 30, { id: 4, name: "格林滨", symbolSize: 40, symbol: `image://${company}` },
value: 8, { id: 5, name: "江西紫宸", symbolSize: 40, symbol: `image://${company}` },
symbol: `image://${company}`, { id: 6, name: "紫江企业", symbolSize: 40, symbol: `image://${company}` },
x: 50, { id: 7, name: "大而美法案", symbolSize: 40, symbol: `image://${company}` },
y: 10 { id: 8, name: "比亚迪", symbolSize: 40, symbol: `image://${company}` },
}, ],
{
id: 1, // 关系数据
name: "国轩高科", links: [
// category: 0,
symbolSize: 30,
value: 9,
symbol: `image://${company}`,
x: 150,
y: 10
},
{
id: 2,
name: "智方纳米",
// category: 2,
symbolSize: 30,
value: 7,
symbol: `image://${company}`,
x: 250,
y: 10
},
{
id: 3,
name: "香百科技",
// category: 1,
symbolSize: 30,
value: 6,
symbol: `image://${company}`,
x: 350,
y: 10
},
{
id: 4,
name: "格林滨",
// category: 2,
symbolSize: 30,
value: 6,
symbol: `image://${company}`,
x: 450,
y: 10
},
{
id: 5,
name: "江西紫宸",
// category: 2,
symbolSize: 30,
value: 7,
symbol: `image://${company}`,
x: 550,
y: 10
},
{
id: 6,
name: "紫江企业",
// category: 4,
symbolSize: 30,
value: 6,
symbol: `image://${company}`,
x: 650,
y: 10
},
{
id: 7,
name: "大而美法案",
// category: 4,
symbolSize: 50,
value: 5,
symbol: `image://${company}`,
x: 300,
y: 200
},
{ {
id: 8, source: 1, target: 0,
name: "比亚迪", label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '持股' },
// category: 0, lineStyle: { color: '#B9DCFF', type: "solid" }
symbolSize: 30,
value: 10,
symbol: `image://${company}`,
x: 50,
y: 400
}, },
{ {
id: 9, source: 2, target: 0,
name: "铜陵有色", label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '持股' },
// category: 3, lineStyle: { color: '#B9DCFF', type: "solid" }
symbolSize: 30,
value: 8,
symbol: `image://${company}`,
x: 150,
y: 400
}, },
{ {
id: 10, source: 3, target: 0,
name: "长盛精密", label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '合作' },
// category: 1, lineStyle: { color: '#B9DCFF', type: "solid" }
symbolSize: 30,
value: 7,
symbol: `image://${company}`,
x: 250,
y: 400
}, },
{ {
id: 11, source: 4, target: 0,
name: "天合光能", label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '从属' },
// category: 0, lineStyle: { color: '#B9DCFF', type: "solid" }
symbolSize: 30,
value: 8,
symbol: `image://${company}`,
x: 350,
y: 400
}, },
{ {
id: 12, source: 5, target: 0,
name: "昆仑化学", label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '合作' },
// category: 2, lineStyle: { color: '#B9DCFF', type: "solid" }
symbolSize: 30,
value: 6,
symbol: `image://${company}`,
x: 250,
y: 400
}, },
{ {
id: 13, source: 6, target: 0,
name: "嘉源科技", label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '持股' },
// category: 1, lineStyle: { color: '#B9DCFF', type: "solid" }
symbolSize: 30,
value: 6,
symbol: `image://${company}`,
x: 450,
y: 400
}, },
{ {
id: 14, source: 7, target: 0,
name: "华阳集团", label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '合作' },
// category: 4, lineStyle: { color: '#B9DCFF', type: "solid" }
symbolSize: 30,
value: 7,
symbol: `image://${company}`,
x: 550,
y: 400
}, },
{ {
id: 15, source: 8, target: 0,
name: "海辰智能", label: { show: true, color: "#055FC2", backgroundColor: "#E7F3FF", borderWidth: 0, offset: [0, 15], formatter: '合作' },
// category: 1, lineStyle: { color: '#B9DCFF', type: "solid" }
symbolSize: 30,
value: 7,
symbol: `image://${company}`,
x: 650,
y: 400
}, },
], ],
// 关系数据
links: [
{ source: 1, target: 7, label: { show: true, formatter: '合作' } },
{ source: 2, target: 7, label: { show: true, formatter: '持股' } },
{ source: 3, target: 7, label: { show: true, formatter: '合作' } },
{ source: 4, target: 7, lineStyle: { type: 'dashed', color: '#d32f2f' }, label: { show: true, formatter: '从属' } },
{ source: 5, target: 7, label: { show: true, formatter: '合作' } },
{ source: 6, target: 7, label: { show: true, formatter: '持股' } },
{ source: 0, target: 7, label: { show: true, formatter: '持股' } },
{ source: 8, target: 7, label: { show: true, formatter: '合作' } },
{ source: 9, target: 7, lineStyle: { type: 'dashed', color: '#d32f2f' }, label: { show: true, formatter: '从属' } },
{ source: 10, target: 7, lineStyle: { type: 'dashed', color: '#d32f2f' }, label: { show: true, formatter: '合作' } },
{ source: 11, target: 7, label: { show: true, formatter: '合作' } },
{ source: 12, target: 7, label: { show: true, formatter: '合作' } },
{ source: 13, target: 7, label: { show: true, formatter: '合作' } },
{ source: 14, target: 7, label: { show: true, formatter: '合作' } },
{ source: 15, target: 7, label: { show: true, formatter: '合作', color: 'red', borderColor: 'red' } },
],
}; };
// 受影响实体 // 受影响实体
...@@ -374,7 +244,7 @@ const handleGetHylyList = async () => { ...@@ -374,7 +244,7 @@ const handleGetHylyList = async () => {
}; };
// 产业链/实体关系 // 产业链/实体关系
const contentType = ref(2); const contentType = ref(1);
const headerContentType = (type) => { const headerContentType = (type) => {
contentType.value = type; contentType.value = type;
}; };
...@@ -693,7 +563,7 @@ onMounted(() => { ...@@ -693,7 +563,7 @@ onMounted(() => {
align-items: flex-end; align-items: flex-end;
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 0 16px; padding: 0 20px;
.title-left { .title-left {
display: flex; display: flex;
border: 1px solid rgb(5, 95, 194); border: 1px solid rgb(5, 95, 194);
...@@ -734,6 +604,14 @@ onMounted(() => { ...@@ -734,6 +604,14 @@ onMounted(() => {
.box2-main { .box2-main {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex;
flex-direction: column;
padding: 16px 20px;
.graph-box {
height: 20px;
flex: auto;
margin-top: 16px;
}
} }
} }
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论