Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
R
risk-monitor
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
蔡建
risk-monitor
Commits
83444d70
提交
83444d70
authored
4月 08, 2026
作者:
闫鹏
浏览文件
操作
浏览文件
下载
差异文件
合并分支 'yp-dev' 到 'pre'
Yp dev 查看合并请求
!312
上级
199fd9dd
8aa2bdf8
流水线
#351
已通过 于阶段
in 2 分 20 秒
变更
3
流水线
1
显示空白字符变更
内嵌
并排
正在显示
3 个修改的文件
包含
1360 行增加
和
22 行删除
+1360
-22
index.vue
...inance/v2.0EntityList/components/dataStatistics/index.vue
+139
-12
constrainedAssociation.vue
...mponents/deepMining/components/constrainedAssociation.vue
+1189
-0
index.vue
...ws/finance/v2.0EntityList/components/deepMining/index.vue
+32
-10
没有找到文件。
src/views/finance/v2.0EntityList/components/dataStatistics/index.vue
浏览文件 @
83444d70
...
...
@@ -376,29 +376,156 @@ const getRegionCountData = async () => {
};
// 实体清单-数据统计- 制裁实体领域数量变化趋势
const
domainNumChartOption
=
ref
({});
const
domainNumChartOption
=
ref
({
color
:
[],
tooltip
:
{
trigger
:
"axis"
,
backgroundColor
:
"rgba(255, 255, 255, 0.9)"
,
textStyle
:
{
color
:
"#666"
},
extraCssText
:
"box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); border-radius: 4px;"
},
grid
:
{
top
:
"15%"
,
right
:
"2%"
,
bottom
:
"5%"
,
left
:
"2%"
,
containLabel
:
true
},
legend
:
{
type
:
"scroll"
,
show
:
true
,
top
:
0
,
left
:
"center"
,
icon
:
"circle"
,
itemWidth
:
12
,
itemHeight
:
12
,
data
:
[],
textStyle
:
{
fontFamily
:
"Microsoft YaHei"
,
fontSize
:
16
,
fontWeight
:
400
,
lineHeight
:
24
,
color
:
"rgb(95, 101, 108)"
}
},
xAxis
:
[
{
type
:
"category"
,
boundaryGap
:
false
,
data
:
[],
axisLine
:
{
show
:
false
},
axisTick
:
{
show
:
false
},
axisLabel
:
{
color
:
"#999"
,
fontSize
:
12
,
margin
:
15
}
}
],
yAxis
:
[
{
type
:
"value"
,
min
:
0
,
// max: 100,
// interval: 20,
axisLine
:
{
show
:
false
},
axisTick
:
{
show
:
false
},
axisLabel
:
{
color
:
"#999"
,
fontSize
:
12
},
splitLine
:
{
lineStyle
:
{
type
:
"dashed"
,
color
:
"#E0E6F1"
}
}
}
],
series
:
[]
});
const
activeDomainTab
=
ref
(
"year"
);
const
handleDomainTabChange
=
tab
=>
{
activeDomainTab
.
value
=
tab
;
getDomainNumData
();
};
// 处理领域趋势数据的方法
const
processDomainTrendData
=
rawData
=>
{
// 提取所有的月份作为标题
const
titles
=
rawData
.
map
(
item
=>
item
.
year
).
reverse
();
// 收集所有不重复的领域名称
const
domainNamesSet
=
new
Set
();
rawData
.
forEach
(
item
=>
{
item
.
domainCountInfo
.
forEach
(
domain
=>
{
domainNamesSet
.
add
(
domain
.
name
);
});
});
const
domainNames
=
Array
.
from
(
domainNamesSet
);
// 定义颜色映射
const
colorMap
=
{
人工智能
:
"#E34D59"
,
新一代通信网络
:
"#FF9F1C"
,
核
:
"#FFB3B3"
,
生物科技
:
"#00A79D"
,
量子科技
:
"#7B61FF"
,
先进制造
:
"#363B42"
,
新能源
:
"#2BA471"
,
太空
:
"#3762F0"
,
集成电路
:
"#0052D9"
,
新材料
:
"#FFD900"
,
航空航天
:
"#3762F0"
,
海洋
:
"#76D1FF"
,
深海
:
"#002060"
,
其他
:
"#A6A6A6"
};
// 生成数据系列
const
dataSeries
=
domainNames
.
map
(
domainName
=>
{
const
values
=
rawData
.
map
(
yearData
=>
{
const
domainItem
=
yearData
.
domainCountInfo
.
find
(
d
=>
d
.
name
===
domainName
);
return
domainItem
?
domainItem
.
count
:
0
;
})
.
reverse
();
// 数据值也需要跟随标题反转顺序
return
{
name
:
domainName
,
color
:
colorMap
[
domainName
]
||
`#
${
Math
.
floor
(
Math
.
random
()
*
16777215
).
toString
(
16
)}
`
,
// 如果没有预定义颜色,则随机生成
value
:
values
};
});
return
{
title
:
titles
,
data
:
dataSeries
};
};
const
getDomainNumData
=
async
()
=>
{
// 参数
const
param
=
{
IDsanTypeId
:
activeDomainTab
.
value
===
"year"
?
"year"
:
"record"
,
type
:
sanTypeId
.
value
countType
:
activeDomainTab
.
value
===
"year"
?
"year"
:
"record"
,
sanTypeId
:
sanTypeId
.
value
};
try
{
const
res
=
await
getDomainNum
(
param
);
if
(
res
&&
res
.
code
===
200
)
{
domainNumChartOption
.
value
=
getMultiLineChart
({
data
:
res
.
data
||
[],
xAxis
:
res
.
xAxis
||
[],
yAxis
:
res
.
yAxis
||
[],
title
:
"制裁实体领域数量变化趋势"
,
xAxisName
:
"时间"
,
yAxisName
:
"数量"
});
if
(
res
&&
res
.
length
>
0
)
{
const
processedData
=
processDomainTrendData
(
res
);
domainNumChartOption
.
value
=
getMultiLineChart
(
processedData
);
console
.
log
(
"获取实体清单-数据统计-processedData:"
,
processedData
);
console
.
log
(
"获取实体清单-数据统计-domainNumChartOption:"
,
domainNumChartOption
.
value
);
}
}
catch
(
error
)
{
console
.
error
(
"获取实体清单-数据统计-制裁实体领域数量变化趋势失败:"
,
error
);
...
...
src/views/finance/v2.0EntityList/components/deepMining/components/constrainedAssociation.vue
0 → 100644
浏览文件 @
83444d70
<
template
>
<div
class=
"main main-association"
>
<div
class=
"left"
>
<AnalysisBox
title=
"制裁历程"
>
<div
class=
"left-main"
>
<div
class=
"date-picker-box"
>
<el-date-picker
v-model=
"dateRange"
type=
"daterange"
range-separator=
"--"
start-placeholder=
"开始日期"
end-placeholder=
"结束日期"
value-format=
"YYYY-MM-DD"
style=
"width: 100%"
:clearable=
"false"
@
change=
"handleDateChange"
/>
</div>
<div
class=
"list-header"
>
<el-checkbox
v-model=
"isAllSelected"
:indeterminate=
"indeterminate"
@
change=
"handleCheckAllChange"
>
全选
</el-checkbox>
<div
class=
"count"
>
共
{{
sanctionList
.
length
}}
次制裁
</div>
<!-- 暂时隐藏,说这里可能是轮播图的效果 -->
<!--
<div
class=
"pagination"
>
<div
class=
"page-btn prev"
@
click=
"handlePrevClick"
>
<el-icon><ArrowLeft
/></el-icon>
</div>
<div
class=
"page-btn next"
@
click=
"handleNextClick"
>
<el-icon><ArrowRight
/></el-icon>
</div>
</div>
-->
</div>
<div
class=
"list-content"
v-loading=
"loading"
>
<div
class=
"list-item"
v-for=
"item in sanctionList"
:key=
"item.id"
:class=
"
{ active: currentSanctionId === item.id }"
@click="handleSanctionSelect(item.id)"
>
<el-checkbox
v-model=
"item.checked"
@
change=
"val => handleCheckOneChange(val, item)"
@
click
.
stop
>
<span
class=
"item-label"
>
<span
class=
"item-left"
>
{{
item
.
date
}}
-
{{
item
.
title
}}
</span>
<span
class=
"item-right"
>
{{
item
.
count
}}{{
item
.
unit
}}
</span>
</span>
</el-checkbox>
<!--
<div
class=
"item-left"
>
{{
item
.
date
}}
-
{{
item
.
title
}}
</div>
<div
class=
"item-right"
>
{{
item
.
count
}}{{
item
.
unit
}}
</div>
-->
</div>
</div>
</div>
</AnalysisBox>
</div>
<div
class=
"right"
>
<AnalysisBox
title=
"制裁产业链时序图"
>
<template
#
header-btn
>
<el-select
v-model=
"selectedIndustryId"
placeholder=
"请选择"
class=
"industry-select"
@
change=
"
() =>
{
getFishboneData();
getCnEntityOnChainData();
}
"
>
<el-option
v-for=
"item in industryList"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
/>
</el-select>
</
template
>
<div
class=
"right-main"
>
<div
class=
"right-main-content"
>
<div
class=
"hintWrap"
>
<div
class=
"icon1"
></div>
<div
class=
"title"
>
2025年实体清单制裁范围扩大至芯片制造环节,为中国的芯片制造能力划定“技术天花板”,阻止其向更先进水平发展。制裁范围向上游设备和材料、下游先进封装以及关键工具(如EDA软件)延伸,意图瓦解中国构建自主可控产业链的努力。
</div>
<div
class=
"icon2Wrap"
>
<div
class=
"icon2"
></div>
</div>
</div>
<div
class=
"right-main-content-main"
>
<div
class=
"fishbone-wrapper"
>
<div
class=
"fishbone-scroll-container"
ref=
"scrollContainerRef"
>
<div
class=
"fishbone"
ref=
"fishboneRef"
v-if=
"fishboneDataList.length > 0"
>
<div
class=
"main-line"
:style=
"{ width: fishboneDataList.length * 200 + 300 + 'px' }"
>
<!-- 主轴上的标签 -->
<div
class=
"main-line-text"
v-for=
"(item, index) in mainLineLabels"
: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 }}
</div>
</div>
<!-- 奇数索引的数据组放在上方 -->
<div
v-for=
"(causeGroup, groupIndex) in getOddGroups(fishboneDataList)"
: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=
"defaultTitle || 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=
"defaultTitle || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{ item.name }}
</div>
</div>
</div>
</div>
<!-- 偶数索引的数据组放在下方 -->
<div
v-for=
"(causeGroup, groupIndex) in getEvenGroups(fishboneDataList)"
: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=
"defaultTitle || 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=
"defaultTitle || 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
class=
"right-main-content-footer"
>
<div
class=
"footer-item1"
>
<div
class=
"footer-item1-bottom"
>
<div
class=
"icon"
>
<img
src=
"../../../../assets/images/warning.png"
alt=
""
/>
</div>
<div
class=
"text"
>
{{
`中国企业${cnEntityOnChainData.upstreamInternalCount || 0}家(${formatRate(
cnEntityOnChainData.upstreamInternalRate
)}%),受制裁${cnEntityOnChainData.upstreamEntityCount || 0}家(${formatRate(
cnEntityOnChainData.upstreamEntityRate
)}%)`
}}
</div>
</div>
<div
class=
"footer-item1-top"
>
{{ "上游" }}
</div>
</div>
<div
class=
"footer-item2"
>
<div
class=
"footer-item2-bottom"
>
<div
class=
"icon"
>
<img
src=
"../../../../assets/images/warning.png"
alt=
""
/>
</div>
<div
class=
"text"
>
{{
`中国企业${cnEntityOnChainData.midstreamInternalCount || 0}家(${formatRate(
cnEntityOnChainData.midstreamInternalRate
)}%),受制裁${cnEntityOnChainData.midstreamEntityCount || 0}家(${formatRate(
cnEntityOnChainData.midstreamEntityRate
)}%)`
}}
</div>
</div>
<div
class=
"footer-item2-top"
>
{{ "中游" }}
</div>
</div>
<div
class=
"footer-item3"
>
<div
class=
"footer-item3-bottom"
>
<div
class=
"icon"
>
<img
src=
"../../../../assets/images/warning.png"
alt=
""
/>
</div>
<div
class=
"text"
>
{{
`中国企业${cnEntityOnChainData.downstreamInternalCount || 0}家(${formatRate(
cnEntityOnChainData.downstreamInternalRate
)}%),受制裁${cnEntityOnChainData.downstreamEntityCount || 0}家(${formatRate(
cnEntityOnChainData.downstreamEntityRate
)}%)`
}}
</div>
</div>
<div
class=
"footer-item3-top"
>
{{ "下游" }}
</div>
</div>
</div>
</div>
</div>
</AnalysisBox>
</div>
</div>
</template>
<
script
setup
>
import
{
ref
,
onMounted
,
nextTick
,
onUnmounted
,
computed
}
from
"vue"
;
import
{
ArrowLeft
,
ArrowRight
}
from
"@element-plus/icons-vue"
;
import
defaultTitle
from
"../../../assets/default-icon2.png"
;
import
{
getDeepMiningSelect
,
getDeepMiningIndustry
,
getDeepMiningIndustryFishbone
,
getDeepMiningIndustryEntity
}
from
"@/api/exportControlV2.0"
;
import
{
useRoute
}
from
"vue-router"
;
const
route
=
useRoute
();
// 存储选中的ID列表 (如果需要获取选中项,可以使用这个,或者直接遍历 sanctionList 找 checked=true 的)
const
selectedSanctionIds
=
ref
([]);
// 计算属性:是否全选
const
isAllSelected
=
computed
({
get
()
{
return
sanctionList
.
value
.
length
>
0
&&
sanctionList
.
value
.
every
(
item
=>
item
.
checked
);
},
set
(
val
)
{
// set 方法由 handleCheckAllChange 处理,这里主要是为了配合 v-model 的读取
}
});
// 全选/取消全选
const
handleCheckAllChange
=
val
=>
{
sanctionList
.
value
.
forEach
(
item
=>
{
item
.
checked
=
val
;
});
updateSelectedIds
();
};
// 单个选中变化
const
handleCheckOneChange
=
(
val
,
item
)
=>
{
item
.
checked
=
val
;
updateSelectedIds
();
};
// 点击行触发复选框切换
const
handleItemClick
=
item
=>
{
item
.
checked
=
!
item
.
checked
;
updateSelectedIds
();
};
// 更新选中ID数组(供外部业务使用,如获取选中数据进行查询等)
const
updateSelectedIds
=
()
=>
{
selectedSanctionIds
.
value
=
sanctionList
.
value
.
filter
(
item
=>
item
.
checked
).
map
(
item
=>
item
.
id
);
// 如果业务需要:当选中项变化时,可能需要触发右侧图表更新?
// 原逻辑是点击选中一个就更新右侧。现在如果是多选,右侧图表如何展示?
// 假设:如果只选中了一个,更新右侧;如果选中多个或没选中,保持现状或显示提示?
// 这里暂时保留原逻辑的触发点,但建议根据实际需求调整。
// 例如:只有当选中项为1个时,才调用 getFishboneData
if
(
selectedSanctionIds
.
value
.
length
===
1
)
{
currentSanctionId
.
value
=
selectedSanctionIds
.
value
[
0
];
getFishboneData
();
getCnEntityOnChainData
();
}
else
if
(
selectedSanctionIds
.
value
.
length
===
0
)
{
// 可选:清空右侧或显示默认状态
}
};
// 计算属性:是否半选(不确定状态)
const
indeterminate
=
computed
(()
=>
{
const
checkedCount
=
sanctionList
.
value
.
filter
(
item
=>
item
.
checked
).
length
;
return
checkedCount
>
0
&&
checkedCount
<
sanctionList
.
value
.
length
;
});
// 实体清单-深度挖掘-产业链中国企业实体信息查询
const
getCnEntityOnChainData
=
async
()
=>
{
const
currentSanction
=
sanctionList
.
value
.
find
(
item
=>
item
.
id
===
currentSanctionId
.
value
);
const
date
=
currentSanction
?
currentSanction
.
date
:
""
;
// 确保 date 格式正确
const
formattedDate
=
date
&&
date
.
includes
(
"年"
)
?
date
.
replace
(
"年"
,
"-"
).
replace
(
"月"
,
"-"
).
replace
(
"日"
,
""
)
:
date
;
const
params
=
{
date
:
formattedDate
};
if
(
selectedIndustryId
.
value
)
{
params
.
chainId
=
selectedIndustryId
.
value
;
}
try
{
const
res
=
await
getDeepMiningIndustryEntity
(
params
);
if
(
res
.
code
===
200
&&
res
.
data
)
{
cnEntityOnChainData
.
value
=
res
.
data
;
}
else
{
cnEntityOnChainData
.
value
=
{};
}
}
catch
(
error
)
{
console
.
error
(
"获取产业链中国企业实体信息失败:"
,
error
);
cnEntityOnChainData
.
value
=
{};
}
};
// 实体清单-深度挖掘-产业链鱼骨图信息
const
fishboneDataList
=
ref
([]);
const
getFishboneData
=
async
()
=>
{
const
currentSanction
=
sanctionList
.
value
.
find
(
item
=>
item
.
id
===
currentSanctionId
.
value
);
const
date
=
currentSanction
?
currentSanction
.
date
:
""
;
// 确保 date 格式正确
const
formattedDate
=
date
&&
date
.
includes
(
"年"
)
?
date
.
replace
(
"年"
,
"-"
).
replace
(
"月"
,
"-"
).
replace
(
"日"
,
""
)
:
date
;
const
params
=
{
date
:
formattedDate
};
if
(
selectedIndustryId
.
value
)
{
params
.
chainId
=
selectedIndustryId
.
value
;
}
try
{
const
res
=
await
getDeepMiningIndustryFishbone
(
params
);
if
(
res
.
code
===
200
&&
res
.
data
&&
res
.
data
.
causes
&&
res
.
data
.
causes
.
length
>
0
)
{
const
rootCauses
=
res
.
data
.
causes
;
if
(
rootCauses
.
length
>
0
&&
rootCauses
[
0
].
causes
)
{
fishboneDataList
.
value
=
rootCauses
.
map
(
group
=>
{
return
{
causes
:
group
.
causes
||
[]
};
});
mainLineLabels
.
value
=
rootCauses
.
map
(
group
=>
group
.
text
||
""
);
}
else
{
fishboneDataList
.
value
=
[];
mainLineLabels
.
value
=
[];
}
}
else
{
fishboneDataList
.
value
=
[];
mainLineLabels
.
value
=
[];
}
}
catch
(
error
)
{
console
.
error
(
"获取产业链鱼骨图数据失败:"
,
error
);
fishboneDataList
.
value
=
[];
}
};
// 实体清单-深度挖掘-产业链列表信息
const
industryList
=
ref
([]);
const
selectedIndustryId
=
ref
(
null
);
const
getIndustryList
=
async
()
=>
{
try
{
const
res
=
await
getDeepMiningIndustry
();
if
(
res
.
code
===
200
&&
res
.
data
&&
res
.
data
.
length
>
0
)
{
industryList
.
value
=
res
.
data
;
selectedIndustryId
.
value
=
res
.
data
[
0
].
id
;
getFishboneData
();
getCnEntityOnChainData
();
}
else
{
industryList
.
value
=
[];
selectedIndustryId
.
value
=
null
;
}
}
catch
(
error
)
{
console
.
error
(
"获取产业链列表数据失败:"
,
error
);
industryList
.
value
=
[];
selectedIndustryId
.
value
=
null
;
}
};
// 获取选择制裁
const
loading
=
ref
(
false
);
const
currentPage
=
ref
(
1
);
const
pageSize
=
ref
(
100
);
const
total
=
ref
(
0
);
const
totalPage
=
ref
(
0
);
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
]
:
""
,
// typeName: "实体清单",
isCn
:
false
,
pageNum
:
currentPage
.
value
,
pageSize
:
pageSize
.
value
,
sanTypeIds
:
[
Number
(
sanTypeId
.
value
)]
||
1
// 实体清单固定1
};
try
{
const
res
=
await
getDeepMiningSelect
(
params
);
if
(
res
.
code
===
200
&&
res
.
data
&&
res
.
data
.
content
)
{
sanctionList
.
value
=
res
.
data
.
content
.
map
(
item
=>
({
id
:
item
.
id
,
date
:
item
.
postDate
,
title
:
item
.
name
,
count
:
item
.
cnEntityCount
,
unit
:
"家中国实体"
,
// 接口未返回单位,暂时固定
summary
:
item
.
summary
,
// 保留额外信息备用
techDomainList
:
item
.
techDomainList
// 保留额外信息备用
}))
.
reverse
();
// 默认选中第一条
if
(
sanctionList
.
value
.
length
>
0
)
{
currentSanctionId
.
value
=
sanctionList
.
value
[
0
].
id
;
// getFishboneData(); // 这里不需要调用,因为getIndustryList会调用
}
}
else
{
sanctionList
.
value
=
[];
}
}
catch
(
error
)
{
console
.
error
(
"获取选择制裁数据失败:"
,
error
);
sanctionList
.
value
=
[];
}
finally
{
loading
.
value
=
false
;
}
};
// 日期选择变化
const
handleDateChange
=
()
=>
{
currentPage
.
value
=
1
;
getDeepMiningSelectData
();
};
// ✅ 自动轮播定时器
const
autoPlayTimer
=
ref
(
null
);
// ✅ 自动下一个(支持循环)
const
handleNextClickAuto
=
()
=>
{
const
currentIndex
=
sanctionList
.
value
.
findIndex
(
item
=>
item
.
id
===
currentSanctionId
.
value
);
let
nextItem
;
if
(
currentIndex
<
sanctionList
.
value
.
length
-
1
)
{
nextItem
=
sanctionList
.
value
[
currentIndex
+
1
];
}
else
{
nextItem
=
sanctionList
.
value
[
0
];
// 循环到第一个
}
if
(
nextItem
)
{
handleSanctionSelect
(
nextItem
.
id
);
}
};
const
handleSanctionSelect
=
id
=>
{
currentSanctionId
.
value
=
id
;
getFishboneData
();
getCnEntityOnChainData
();
};
const
activeTab
=
ref
([
"制裁时序分析"
,
"限制关联分析"
]);
const
activeIndex
=
ref
(
0
);
const
dateRange
=
ref
([
"2025-01-01"
,
"2025-12-31"
]);
const
sanctionList
=
ref
([]);
const
currentSanctionId
=
ref
(
5
);
const
cnEntityOnChainData
=
ref
({});
const
mainLineLabels
=
ref
([
"关键原材料"
,
"电池材料"
,
"电子元器件"
,
"动力电池"
,
"电子控制系统"
,
"动力电池"
]);
// 获取奇数索引的数据组(放在上方)
const
getOddGroups
=
data
=>
{
return
data
.
filter
((
_
,
index
)
=>
index
%
2
!==
0
);
};
// 获取偶数索引的数据组(放在下方)
const
getEvenGroups
=
data
=>
{
return
data
.
filter
((
_
,
index
)
=>
index
%
2
===
0
);
};
// 获取上方鱼骨图位置类名
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
getLeftItems
=
items
=>
{
const
midpoint
=
Math
.
ceil
(
items
.
length
/
2
);
return
items
.
slice
(
0
,
midpoint
);
};
// 获取右侧显示的项目(后半部分)
const
getRightItems
=
items
=>
{
const
midpoint
=
Math
.
ceil
(
items
.
length
/
2
);
return
items
.
slice
(
midpoint
);
};
// 格式化比率
const
formatRate
=
rate
=>
{
if
(
rate
===
undefined
||
rate
===
null
)
return
"0.00"
;
return
(
rate
*
100
).
toFixed
(
2
);
};
const
sanTypeId
=
ref
(
""
);
onMounted
(()
=>
{
// 获取路由参数中的sanTypeId
sanTypeId
.
value
=
route
.
query
.
sanTypeId
||
""
;
// 获取选择制裁
getDeepMiningSelectData
();
// 获取产业链信息
getIndustryList
();
});
</
script
>
<
style
lang=
"scss"
scoped
>
.main
{
width
:
100%
;
padding-top
:
16px
;
padding-bottom
:
50px
;
display
:
flex
;
justify-content
:
space-between
;
.left
{
width
:
480px
;
height
:
828px
;
.left-main
{
margin-top
:
11px
;
padding
:
0
22px
0
23px
;
display
:
flex
;
flex-direction
:
column
;
height
:
calc
(
100%
-
56px
);
.date-picker-box
{
margin-bottom
:
16px
;
}
.list-header
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
margin-bottom
:
16px
;
font-size
:
14px
;
color
:
#666
;
.count
{
font-size
:
16px
;
font-weight
:
400
;
font-family
:
"Microsoft YaHei"
;
color
:
rgb
(
95
,
101
,
108
);
}
.pagination
{
display
:
flex
;
gap
:
12px
;
.page-btn
{
width
:
28px
;
height
:
28px
;
background
:
rgba
(
231
,
243
,
255
,
1
);
border-radius
:
4px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
cursor
:
pointer
;
color
:
rgb
(
5
,
95
,
194
);
font-size
:
16px
;
&
.disabled
{
cursor
:
not
-
allowed
;
background
:
#f5f7fa
;
color
:
#c0c4cc
;
}
&
:not
(
.disabled
)
:hover
{
background
:
#e1eeff
;
}
}
}
}
.list-content
{
flex
:
1
;
overflow-y
:
auto
;
padding-bottom
:
20px
;
&
:
:-
webkit-scrollbar
{
width
:
6px
;
}
&
:
:-
webkit-scrollbar-thumb
{
background
:
#ccc
;
border-radius
:
3px
;
}
.list-item
{
// height: 60px;
border
:
1px
solid
rgb
(
234
,
236
,
238
);
border-radius
:
4px
;
margin-bottom
:
8px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
padding
:
15px
16px
;
cursor
:
pointer
;
transition
:
all
0
.3s
;
position
:
relative
;
background
:
#fff
;
.item-left
{
width
:
260px
;
font-weight
:
700
;
color
:
rgb
(
59
,
65
,
75
);
font-size
:
16px
;
font-family
:
"Microsoft YaHei"
;
}
.item-right
{
color
:
rgb
(
132
,
136
,
142
);
font-size
:
16px
;
font-weight
:
400
;
font-family
:
"Microsoft YaHei"
;
}
&
:hover
{
border-color
:
#055fc2
;
}
&
.active
{
border-color
:
rgb
(
5
,
95
,
194
);
background-color
:
rgba
(
246
,
250
,
255
,
1
);
.item-left
,
.item-right
{
color
:
rgb
(
5
,
95
,
194
);
}
&
:
:
after
{
content
:
""
;
position
:
absolute
;
right
:
0
;
top
:
10px
;
bottom
:
10px
;
width
:
4px
;
background-color
:
rgb
(
5
,
95
,
194
);
// border-radius: 4px 0 0 4px;
}
}
}
}
}
}
.right
{
width
:
1105px
;
height
:
828px
;
.right-main
{
margin-top
:
11px
;
height
:
calc
(
100%
-
56px
);
padding
:
0
16px
16px
16px
;
.right-main-content
{
height
:
100%
;
display
:
flex
;
flex-direction
:
column
;
.hintWrap
{
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
);
margin-bottom
:
9px
;
.icon1
{
width
:
19px
;
height
:
20px
;
background-image
:
url("../assets/ai.png")
;
background-size
:
100%
100%
;
flex-shrink
:
0
;
}
.title
{
color
:
rgb
(
5
,
95
,
194
);
font-size
:
16px
;
font-weight
:
400
;
line-height
:
24px
;
margin-left
:
13px
;
flex
:
1
;
}
.icon2Wrap
{
width
:
24px
;
height
:
24px
;
background-color
:
rgba
(
231
,
243
,
255
,
1
);
display
:
flex
;
justify-content
:
center
;
align-items
:
center
;
border-radius
:
12px
;
margin-left
:
20px
;
flex-shrink
:
0
;
.icon2
{
width
:
24px
;
height
:
24px
;
background-image
:
url("../assets/right.png")
;
background-size
:
100%
100%
;
}
}
}
.right-main-content-main
{
flex
:
1
;
// border: 1px solid #eaecee;
// border-radius: 4px;
// background: #f7f8f9;
position
:
relative
;
overflow
:
hidden
;
.fishbone-wrapper
{
position
:
relative
;
width
:
100%
;
height
:
100%
;
}
.fishbone-scroll-container
{
display
:
flex
;
align-items
:
center
;
width
:
100%
;
height
:
100%
;
overflow-x
:
auto
;
overflow-y
:
hidden
;
scrollbar-width
:
thin
;
scrollbar-color
:
rgba
(
144
,
202
,
249
,
0
.5
)
transparent
;
&
:
:-
webkit-scrollbar
{
height
:
6px
;
}
&
:
:-
webkit-scrollbar-track
{
background
:
transparent
;
}
&
:
:-
webkit-scrollbar-thumb
{
background-color
:
rgba
(
144
,
202
,
249
,
0
.5
);
border-radius
:
3px
;
}
}
.fishbone
{
position
:
relative
;
width
:
fit-content
;
height
:
100%
;
margin-top
:
40px
;
min-width
:
100%
;
padding-left
:
275px
;
margin-left
:
40px
;
.main-line
{
margin-top
:
280px
;
width
:
1888px
;
height
:
3px
;
background
:
rgb
(
230
,
231
,
232
);
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
padding
:
0
100px
;
// 虚线
&
:
:
after
{
content
:
""
;
position
:
absolute
;
top
:
0
;
left
:
0
;
width
:
100%
;
height
:
100%
;
// background: repeating-linear-gradient(to right, rgba(174, 208, 255, 1) 0, rgba(174, 208, 255, 1) 10px, transparent 10px, transparent 20px);
}
// 添加中间的文字块
.main-line-text
{
position
:
absolute
;
// top: -14px;
font-size
:
16px
;
color
:
#055fc2
;
font-weight
:
bold
;
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
);
}
}
}
}
.company-icon
{
width
:
16px
;
height
:
16px
;
margin
:
0
4px
;
object-fit
:
contain
;
}
.top-bone
{
position
:
absolute
;
top
:
20px
;
right
:
200px
;
width
:
3px
;
height
:
260px
;
background
:
rgb
(
230
,
231
,
232
);
transform
:
skew
(
30deg
);
z-index
:
1
;
.left-bone
{
color
:
#777
;
position
:
absolute
;
top
:
0
;
left
:
-150px
;
width
:
150px
;
height
:
50px
;
// overflow: hidden;
.left-bone-item
{
transform
:
skew
(
-30deg
);
height
:
45px
;
margin-bottom
:
2px
;
margin-top
:
2px
;
display
:
flex
;
justify-content
:
flex-end
;
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
{
color
:
#777
;
position
:
absolute
;
top
:
0
;
right
:
-150px
;
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
{
@extend
.top-bone
;
right
:
500px
;
}
.top-bone2
{
@extend
.top-bone
;
right
:
800px
;
}
.bottom-bone
{
position
:
absolute
;
top
:
280px
;
right
:
360px
;
width
:
3px
;
height
:
260px
;
background
:
rgb
(
230
,
231
,
232
);
transform
:
skew
(
-30deg
);
z-index
:
1
;
.left-bone
{
color
:
#777
;
position
:
absolute
;
top
:
50px
;
left
:
-150px
;
width
:
150px
;
height
:
260px
;
.left-bone-item
{
transform
:
skew
(
30deg
);
height
:
39px
;
margin-bottom
:
2px
;
margin-top
:
2px
;
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
{
color
:
#777
;
position
:
absolute
;
top
:
50px
;
right
:
-150px
;
width
:
150px
;
height
:
260px
;
.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
;
}
}
.right-main-content-footer
{
height
:
84px
;
margin-top
:
16px
;
display
:
flex
;
justify-content
:
space-between
;
.footer-item1
,
.footer-item2
,
.footer-item3
{
flex
:
1
;
display
:
flex
;
flex-direction
:
column
;
justify-content
:
flex-end
;
}
.footer-item1
{
.footer-item1-top
{
height
:
28px
;
text-align
:
center
;
line-height
:
28px
;
background
:
url("../../../../assets/images/bg3.png")
;
background-size
:
100%
100%
;
color
:
rgba
(
22
,
119
,
255
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
700
;
margin-top
:
15px
;
margin-right
:
-10px
;
// Negative margin to overlap/connect
position
:
relative
;
// Ensure z-index works if needed
z-index
:
1
;
}
.footer-item1-bottom
{
display
:
flex
;
justify-content
:
center
;
.icon
{
margin-top
:
9px
;
width
:
16px
;
height
:
16px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.text
{
margin-top
:
7px
;
margin-left
:
8px
;
height
:
22px
;
color
:
rgba
(
206
,
79
,
81
,
1
);
font-size
:
14px
;
}
}
}
.footer-item2
{
.footer-item2-top
{
height
:
28px
;
text-align
:
center
;
line-height
:
28px
;
background
:
url("../../../../assets/images/bg2.png")
;
background-size
:
100%
100%
;
color
:
rgba
(
19
,
168
,
168
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
700
;
margin-top
:
15px
;
margin-right
:
-10px
;
// Negative margin to connect with next item
margin-left
:
-10px
;
// Negative margin to connect with prev item
position
:
relative
;
z-index
:
1
;
}
.footer-item2-bottom
{
display
:
flex
;
justify-content
:
center
;
.icon
{
margin-top
:
9px
;
width
:
16px
;
height
:
16px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.text
{
margin-top
:
7px
;
margin-left
:
8px
;
height
:
22px
;
color
:
rgba
(
206
,
79
,
81
,
1
);
font-size
:
14px
;
}
}
}
.footer-item3
{
.footer-item3-top
{
height
:
28px
;
text-align
:
center
;
line-height
:
28px
;
background
:
url("../../../../assets/images/bg1.png")
;
background-size
:
100%
100%
;
color
:
rgba
(
146
,
84
,
222
,
1
);
font-family
:
Microsoft
YaHei
;
font-size
:
16px
;
font-weight
:
700
;
margin-top
:
15px
;
margin-left
:
-10px
;
// Negative margin to connect
position
:
relative
;
z-index
:
1
;
}
.footer-item3-bottom
{
display
:
flex
;
justify-content
:
center
;
.icon
{
margin-top
:
9px
;
width
:
16px
;
height
:
16px
;
img
{
width
:
100%
;
height
:
100%
;
}
}
.text
{
margin-top
:
7px
;
margin-left
:
8px
;
height
:
22px
;
color
:
rgba
(
206
,
79
,
81
,
1
);
font-size
:
14px
;
}
}
}
}
}
}
}
}
.main-association
{
justify-content
:
flex-start
!
important
;
gap
:
16px
;
}
</
style
>
src/views/finance/v2.0EntityList/components/deepMining/index.vue
浏览文件 @
83444d70
...
...
@@ -13,6 +13,7 @@
</div>
</div>
<div
class=
"main"
>
<div
v-if=
"activeIndex == 0"
>
<div
class=
"left"
>
<AnalysisBox
title=
"选择制裁"
>
<div
class=
"left-main"
>
...
...
@@ -88,7 +89,10 @@
<div
class=
"fishbone-wrapper"
>
<div
class=
"fishbone-scroll-container"
ref=
"scrollContainerRef"
>
<div
class=
"fishbone"
ref=
"fishboneRef"
v-if=
"fishboneDataList.length > 0"
>
<div
class=
"main-line"
:style=
"{ width: fishboneDataList.length * 200 + 300 + 'px' }"
>
<div
class=
"main-line"
:style=
"{ width: fishboneDataList.length * 200 + 300 + 'px' }"
>
<!-- 主轴上的标签 -->
<div
class=
"main-line-text"
...
...
@@ -117,7 +121,11 @@
v-for=
"(item, index) in getLeftItems(causeGroup.causes)"
:key=
"'left-' + index"
>
<img
:src=
"defaultTitle || item.picture"
alt=
""
class=
"company-icon"
/>
<img
:src=
"defaultTitle || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{ item.name }}
</div>
<div
class=
"line"
></div>
</div>
...
...
@@ -129,7 +137,11 @@
:key=
"'right-' + index"
>
<div
class=
"line"
></div>
<img
:src=
"defaultTitle || item.picture"
alt=
""
class=
"company-icon"
/>
<img
:src=
"defaultTitle || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{ item.name }}
</div>
</div>
</div>
...
...
@@ -148,7 +160,11 @@
v-for=
"(item, index) in getLeftItems(causeGroup.causes)"
:key=
"'left-bottom-' + index"
>
<img
:src=
"defaultTitle || item.picture"
alt=
""
class=
"company-icon"
/>
<img
:src=
"defaultTitle || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{ item.name }}
</div>
<div
class=
"line"
></div>
</div>
...
...
@@ -160,7 +176,11 @@
:key=
"'right-bottom-' + index"
>
<div
class=
"line"
></div>
<img
:src=
"defaultTitle || item.picture"
alt=
""
class=
"company-icon"
/>
<img
:src=
"defaultTitle || item.picture"
alt=
""
class=
"company-icon"
/>
<div
class=
"text"
:title=
"item.name"
>
{{ item.name }}
</div>
</div>
</div>
...
...
@@ -239,6 +259,10 @@
</AnalysisBox>
</div>
</div>
<div
v-if=
"activeIndex == 1"
>
<constrained-association
/>
</div>
</div>
</div>
</template>
...
...
@@ -252,6 +276,7 @@ import {
getDeepMiningIndustryFishbone
,
getDeepMiningIndustryEntity
}
from
"@/api/exportControlV2.0"
;
import
constrainedAssociation
from
"./components/constrainedAssociation.vue"
;
import
{
useRoute
}
from
"vue-router"
;
const
route
=
useRoute
();
...
...
@@ -461,7 +486,7 @@ const handleSanctionSelect = id => {
startAutoPlay
();
};
const
activeTab
=
ref
([
"制裁时序分析"
]);
const
activeTab
=
ref
([
"制裁时序分析"
,
"限制关联分析"
]);
const
activeIndex
=
ref
(
0
);
const
dateRange
=
ref
([
"2025-01-01"
,
"2025-12-31"
]);
...
...
@@ -532,10 +557,7 @@ onUnmounted(() => {
</
script
>
<
style
scoped
lang=
"scss"
>
*
{
margin
:
0
;
padding
:
0
;
}
.deep-mining
{
width
:
1601px
;
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论