提交 83444d70 authored 作者: 闫鹏's avatar 闫鹏

合并分支 'yp-dev' 到 'pre'

Yp dev 查看合并请求 !312
流水线 #351 已通过 于阶段
in 2 分 20 秒
......@@ -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);
......
<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>
......@@ -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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论