提交 c8abcacd authored 作者: 张烨's avatar 张烨

feat:科技政令增加受影响实体页面

上级 554cda8d
......@@ -2,7 +2,10 @@
<div class="analysis-box-wrapper" :style="{ width: width ? width : '100%', height: height ? height : '100%' }">
<div class="wrapper-header">
<div class="header-icon"></div>
<div class="header-title">{{ title }}</div>
<div class="header-title">
<div v-if="title">{{ title }}</div>
<slot v-else name="custom-title"></slot>
</div>
<div class="header-btn" v-if="!showAllBtn">
<slot name="header-btn"></slot>
</div>
......@@ -81,54 +84,45 @@ const emit = defineEmits(['save','download','collect'])
.wrapper-header {
height: 45px;
display: flex;
padding-right: 14px;
align-items: center;
box-sizing: border-box;
position: relative;
.header-icon {
margin-top: 18px;
width: 8px;
height: 20px;
background: var(--color-main-active);
border-radius: 0 4px 4px 0;
margin-right: 14px;
}
.header-title {
margin-left: 14px;
margin-top: 14px;
height: 26px;
flex: auto;
width: 20px;
// color: var(--color-main-active);
// font-family: Source Han Sans CN;
// font-size: 20px;
// font-weight: 700;
// line-height: 26px;
// letter-spacing: 0px;
height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
height: 100%;
&>div {
height: 100%;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
line-height: 45px;
font-weight: 700;
}
}
.header-btn {
position: absolute;
top: 14px;
right: 84px;
// display: flex;
// justify-content: flex-end;
// gap: 8px;
}
.header-btn1 {
position: absolute;
top: 14px;
right: 104px;
}
.header-right {
position: absolute;
top: 14px;
right: 14px;
height: 28px;
display: flex;
justify-content: flex-end;
......
<template>
<div class="wrap">
<div class="box1">
<AnalysisBox title="相关政令关联分析" :showAllBtn="false">
<AnalysisBox title="相关政令" :showAllBtn="false">
<div class="box1-main">
<div class="left">
<el-scrollbar height="100%" always>
<el-empty v-if="siderList.length === 0" style="padding-top: 240px" description="暂无数据" :image-size="100" />
<div class="left-item" :class="{ leftItemActive: siderActiveIndex === index }"
v-for="(item, index) in siderList" :key="index" @click="handleClickSider(index)">
<div class="time">{{ item.time }}</div>
<div class="title">{{ item.title }}</div>
</div>
</div>
<div class="right">
<div class="info-box">
<div class="info-left">
<img v-if="decreeInfo.img" :src="decreeInfo.img" alt="" />
<div v-else class="box1-main-left-img-mock">
<img class="img-mock-badge-img" src="./assets/icons/badge.png" />
<div class="img-mock-badge-title">{{ decreeInfo.eTotalTitle }}</div>
</div>
</div>
<div class="info-right">
<div class="info-item">
<div class="item-left">{{ "政令全称:" }}</div>
<div class="item-right1">
<div class="item-right-text">
{{ decreeInfo.totalTitle }}
</div>
<div class="item-right-icon" v-if="decreeInfo.totalTitle" @click="handleToDecreeDetail(decreeInfo)">
<img src="./assets/icons/open-icon.png" alt="" />
</div>
</div>
</div>
<div class="info-item">
<div class="item-left">{{ "英文全称:" }}</div>
<div class="item-right">
{{ decreeInfo.eTotalTitle }}
</div>
</div>
<div class="info-item">
<div class="item-left">{{ "签署时间:" }}</div>
<div class="item-right">
{{ decreeInfo.signTime }}
</div>
</div>
<div class="info-item">
<div class="item-left">{{ "发布机构:" }}</div>
<div class="item-right">
{{ decreeInfo.signOrg }}
</div>
</div>
</div>
</div>
<div class="list-box">
<div class="list-header">
<div class="icon">
<img src="./assets/icons/box1-list-header-icon.png" alt="" />
</div>
<div class="title">{{ "政令主要内容" }}</div>
</div>
<div class="list-main">
<el-empty v-if="showList.length === 0" style="padding-top: 150px" description="暂无数据"
:image-size="100" />
<div class="list-item" v-for="(val, idx) in showList" :key="idx">
<div class="id">{{ idx + 1 }}</div>
<div class="title">{{ val.content }}</div>
<!-- <div class="open">
<img src="./assets/icons/open-icon.png" alt="" />
</div> -->
</div>
<div class="left-item" :class="{ 'item-active': siderActiveIndex===index }" v-for="(item, index) in siderList" :key="index" @click="handleClickSider(index)">
<div class="item-head">
<div class="itme-name one-line-ellipsis">{{ item.title }}</div>
<div class="item-tag">政令</div>
</div>
<div class="itme-time one-line-ellipsis">{{ item.time }} · {{ "美国白宫" }} </div>
</div>
<div class="list-footer">
<div class="footer-left">
{{ `共 ${decreeInfo.list.length} 项` }}
</div>
<div class="footer-right">
<el-pagination @current-change="handleCurrentChange" :pageSize="pageSize" :current-page="currentPage"
background layout="prev, pager, next" :total="decreeInfo.list.length" />
</div>
</div>
</div>
</el-scrollbar>
</div>
</AnalysisBox>
</div>
<div class="box2">
<AnalysisBox title="政令关系挖掘" :showAllBtn="false">
<div class="box2-main">
</div>
</AnalysisBox>
</div>
......@@ -115,26 +53,22 @@ const showList = computed(() => {
});
const siderList = ref([
// {
// time: "",
// title: ""
// },
// {
// time: "2023",
// title: "拜登人工智能政令"
// },
// {
// time: "2025",
// title: "特朗普撤销拜登AI规则"
// },
// {
// time: "2023",
// title: "美国AI行动计划"
// },
// {
// time: "2024",
// title: "对中国AI芯片限制"
// }
{
time: "2023年7月25日",
title: "拜登人工智能政令拜登人工智能政令拜登人工智能政令拜登人工智能政令",
},
{
time: "2025年7月25日",
title: "特朗普撤销拜登AI规则"
},
{
time: "2023年7月25日",
title: "美国AI行动计划"
},
{
time: "2024年7月25日",
title: "对中国AI芯片限制"
}
]);
const siderActiveIndex = ref(0);
const handleClickSider = async index => {
......@@ -223,7 +157,7 @@ const handleGetRelateOrder = async () => {
} catch (error) { }
} else {
allData.value = [];
siderList.value = [];
// siderList.value = [];
decreeInfo.value.id = 0;
decreeInfo.value.img = "";
decreeInfo.value.totalTitle = "";
......@@ -264,372 +198,102 @@ onMounted(() => {
<style lang="scss" scoped>
.wrap {
height: 100%;
overflow: hidden;
.box-header {
height: 56px;
display: flex;
position: relative;
.header-left {
margin-top: 18px;
width: 8px;
height: 20px;
border-radius: 0 4px 4px 0;
background: var(--color-main-active);
}
.title {
margin-left: 14px;
margin-top: 14px;
height: 26px;
line-height: 26px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
}
.header-btn-box {
position: absolute;
top: 14px;
right: 52px;
display: flex;
.btn {
margin-left: 8px;
}
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
height: 28px;
display: flex;
gap: 4px;
.icon {
width: 28px;
height: 28px;
img {
width: 100%;
height: 100%;
}
}
}
}
min-height: 845px;
width: 1600px;
margin: 16px auto;
display: flex;
gap: 16px;
.box1 {
margin: 16px auto;
width: 1600px;
max-height: 898px;
min-height: 788px;
width: 480px;
.box1-main {
display: flex;
margin-top: 5px;
margin-bottom: 10px;
.left {
margin-left: 21px;
width: 300px;
.left-item {
width: 300px;
height: 64px;
border-radius: 4px;
padding: 10px 16px;
.left-item {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
border-bottom: 1px solid rgba(240, 242, 244, 1);
padding: 10px;
cursor: pointer;
&:first-child {
border-top: 1px solid rgba(240, 242, 244, 1);
}
&:hover {
background: rgba(246, 250, 255, 1);
}
.item-head {
display: flex;
align-items: center;
margin-bottom: 6px;
}
.itme-name {
width: 20px;
flex: auto;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-weight: 400;
cursor: pointer;
&:hover {
background: rgba(246, 250, 255, 1);
}
.time {
width: 45px;
height: 24px;
line-height: 24px;
margin-left: 18px;
margin-top: 20px;
}
.title {
width: 200px;
margin-left: 17px;
margin-top: 17px;
height: 30px;
line-height: 30px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
letter-spacing: 0px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
font-size: 16px;
font-weight: 600;
letter-spacing: 0px;
}
.leftItemActive {
.item-tag{
height: 24px;
line-height: 24px;
text-align: center;
padding: 0 8px;
border-radius: 12px;
background: rgba(231, 243, 255, 1);
color: var(--color-main-active);
font-weight: 700;
background: rgba(246, 250, 255, 1);
&::after {
position: relative;
content: "";
width: 5px;
height: 48px;
background: var(--color-main-active);
right: -15px;
top: 8px;
}
}
}
.right {
margin-left: 36px;
.info-box {
margin-left: 28px;
width: 1180px;
height: 188px;
box-sizing: border-box;
border: 1px solid rgba(231, 243, 255, 1);
background: rgba(246, 250, 255, 1);
display: flex;
.info-left {
width: 242px;
height: 136px;
margin-top: 25px;
margin-left: 28px;
img {
// width: 100%;
height: 100%;
}
.box1-main-left-img-mock {
width: 100%;
height: 100%;
border-radius: 4px;
background-color: #0b1932;
display: flex;
align-items: center;
flex-direction: column;
justify-content: space-around;
padding: 15px;
.img-mock-badge-img {
width: 50px;
height: 50px;
}
.img-mock-badge-title {
text-align: center;
font-size: 14px;
line-height: 20px;
color: #fff;
height: 40px;
display: -webkit-box;
/* 2. 设置排列方向为垂直 */
-webkit-box-orient: vertical;
/* 3. 设置显示的行数(这里设为2行) */
-webkit-line-clamp: 2;
/* 4. 处理溢出和换行 */
overflow: hidden;
text-overflow: ellipsis;
word-break: break-word;
}
// .img-mock-badge-org {
// text-align: center;
// font-size: 14px;
// color: #fff;
// }
}
}
.info-right {
margin-left: 20px;
margin-top: 22px;
.info-item {
display: flex;
min-height: 30px;
max-height: 60px;
margin-bottom: 8px;
.item-left {
// margin-top: 3px;
width: 100px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
}
.item-right {
width: 769px;
// height: 30px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 25px;
}
.item-right1 {
display: flex;
.item-right-text {
height: 30px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 30px;
}
.item-right-icon {
margin-left: 13px;
margin-top: 7px;
width: 16px;
height: 16px;
cursor: pointer;
img {
width: 100%;
height: 100%;
}
}
}
}
}
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
letter-spacing: 0px;
margin-left: 6px;
}
.list-box {
margin-left: 36px;
.list-header {
display: flex;
height: 56px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
.icon {
margin-top: 21px;
margin-left: 17px;
width: 19px;
height: 19px;
img {
width: 100%;
height: 100%;
}
}
.title {
margin-top: 16px;
margin-left: 16px;
height: 30px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 30px;
}
}
.list-main {
min-height: 420px;
max-height: 540px;
overflow-x: hidden;
overflow-y: auto;
.list-item {
width: 1180px;
min-height: 65px;
box-sizing: border-box;
border-radius: 4px;
background: rgba(255, 255, 255, 1);
border-bottom: 1px solid rgba(234, 236, 238, 1);
display: flex;
cursor: pointer;
&:hover {
background: var(--color-bg-hover);
}
.id {
width: 24px;
height: 24px;
margin-left: 15px;
margin-top: 15px;
border-radius: 12px;
background: #e7f3ff;
text-align: center;
line-height: 24px;
color: #0a57a6;
}
.title {
margin-left: 13px;
margin-top: 12px;
margin-bottom: 12px;
// height: 30px;
width: 1100px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 25px;
}
.open {
width: 16px;
height: 16px;
margin-top: 20px;
img {
width: 100%;
height: 100%;
}
}
}
}
.itme-time {
color: rgb(95, 101, 108);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 400;
letter-spacing: 0px;
}
}
.list-footer {
margin-left: 35px;
height: 32px;
margin-top: 10px;
display: flex;
justify-content: space-between;
.item-active {
background: rgba(246, 250, 255, 1);
position: relative;
.footer-left {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 32px;
}
&::after {
position: absolute;
content: "";
width: 5px;
height: 100%;
background: var(--color-main-active);
right: 0px;
top: 0px;
}
}
}
}
.box2 {
width: 20px;
flex: auto;
}
}
// 修改element-plus滚动条样式
:deep(.el-scrollbar__bar.is-vertical) {
right: 0px;
width: 4px;
background: transparent;
border-radius: 2px;
&>div {
background: #c5c7c9;
opacity: 1;
}
&>div:hover {
background: #505357;
}
}
</style>
......@@ -213,13 +213,13 @@ const mainHeaderBtnList = ref([
activeIcon: icon2Active,
name: "深度挖掘",
path: "/decreeLayout/deepDig"
},
{
icon: icon3,
activeIcon: icon3Active,
name: "影响分析",
path: "/decreeLayout/influence"
}
// {
// icon: icon3,
// activeIcon: icon3Active,
// name: "影响分析",
// path: "/decreeLayout/influence"
// }
]);
const activeTitle = ref("政令概况");
......@@ -304,7 +304,7 @@ onMounted(() => {
activeTitle.value = "政令概况";
} else if (route.path === "/decreeLayout/deepDig") {
activeTitle.value = "深度挖掘";
} else {
} else if (route.path === "/decreeLayout/influence") {
activeTitle.value = "影响分析";
}
handleGetReport();
......
<template>
<div class="view-box">
<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="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 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="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 class="right-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>
</div>
</template>
<script setup name="ChartChain">
import { ref, onMounted } from "vue";
import defaultIcon2 from "@/assets/icons/default-icon2.png";
import noticeIcon from "./assets/images/notice-icon.png";
import { getDeepMiningSelect, getDeepMiningIndustry, getDeepMiningIndustryFishbone, getDeepMiningIndustryEntity } from "@/api/exportControlV2.0";
// 实体清单-深度挖掘-产业链中国企业实体信息查询
const cnEntityOnChainData = ref({});
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);
console.log("企业信息", res)
if (res.code === 200 && res.data) {
cnEntityOnChainData.value = res.data;
} else {
cnEntityOnChainData.value = {};
}
} catch (error) {
console.error("获取产业链中国企业实体信息失败:", error);
cnEntityOnChainData.value = {};
}
}
// 实体清单-深度挖掘-产业链鱼骨图信息
const fishboneDataList = ref([]);
const mainLineLabels = 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);
console.log("获取产业链数据:", res);
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(10000);
const getDeepMiningSelectData = async () => {
loading.value = true;
const params = {
startDate: dateRange.value && dateRange.value[0] ? dateRange.value[0] : '',
endDate: dateRange.value && dateRange.value[1] ? dateRange.value[1] : '',
typeName: "实体清单",
isCn: false,
pageNum: currentPage.value,
pageSize: pageSize.value
};
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 // 保留额外信息备用
}));
// 默认选中第一条
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 dateRange = ref(["2025-01-01", "2025-12-31"]);
const sanctionList = ref([
{ id: 1, date: "2025年2月8日", title: "实体清单更新", count: 2, unit: "家中国实体" },
{ id: 2, date: "2025年4月10日", title: "实体清单更新", count: 5, unit: "家中国实体" },
{ id: 3, date: "2025年6月29日", title: "实体清单更新", count: 6, unit: "家中国实体" },
{ id: 4, date: "2025年8月12日", title: "实体清单更新", count: 24, unit: "家中国实体" },
{ id: 5, date: "2025年8月19日", title: "实体清单更新", count: 11, unit: "家中国实体" },
{ id: 6, date: "2025年9月12日", title: "实体清单更新", count: 3, unit: "家中国实体" },
{ id: 7, date: "2025年9月26日", title: "实体清单更新", count: 6, unit: "家中国实体" },
{ id: 8, date: "2025年10月12日", title: "实体清单更新", count: 18, unit: "家中国实体" }
]);
const currentSanctionId = ref(5);
// 获取奇数索引的数据组(放在上方)
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, ratio=false) => {
if (!rate) return '0.00';
return (rate * 100).toFixed(2);
};
onMounted(() => {
// 获取选择制裁
getDeepMiningSelectData();
// 获取产业链信息
getIndustryList();
});
</script>
<style scoped lang="scss">
.view-box {
width: 100%;
height: 100%;
}
.right-main {
height: 100%;
padding: 11px 16px 20px;
.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/images/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/images/right.png");
background-size: 100% 100%;
}
}
}
.right-main-content-main {
flex: 1;
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 {
margin-top: 16px;
display: flex;
justify-content: space-between;
.footer-item {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.footer-item {
.footer-item-top {
height: 28px;
text-align: center;
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;
.text {
color: rgba(206, 79, 81, 1);
font-size: 14px;
line-height: 14px;
margin-left: 6px;
}
.icon {
width: 16px;
height: 16px;
font-size: 0;
img {
width: 100%;
height: 100%;
}
}
}
}
.footer-item1 {
color: rgba(22, 119, 255, 1);
.footer-item-top {
background: rgba(231, 243, 255, 1);
}
}
.footer-item2 {
color: rgba(19, 168, 168, 1);
.footer-item-top {
background: rgba(225, 255, 251, 1);
}
}
.footer-item3 {
color: rgba(146, 84, 222, 1);
.footer-item-top {
background: rgba(246, 235, 255, 1);
}
}
}
}
}
</style>
\ No newline at end of file
<template>
<div class="relation-graph-wrapper">
<div class="graph-controls">
<div v-for="item in controlBtns" :key="item.type"
:class="['control-btn', { 'control-btn-active': currentLayoutType === item.type }]"
@click="handleClickControlBtn(item.type)">
<img :src="item.icon" alt="" />
</div>
</div>
<div ref="containerRef" class="graph-container"></div>
<div v-if="selectedNode" class="node-popup">
<div class="popup-header">
<img :src="selectedNode.image || echartsIcon03" alt="" class="popup-icon" />
<div class="popup-title">{{ selectedNode.name }}</div>
<el-icon class="close-icon" @click="selectedNode = null">
<Close />
</el-icon>
</div>
<div class="popup-body">
<div v-if="selectedNode.isSanctioned" class="tag-row">
<span class="red-dot"></span>
<span class="red-text">被制裁实体</span>
</div>
<p class="desc">{{ selectedNode.description || '暂无描述' }}</p>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
import G6 from '@antv/g6'
import { Close } from '@element-plus/icons-vue'
import echartsIcon01 from './assets/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 selectedNode = ref(null)
const controlBtns = [
{ type: 1, icon: echartsIcon01, name: '力导向布局' },
{ type: 2, icon: echartsIcon02, name: '树布局' },
{ type: 3, icon: echartsIcon03, name: '环状布局' }
]
const initGraph = (layoutType = 1) => {
if (!containerRef.value) return
destroyGraph()
nextTick(() => {
const 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()
selectedNode.value = model
emit('nodeClick', model)
})
graphInstance.value.on('canvas:click', () => {
selectedNode.value = null
})
}
const handleClickControlBtn = (btn) => {
currentLayoutType.value = btn
emit('layoutChange', btn)
initGraph(btn)
}
const destroyGraph = () => {
if (graphInstance.value) {
graphInstance.value.destroy()
graphInstance.value = null
}
}
const handleResize = () => {
if (graphInstance.value && containerRef.value) {
const width = containerRef.value.offsetWidth
const height = containerRef.value.offsetHeight
graphInstance.value.changeSize(width, height)
graphInstance.value.fitView()
}
}
watch(
() => props.graphData,
() => {
initGraph(currentLayoutType.value)
}
)
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);
}
}
.node-popup {
position: absolute;
bottom: 16px;
left: 16px;
width: 320px;
background: rgba(255, 255, 255, 1);
border-radius: 8px;
box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(234, 236, 238, 1);
z-index: 20;
.popup-header {
display: flex;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
.popup-icon {
width: 32px;
height: 32px;
margin-right: 8px;
border-radius: 50%;
object-fit: cover;
}
.popup-title {
flex: 1;
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
color: rgba(59, 65, 75, 1);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.close-icon {
cursor: pointer;
color: rgba(132, 136, 142, 1);
font-size: 16px;
&:hover {
color: rgba(5, 95, 194, 1);
}
}
}
.popup-body {
padding: 12px 16px;
.tag-row {
display: flex;
align-items: center;
margin-bottom: 8px;
.red-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: rgba(245, 63, 63, 1);
margin-right: 8px;
}
.red-text {
font-size: 14px;
font-family: "Microsoft YaHei";
color: rgba(245, 63, 63, 1);
}
}
.desc {
font-size: 14px;
font-family: "Microsoft YaHei";
line-height: 22px;
color: rgba(95, 101, 108, 1);
margin: 0;
}
}
}
</style>
\ No newline at end of file
<template>
<div class="wrapper">
<div class="left">
<div class="box1">
<div class="box-header">
<div class="icon"></div>
<div class="title">{{ "涉及行业" }}</div>
<div class="header-right1">
<el-checkbox v-model="isCRelated" label="只看中国企业" size="large" />
</div>
<div class="header-right">
<div class="header-right-icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
<div class="box1">
<AnalysisBox title="受影响实体" :showAllBtn="false">
<div class="box1-main">
<div class="data-filter">
<div class="filter-select">
<el-select v-model="curAreaId" :empty-values="[null, undefined]" style="width: 100%">
<el-option label="全部领域" value="" />
<el-option v-for="item in areaList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</div>
<div class="header-right-icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
<div class="filter-input">
<el-input v-model="commandWord" @keyup.enter="handleSearch" style="width: 100%; height: 100%;" :suffix-icon="Search" placeholder="搜索实体" />
</div>
</div>
</div>
<div class="box1-top" id="chart1"></div>
<div class="box1-tab-box">
<div
class="tab"
:class="{ tabActive: box1BtnActiveName === item.name }"
v-for="(item, index) in box1BtnList"
:key="index"
@click="handleClickBox1Btn(item)"
>
{{ item.name }}
<div class="data-title">实体名称</div>
<div style="height: 20px; flex: auto;">
<el-scrollbar height="100%" always>
<el-empty v-if="showCompanyList.length === 0" style="padding-top: 240px" description="暂无数据" :image-size="100" />
<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="item-icon">
<img :src="defaultIcon2" alt="" class="item-img" />
</div>
<div class="item-name one-line-ellipsis">{{ item.name }}</div>
<div class="item-icon item-icon-tag">
<img :src="noticeIcon" alt="" class="item-img" />
</div>
<div class="item-tag">提及</div>
</div>
</div>
</el-scrollbar>
</div>
</div>
<div class="box1-list-box">
<div class="box1-item" v-for="(item, index) in showCompanyList" :key="index" @click="handleToCompanyDetail">
<div class="id">{{ index + 1 }}</div>
<div class="title">{{ item.name }}</div>
<div class="icon">
<img v-if="item.status >= 0" src="./assets/images/up.png" alt="" />
<img v-else src="./assets/images/down.png" alt="" />
<div class="pagination-info">
<div class="pagination-left">{{ `共 ${companyTotalNum} 家企业` }}</div>
<div class="pagination-right">
<el-pagination
@current-change="handleCurrentChange"
:pageSize="pageSize"
:current-page="currentPage"
background
layout="prev, pager, next"
size="small"
:total="companyTotalNum"
/>
</div>
</div>
</div>
<div class="box1-footer">
<div class="box1-footer-left">{{ `共 ${companyTotalNum} 项` }}</div>
<div class="box1-footer-right">
<el-pagination
@current-change="handleCurrentChange"
:pageSize="pageSize"
:current-page="currentPage"
background
layout="prev, pager, next"
size="small"
:total="companyTotalNum"
/>
</div>
</div>
</div>
</AnalysisBox>
</div>
<div class="right">
<div class="box2">
<div class="box-header">
<div class="icon"></div>
<div class="title">{{ "产业链分析" }}</div>
<div class="header-right">
<div class="header-right-icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
</div>
<div class="header-right-icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="header-right-icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box2-main">
<div class="box2-main-header">
<div class="box2-main-header-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="" />
</div>
<div class="box2-main-header-center">
{{
"法案核心意图在于通过税收优惠吸引制造业回流美国​,并在关键科技领域对中国进行遏制,限制中国获取先进技术、资本和市场渠道,从而延缓中国科技产业的发展速度。给半导体、新能源、人工智能等相关科技行业带来不小的短期压力。"
}}
</div>
<div class="box2-main-header-right">
<img src="@/assets/icons/box-footer-right-icon.png" alt="" />
</div>
</div>
<div class="box2-main-main">
<Fishbone :chainId="chainId" />
</div>
<div class="box2-main-footer">
<div class="box2-main-footer-left">
<div class="top">
<div class="icon">
<img src="@/assets/icons/warning.png" alt="" />
</div>
<div class="text">
{{
`中国企业${chainInfo.upstreamInternalCount}家(${
Number(chainInfo.upstreamInternalRate * 100).toFixed(2)
}%),受制裁${chainInfo.upstreamEntityCount}家(${Number(chainInfo.upstreamEntityRate * 100).toFixed(2)}%)`
}}
<div class="box2">
<AnalysisBox :showAllBtn="false">
<template #custom-title>
<div class="custom-title">
<div class="title-left">
<div :class="['title-item', {'title-active': contentType==1}]" @click="headerContentType(1)">
<div class="title-icon">
<img :src="contentType==1 ? icon1620 : icon1621" alt="">
</div>
<div>产业链</div>
</div>
<div class="bottom">{{ "基础支撑" }}</div>
</div>
<div class="box2-main-footer-center">
<div class="top">
<div class="icon">
<img src="@/assets/icons/warning.png" alt="" />
</div>
<div class="text">
{{
`中国企业${chainInfo.midstreamInternalCount}家(${
Number(chainInfo.midstreamInternalRate * 100).toFixed(2)
}%),受制裁${chainInfo.midstreamEntityCount}家(${
Number(chainInfo.midstreamEntityRate * 100).toFixed(2)
}%)`
}}
<div :class="['title-item', {'title-active': contentType==2}]" @click="headerContentType(2)">
<div class="title-icon">
<img :src="contentType==2 ? icon422 : icon423" alt="">
</div>
<div>实体关系</div>
</div>
<div class="bottom">{{ "软件算法" }}</div>
</div>
<div class="box2-main-footer-right">
<div class="top">
<div class="icon">
<img src="@/assets/icons/warning.png" alt="" />
</div>
<div class="text">
{{
`中国企业${chainInfo.downstreamInternalCount}家(${
Number(chainInfo.downstreamInternalRate * 100).toFixed(2)
}%),受制裁${chainInfo.downstreamEntityCount}家(${
Number(chainInfo.downstreamEntityRate * 100).toFixed(2)
}%)`
}}
</div>
</div>
<div class="bottom">{{ "行业应用" }}</div>
<div class="title-right" v-if="contentType==1">
<el-select v-model="curAreaId" :empty-values="[null, undefined]" style="width: 100%">
<el-option label="全部领域" value="" />
<el-option v-for="item in areaList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</div>
</div>
</template>
<div class="box2-main" v-if="contentType==1">
<ChartChain />
</div>
<div class="box2-main" v-if="contentType==2">
<ChartRelation
:graph-data="graphData"
:tree-data="treeData"
:control-active="1"
@node-click="handleNodeClick"
@layout-change="handleLayoutChange"
/>
</div>
</div>
</AnalysisBox>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from "vue";
import Fishbone from "./fishbone.vue";
import { useRoute } from "vue-router";
import router from "@/router";
import setChart from "@/utils/setChart";
import { Search } from '@element-plus/icons-vue'
import getBarChart from "./utils/barChart";
import { getDecreeIndustry, getDecreehylyList, getDecreeCompany, getDecreeAction } from "@/api/decree/influence";
import { getCnEntityOnChain, getChainFishbone, getChainInfoByDomainId, getChainStructure } from "@/api/exportControl";
import { getSingleSanctionEntitySupplyChain } from "@/api/exportControlV2.0";
import ChartChain from "./ChartChain.vue";
import ChartRelation from "./ChartRelation.vue";
import defaultIcon2 from "@/assets/icons/default-icon2.png";
import noticeIcon from "./assets/images/notice-icon.png";
import icon422 from "./assets/images/icon422.png";
import icon423 from "./assets/images/icon423.png";
import icon1620 from "./assets/images/icon1620.png";
import icon1621 from "./assets/images/icon1621.png";
import company from "./assets/images/company.png";
import companyActive from "./assets/images/company-active.png";
const route = useRoute();
// 跳转企业详情
const handleToCompanyDetail = () => {
const route = router.resolve("/companyPages");
window.open(route.href, "_blank");
// 受影响实体
const companyList = ref([]);
const activeEntityId = ref(1);
const currentPage = ref(1);
const pageSize = ref(10);
const handleToCompanyDetail = (item) => {
activeEntityId.value = item.id;
};
// 企业影响分析
const companyTotalNum = ref(0); // 企业数量
const isCRelated = ref(false); // 只看中国企业
const chart1Data = ref({
// title: ["集成电路", "新能源", "人工智能", "先进制造", "量子科技"],
// value: [109, 95, 79, 25, 11]
});
const handleGetChart1Data = async () => {
const handleCurrentChange = page => {
currentPage.value = page;
};
// const showCompanyList = computed(() => {
// const startIndex = (currentPage.value - 1) * pageSize.value;
// const endIndex = startIndex + pageSize.value;
// return companyList.value.slice(startIndex, endIndex);
// });
const showCompanyList = ref([
{ id: 1, name: "北京市", status: "上市" },
{ id: 2, name: "上海市", status: "上市" },
{ id: 3, name: "广州市广州市广州市广州市广州市广州市广州市广州市", status: "上市" },
{ id: 4, name: "深圳市", status: "上市" },
{ id: 5, name: "成都市", status: "上市" },
{ id: 7, name: "天津市", status: "上市" },
{ id: 9, name: "武汉市", status: "上市" },
{ id: 10, name: "西安市", status: "上市" },
]);
const handleGetCompanyListByArea = async () => {
const params = {
cRelated: isCRelated.value,
id: 147
id: curAreaId.value
};
try {
const res = await getDecreeIndustry(params);
console.log("企业影响分析", res);
const res = await getDecreeCompany(params);
console.log("行业领域公司列表", res);
if (res.code === 200 && res.data) {
chart1Data.value.title = res.data.map(item => {
return item.hylyName;
});
chart1Data.value.value = res.data.map(item => {
return item.companyNum;
companyList.value = res.data.map(item => {
return {
name: item.name,
id: item.id,
status: item.marketChange
};
});
companyTotalNum.value = companyList.value.length;
if (res.data?.length) handleToCompanyDetail(res.data[0])
} else {
chart1Data.value.title = [];
chart1Data.value.value = [];
companyList.value = [];
companyTotalNum.value = 0;
}
} catch (error) {
chart1Data.value.title = [];
chart1Data.value.value = [];
companyList.value = [];
companyTotalNum.value = 0;
}
};
const handelBox1 = async () => {
await handleGetChart1Data();
let chart1 = getBarChart(chart1Data.value.title, chart1Data.value.value);
setChart(chart1, "chart1");
};
// 指令搜索
const commandWord = ref("");
const handleSearch = () => {
const box1BtnActiveName = ref("");
const curAreaId = ref(0);
const box1BtnList = ref([
// "集成电路",
// "新能源",
// "人工智能",
// "先进制造",
// "量子科技"
]);
const handleClickBox1Btn = btn => {
box1BtnActiveName.value = btn.name;
curAreaId.value = btn.id;
handleGetCompanyListByArea();
handleGetCompanyListByArea();
handleGetChainId();
};
// 获取行业领域列表
// 行业领域
const curAreaId = ref("");
const areaList = ref([]);
const handleGetHylyList = async () => {
try {
const res = await getDecreehylyList();
console.log("行业领域列表", res);
if (res.code === 200 && res.data) {
box1BtnList.value = res.data;
box1BtnActiveName.value = box1BtnList.value[0].name;
curAreaId.value = box1BtnList.value[0].id;
handleGetCompanyListByArea();
areaList.value = res.data;
handleGetChainId();
}
} catch (error) {}
};
const companyList = ref([
// {
// name: "宁德时代新能源科技股份有限公司",
// status: "down"
// },
// {
// name: "比亚迪股份有限公司",
// status: "down"
// },
// {
// name: "隆基绿能科技股份有限公司",
// status: "down"
// },
// {
// name: "晶科能源控股有限公司",
// status: "down"
// },
// {
// name: "厦门海辰储能科技股份有限公司",
// status: "down"
// }
]);
const currentPage = ref(1);
const pageSize = ref(10);
// 处理页码改变事件
const handleCurrentChange = page => {
currentPage.value = page;
// 产业链/实体关系
const contentType = ref(2);
const headerContentType = (type) => {
contentType.value = type;
};
const chainId = ref(0);
const handleGetChainId = async () => {
try {
const res = await getChainInfoByDomainId(curAreaId.value);
console.log("获取chainId", res);
if (res && res.length) {
chainId.value = res[0].id;
// handleGetChainInfoByChainId();
}
} catch (error) {
console.error("chainId error", error);
}
};
const showCompanyList = computed(() => {
const startIndex = (currentPage.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value;
return companyList.value.slice(startIndex, endIndex);
});
const handleNodeClick = (node) => {
};
const handleLayoutChange = (type) => {
const handleGetCompanyListByArea = async () => {
const params = {
cRelated: isCRelated.value,
id: curAreaId.value
};
};
const treeData = ref(null);
const graphData = ref({ nodes: [], links: [] });
const singleSanctionEntitySupplyChainData = ref(null);
const updateGraphData = () => {
const data = singleSanctionEntitySupplyChainData.value;
if (!data) return;
const nodes = [];
const links = [];
nodes.push({
id: "0",
name: data.orgName,
image: companyActive,
symbolSize: 60,
isSanctioned: true
});
const parentList = data.parentOrgList || [];
parentList.forEach((item, index) => {
nodes.push({
id: `p-${item.id || index}`,
name: item.name,
image: item.isSanctioned ? companyActive : company,
symbolSize: 40,
isSanctioned: item.isSanctioned
});
links.push({
source: `p-${item.id || index}`,
target: "0",
name: "供应商"
});
});
const childList = data.childrenOrgList || [];
childList.forEach((item, index) => {
nodes.push({
id: `c-${item.id || index}`,
name: item.name,
image: item.isSanctioned ? companyActive : company,
symbolSize: 40,
isSanctioned: item.isSanctioned
});
links.push({
source: "0",
target: `c-${item.id || index}`,
name: "客户"
});
});
graphData.value = { nodes, links };
};
const getSingleSanctionEntitySupplyChainRequest = async () => {
try {
const res = await getDecreeCompany(params);
console.log("行业领域公司列表", res);
const res = await getSingleSanctionEntitySupplyChain({
orgId: "91370102723265504D"
});
console.log("data1", res)
if (res.code === 200 && res.data) {
companyList.value = res.data.map(item => {
return {
singleSanctionEntitySupplyChainData.value = res.data;
updateGraphData();
treeData.value = {
id: res.data.orgId,
name: res.data.orgName,
image: companyActive,
symbolSize: 50,
children: (res.data.parentOrgList || []).map((item, index) => ({
id: item.id || `p-${index}`,
name: item.name,
id: item.id,
status: item.marketChange
};
});
companyTotalNum.value = companyList.value.length;
} else {
companyList.value = [];
companyTotalNum.value = 0;
image: item.isSanctioned ? companyActive : company,
symbolSize: 30
}))
};
}
} catch (error) {
companyList.value = [];
companyTotalNum.value = 0;
console.log(error);
}
};
// 政令举措落实分析
const timeLineList = ref([
// {
// time: "2025年7月25日",
// content: "商务部已成立AI出口计划办公室,并开始招募专业人员。"
// },
// {
// time: "2025年7月31日",
// content: "英伟达、微软、谷歌等企业已提交初步技术栈提案。"
// }
]);
// 企业影响分析
const companyTotalNum = ref(0); // 企业数量
const handleGetAction = async () => {
const chart1Data = ref({
// title: ["集成电路", "新能源", "人工智能", "先进制造", "量子科技"],
// value: [109, 95, 79, 25, 11]
});
const handleGetChart1Data = async () => {
const params = {
id: route.query.id
id: 147
};
try {
const res = await getDecreeAction(params);
console.log("政令举措落实分析", res);
const res = await getDecreeIndustry(params);
console.log("企业影响分析", res);
if (res.code === 200 && res.data) {
timeLineList.value = res.data.map(item => {
return {
time: item.time,
content: item.describe
};
chart1Data.value.title = res.data.map(item => {
return item.hylyName;
});
chart1Data.value.value = res.data.map(item => {
return item.companyNum;
});
} else {
timeLineList.value = [];
chart1Data.value.title = [];
chart1Data.value.value = [];
}
} catch (error) {
timeLineList.value = [];
chart1Data.value.title = [];
chart1Data.value.value = [];
}
};
// 历史相似举措及落实情况
const box3List = ref([
// {
// type: "科技法案",
// title: "瓦森纳安排",
// content: "落实情况:持续有效,但面临技术快速迭代挑战。",
// time: "1996-至今",
// tag: "人工智能"
// },
// {
// type: "科技法案",
// title: "云计算出口管制",
// content: "落实情况:部分有效,但执行难度大。",
// time: "1996-至今",
// tag: "人工智能"
// },
// {
// type: "科技法案",
// title: "芯片与科学法案",
// content: "落实情况:正在实施,效果待观察。",
// time: "2022-至今",
// tag: "人工智能"
// },
// {
// type: "科技法案",
// title: "AI芯片出口管制",
// content: "落实情况:部分有效,但催生中国自主创新。",
// time: "1996-至今",
// tag: "人工智能"
// },
// {
// type: "科技法案",
// title: "瓦森纳安排",
// content: "落实情况:持续有效,但面临技术快速迭代挑战。",
// time: "1996-至今",
// tag: "人工智能"
// },
// {
// type: "科技法案",
// title: "瓦森纳安排",
// content: "落实情况:持续有效,但面临技术快速迭代挑战。",
// time: "1996-至今",
// tag: "人工智能"
// }
]);
watch(
() => isCRelated.value,
val => {
handleGetCompanyListByArea();
handelBox1();
}
);
const handelBox1 = async () => {
await handleGetChart1Data();
let chart1 = getBarChart(chart1Data.value.title, chart1Data.value.value);
setChart(chart1, "chart1");
};
const chainInfo = ref({
upstreamInternalCount: 0,
......@@ -402,24 +342,6 @@ const chainInfo = ref({
downstreamEntityRate: 0
});
const chainId = ref(0);
// 根据领域id获取chainId
const handleGetChainId = async () => {
try {
const res = await getChainInfoByDomainId(curAreaId.value);
console.log("获取chainId", res);
if (res && res.length) {
chainId.value = res[0].id;
console.log("chainId", chainId.value);
handleGetChainInfoByChainId();
}
} catch (error) {
console.error("chainId error", error);
}
};
// 根据chainId获取chainInfo
const handleGetChainInfoByChainId = async () => {
try {
......@@ -434,168 +356,128 @@ const handleGetChainInfoByChainId = async () => {
};
onMounted(() => {
// handleGetCompanyListByArea();
handleGetChart1Data();
handleGetHylyList();
// handleGetAction();
handelBox1();
getSingleSanctionEntitySupplyChainRequest()
});
</script>
<style lang="scss" scoped>
.wrapper {
width: 100%;
height: 100%;
min-height: 845px;
width: 1600px;
margin: 16px auto;
display: flex;
justify-content: center;
.box-header {
display: flex;
height: 48px;
position: relative;
.icon {
margin-top: 18px;
width: 8px;
height: 20px;
border-radius: 0 4px 4px 0;
background: var(--color-main-active);
}
.title {
height: 26px;
margin-left: 14px;
margin-top: 14px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
gap: 16px;
.box1 {
width: 480px;
.box1-main {
height: 100%;
padding: 10px 16px;
display: flex;
justify-content: flex-end;
gap: 4px;
.header-right-icon {
width: 28px;
height: 28px;
img {
width: 100%;
height: 100%;
}
}
}
.header-right1 {
position: absolute;
top: 8px;
right: 84px;
}
}
.left {
margin-top: 16px;
.box1 {
width: 480px;
height: 845px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
.box1-top {
width: 446px;
height: 188px;
margin: 7px auto 0;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 4px;
background: rgba(247, 248, 249, 1);
}
.box1-tab-box {
height: 60px;
width: 446px;
margin: 0 auto;
overflow-x: auto;
flex-direction: column;
.data-filter {
display: flex;
white-space: nowrap;
gap: 8px;
.tab {
min-width: min-content;
margin-top: 18px;
padding: 0 8px;
height: 28px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
cursor: pointer;
&:hover {
background: var(--btn-active-bg-color);
border: 1px solid var(--btn-active-border-color);
}
align-items: center;
gap: 16px;
margin-bottom: 10px;
.filter-select {
width: 150px;
}
.tabActive {
color: var(--btn-active-text-color);
background: var(--btn-active-bg-color);
border: 1px solid var(--btn-active-border-color);
.filter-input {
width: 20px;
flex: auto;
background-color: var(--el-fill-color-blank);
border-radius: var(--el-border-radius-base);
box-shadow: 0 0 0 1px var(--el-border-color) inset;
box-sizing: border-box;
height: 32px;
}
}
.box1-list-box {
.data-title {
border-top: 1px solid rgba(240, 242, 244, 1);
padding: 12px;
font-weight: bold;
}
.list-data {
height: 480px;
width: 446px;
margin: 0 auto;
.box1-item {
width: 446px;
height: 48px;
box-sizing: border-box;
border-bottom: 1px solid rgba(234, 236, 238, 1);
border-radius: 4px;
.list-item {
display: flex;
align-items: center;
border: 1px solid transparent;
border-bottom: 1px solid rgba(240, 242, 244, 1);
padding: 12px;
cursor: pointer;
&:first-child {
border-top: 1px solid rgba(240, 242, 244, 1);
}
&:hover {
background: var(--color-bg-hover);
background-color: #f7f8fa;
}
.id {
.item-icon {
width: 24px;
height: 24px;
margin-top: 12px;
margin-left: 12px;
border-radius: 12px;
background: #e7f3ff;
font-size: 0px;
.item-img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.item-icon-tag {
width: 20px;
height: 20px;
}
.item-tag {
color: rgb(206, 79, 81);
font-size: 14px;
font-family: Microsoft YaHei;
text-align: center;
line-height: 24px;
color: var(--color-main-active);
line-height: 14px;
}
.title {
margin-left: 10px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
.item-name {
width: 20px;
flex: auto;
padding: 0 10px;
font-size: 16px;
color: rgb(59, 65, 75);
font-weight: 400;
line-height: 48px;
font-family: "Microsoft YaHei";
}
.icon {
width: 8px;
height: 6px;
margin-top: 14px;
margin-left: 6px;
img {
width: 100%;
height: 100%;
}
}
.list-item.item-active {
background-color: rgba(5, 95, 194, 0.05);
border-color: rgba(174, 214, 255, 1);
.item-name {
color: rgb(5, 95, 194);
font-weight: bold;
}
}
}
.box1-footer {
.pagination-info {
height: 65px;
width: 446px;
margin: 0 auto;
display: flex;
justify-content: space-between;
box-sizing: border-box;
.box1-footer-left {
.pagination-left {
margin-top: 25px;
height: 18px;
color: rgba(95, 101, 108, 1);
......@@ -604,217 +486,66 @@ onMounted(() => {
font-weight: 400;
line-height: 18px;
}
.box1-footer-right {
.pagination-right {
margin-top: 23px;
}
}
}
}
.right {
margin-top: 16px;
margin-left: 16px;
.box2 {
width: 1105px;
height: 847px;
background: rgba(255, 255, 255);
border-radius: 10px;
position: relative;
.box2-main {
height: 799px;
.box2-main-header {
margin: 14px auto 36px;
width: 1066px;
height: 64px;
box-sizing: border-box;
border: 1px solid rgba(231, 243, 255, 1);
border-radius: 4px;
background: rgba(246, 250, 255, 1);
padding: 6px 12px;
.box2 {
width: 20px;
flex: auto;
:deep(.header-icon) {
display: none;
}
.custom-title {
display: flex;
justify-content: space-between;
align-items: flex-end;
width: 100%;
height: 100%;
padding: 0 16px;
.title-left {
display: flex;
border: 1px solid rgb(5, 95, 194);
color: rgb(5, 95, 194);
border-radius: 16px;
width: 240px;
height: 32px;
overflow: hidden;
cursor: pointer;
.title-item {
display: flex;
align-items: center;
justify-content: center;
gap: 13px;
.box2-main-header-left {
width: 19px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.box2-main-header-center {
width: 973px;
height: 48px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.box2-main-header-right {
width: 24px;
height: 24px;
gap: 6px;
width: 50%;
font-size: 16px;
line-height: 16px;
font-family: "Microsoft YaHei";
.title-icon {
width: 14px;
height: 14px;
font-size: 0;
img {
width: 100%;
height: 100%;
}
}
}
.box2-main-main {
width: 1053px;
height: 568px;
margin: 0 auto;
}
.box2-main-footer {
margin-left: 24px;
width: 1053px;
height: 117px;
display: flex;
box-sizing: border-box;
padding-top: 36px;
overflow-x: auto;
.box2-main-footer-left {
width: 408px;
text-align: center;
position: relative;
z-index: 3;
.top {
height: 22px;
display: flex;
justify-content: center;
gap: 6px;
align-items: center;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
color: rgba(206, 79, 81, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 14px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
text-align: left;
}
}
.bottom {
margin-top: 13px;
width: 408px;
height: 28px;
color: rgba(22, 119, 255, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: center;
background: url("./assets/images/bg1.png");
}
}
.box2-main-footer-center {
width: 408px;
text-align: center;
margin-left: -10px;
position: relative;
z-index: 2;
.top {
height: 22px;
display: flex;
justify-content: center;
gap: 6px;
align-items: center;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
color: rgba(206, 79, 81, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 14px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
text-align: left;
}
}
.bottom {
margin-top: 13px;
width: 408px;
height: 28px;
color: rgba(19, 168, 168, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: center;
background: url("./assets/images/bg2.png");
}
}
.box2-main-footer-right {
width: 408px;
text-align: center;
margin-left: -10px;
position: relative;
z-index: 1;
.top {
height: 22px;
display: flex;
justify-content: center;
gap: 6px;
align-items: center;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
color: rgba(206, 79, 81, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 14px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
text-align: left;
}
}
.bottom {
margin-top: 13px;
width: 408px;
height: 28px;
color: rgba(146, 84, 222, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: center;
background: url("./assets/images/bg3.png");
}
}
.title-active {
background-color: rgb(5, 95, 194);
color: white;
}
}
.title-right {
width: 180px;
}
}
.box2-main {
width: 100%;
height: 100%;
}
}
}
......
......@@ -177,7 +177,7 @@
<el-empty v-if="!relatedEvents.length" description="暂无数据" :image-size="100" />
<div class="box2-item" v-for="(item, index) in relatedEvents" :key="index">
<div class="item-left">
<img :src="item.image" alt="" />
<img :src="item.image || DefaultIconNews" alt="" />
</div>
<div class="item-center">
<div class="bubble-header" @click="handleClickToNewsDetail(item)">
......@@ -326,7 +326,7 @@ import { getDecreeRelatedEvent } from "@/api/decree/background";
import DefaultIcon1 from "@/assets/icons/default-icon1.png";
import DefaultIcon2 from "@/assets/icons/default-icon2.png";
import { ElMessage } from "element-plus";
import DefaultIconNews from "@/assets/icons/default-icon-news.png";
const route = useRoute();
const decreeId = ref(route.query.id);
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论