提交 023c2fec authored 作者: 李智林's avatar 李智林

出口管制2.0部分对接

上级 f09e612f
...@@ -50,7 +50,8 @@ export function getEntitiesDataInfo() { ...@@ -50,7 +50,8 @@ export function getEntitiesDataInfo() {
request({ request({
method: "GET", method: "GET",
// url: '/api/entitiesDataInfo/latestInfoSelect', // url: '/api/entitiesDataInfo/latestInfoSelect',
url: "/api/entitiesDataInfo/getLatestInfo" // url: "/api/entitiesDataInfo/getLatestInfo",
url: "/api/sanctionList/export/getLatestInfo"
}) })
); );
} }
...@@ -477,7 +478,7 @@ export function getEntitiesGrowthTrend() { ...@@ -477,7 +478,7 @@ export function getEntitiesGrowthTrend() {
} }
/** /**
* 实体清单更新频率 * 实体清单发布频度
* @returns {Promise<{ * @returns {Promise<{
* series: number[] * series: number[]
* xAxis: string[] * xAxis: string[]
...@@ -836,3 +837,28 @@ export function getScientificImpactEntityList(startTime) { ...@@ -836,3 +837,28 @@ export function getScientificImpactEntityList(startTime) {
}) })
); );
} }
/**
* 概览页--获取出口管制制裁措施
* @param {Object} data
* @param {string} [data.typeName="实体清单"] - 类型名称
* @param {boolean} [data.isCn=true] - 是否只看中国实体
* @param {string[]} [data.techDomains] - 科技领域ID列表
* @param {number[]} [data.years] - 制裁年份列表
* @param {string} [data.startDate] - 开始时间
* @param {string} [data.endDate] - 结束时间
* @param {string} [data.keyword] - 搜索字段
* @param {number[]} [data.entityTypes] - 实体类型(2:科研院所;3:高校;4:企业)
* @param {number} [data.pageNum=1] - 页码
* @param {number} [data.pageSize=10] - 每页数量
*/
export function getExportControlList(data) {
return request200(
request({
method: "POST",
url: "/api/sanctionList/pageQuery",
data
})
);
}
\ No newline at end of file
import request from "@/api/request.js";
// 实体清单-制裁概况-获取实体清单基本信息
export function getEntityInfo() {
return request({
method: 'GET',
url: `/api/sanctionList/baseInfo/el`
})
}
// 实体清单-制裁概况-获取发布机构与重点人物
/**
* @param {sanTypeId}
* @header token
*/
export function getPublishInfo(params) {
return request({
method: 'GET',
url: `/api/sanctionList/getPublishedOrg`,
params,
})
}
// 实体清单-制裁概况-获取发布机构机构动态
/**
* @param {Object} data
* @param {string} data.orgId
* @header token
*/
export function getPublishOrgInfo(data) {
return request({
method: 'POST',
url: `/api/organization/relate/news`,
data,
})
}
// 实体清单-制裁概况-获取实体清单更新历史
/**
* @param {Object} data
* @param {string} [data.typeName="实体清单"] - 类型名称
* @param {boolean} [data.isCn=false] - 是否只看中国实体
* @param {number[]} [data.techDomainIds] - 科技领域ID列表
* @param {number} [data.pageNum=1] - 页码
* @param {number} [data.pageSize=10] - 每页数量
* @header token
*/
export function getEntityUpdateInfo(data) {
return request({
method: 'POST',
url: `/api/entitiesDataCount/getSanctionProcess`,
data,
})
}
/**
* 实体清单列表-制裁概况-获取实体清单列表
* @param {Object} data
* @param {string} [data.typeName="实体清单"] - 类型名称
* @param {boolean} [data.isCn=true] - 是否只看中国实体
* @param {string[]} [data.techDomains] - 科技领域ID列表
* @param {number[]} [data.years] - 制裁年份列表
* @param {string} [data.startDate] - 开始时间
* @param {string} [data.endDate] - 结束时间
* @param {string} [data.keyword] - 搜索字段
* @param {number[]} [data.entityTypes] - 实体类型(2:科研院所;3:高校;4:企业)
* @param {number} [data.pageNum=1] - 页码
* @param {number} [data.pageSize=10] - 每页数量
*/
export function getExportControlList(data, options = {}) {
return request({
method: "POST",
url: "/api/sanctionList/pageQuery",
data,
...options
})
}
// 实体清单-制裁概况-50%规则涉及实体数
/**
* @param {Object} data
* @param {string} [data.typeName="实体清单"] - 类型名称
* @param {boolean} [data.isCn=true] - 是否只看中国实体
* @param {string[]} [data.techDomains] - 科技领域ID列表
* @param {number[]} [data.years] - 制裁年份列表
* @param {string} [data.startDate] - 开始时间
* @param {string} [data.endDate] - 结束时间
* @param {string} [data.keyword] - 搜索字段
* @param {number[]} [data.entityTypes] - 实体类型(2:科研院所;3:高校;4:企业)
* @header token
*/
export function get50PercentEntityCount(data) {
return request({
method: 'POST',
url: `/api/sanctionList/getRuleCount`,
data,
})
}
// 实体清单-数据统计-总量统计
export function getTotalCount() {
return request({
method: 'GET',
url: `/api/sanctionList/statistics/el/total`
})
}
// 实体清单-数据统计-制裁实体数量变化情况
/**
* @param {countType}
* @header token
*/
export function getSanctionCountChange(params) {
return request({
method: 'GET',
url: `/api/sanctionList/statistics/el/num`,
params,
})
}
// 实体清单-数据统计-制裁实体地域分布情况
/**
* @param {Object} params
* @param {string} [params.startDate] - 开始时间
* @param {string} [params.endDate] - 结束时间
* @header token
*/
export function getRegionCount(params) {
return request({
method: 'GET',
url: `/api/sanctionList/statistics/el/region`,
params,
})
}
// 实体清单-数据统计-制裁实体领域分布情况
/**
* @param {Object} params
* @param {string} [params.startDate] - 开始时间
* @param {string} [params.endDate] - 结束时间
* @header token
*/
export function getTechDomainCount(params) {
return request({
method: 'GET',
url: `/api/sanctionList/statistics/el/domain`,
params,
})
}
// 实体清单-数据统计-制裁实体类型分布情况
/**
* @param {Object} params
* @param {string} [params.startDate] - 开始时间
* @param {string} [params.endDate] - 结束时间
* @header token
*/
export function getEntityTypeCount(params) {
return request({
method: 'GET',
url: `/api/sanctionList/statistics/el/entityType`,
params,
})
}
// 实体清单-深度挖掘-选择制裁
/**
* @param {Object} data
* @param {string} [data.startDate] - 开始时间
* @param {string} [data.endDate] - 结束时间
* @param {string} [data.typeName="实体清单"] - 类型名称
* @param {number} [data.pageNum=1] - 页码
* @param {number} [data.pageSize=10] - 每页数量
* @header token
*/
export function getDeepMiningSelect(data) {
return request({
method: 'POST',
url: `/api/entitiesDataCount/getSanctionProcess`,
data,
})
}
// 实体清单-深度挖掘-产业链列表信息
/**
* @param {Object} params
* @header token
*/
export function getDeepMiningIndustry(params) {
return request({
method: 'GET',
url: `/api/chain/getChainInfo`,
params,
})
}
// 实体清单-深度挖掘-产业链鱼骨图信息
/**
* @param {Object} params
* @param {number} params.chainId - 产业链ID
* @param {string} [params.date] - 日期
* @header token
*/
export function getDeepMiningIndustryFishbone(params) {
return request({
method: 'GET',
url: `/api/chain/getChainFishbone`,
params,
})
}
// 实体清单-深度挖掘-产业链中国企业实体信息查询
/**
* @param {Object} params
* @param {number} params.chainId - 产业链ID
* @param {string} [params.date] - 日期
* @header token
*/
export function getDeepMiningIndustryEntity(params) {
return request({
method: 'GET',
url: `/api/chain/getChainEntityStat`,
params,
})
}
\ No newline at end of file
...@@ -90,6 +90,7 @@ const headerTitleClasses = computed(() => [ ...@@ -90,6 +90,7 @@ const headerTitleClasses = computed(() => [
width: 100%; width: 100%;
margin-bottom: 15px; margin-bottom: 15px;
position: relative; position: relative;
margin-bottom: 100px;
} }
.container-header { .container-header {
......
...@@ -86,7 +86,24 @@ const exportControlRoutes = [ ...@@ -86,7 +86,24 @@ const exportControlRoutes = [
title: "科研资助" title: "科研资助"
} }
}, },
// V2.0全部实体清单
{
path: "/exportControl/entityList",
name: "entityList",
component: () => import("@/views/exportControl/v2.0EntityList/index.vue"),
meta: {
title: "全部实体清单"
}
},
// V2.0单条制裁详情
{
path: "/exportControl/singleSanction",
name: "singleSanction",
component: () => import("@/views/exportControl/v2.0SingleSanction/index.vue"),
meta: {
title: "单条制裁详情"
}
},
] ]
export default exportControlRoutes export default exportControlRoutes
\ No newline at end of file
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
import * as echarts from 'echarts' import * as echarts from 'echarts'
const setChart = (option, chartId) => { const setChart = (option, chartId) => {
let chartDom = document.getElementById(chartId); let chartDom = document.getElementById(chartId);
if (!chartDom) {
return null;
}
chartDom.removeAttribute("_echarts_instance_"); chartDom.removeAttribute("_echarts_instance_");
let chart = echarts.init(chartDom); let chart = echarts.init(chartDom);
chart.setOption(option); chart.setOption(option);
......
...@@ -82,7 +82,7 @@ ...@@ -82,7 +82,7 @@
:key="index" :key="index"
> >
<div class="left"> <div class="left">
<img :src="item.imageUrl || item.image" alt="" /> <img :src="item.imageUrl || defaultNew" @error="e => e.target.src = defaultNew" alt="" />
</div> </div>
<div class="center"> <div class="center">
<div class="title">{{ item.sjbt }}</div> <div class="title">{{ item.sjbt }}</div>
...@@ -211,6 +211,7 @@ import user5 from "./assets/images/user5.png"; ...@@ -211,6 +211,7 @@ import user5 from "./assets/images/user5.png";
import userIcon from "./assets/icons/user-icon.png"; import userIcon from "./assets/icons/user-icon.png";
import userIcon1 from "./assets/icons/user-icon1.png"; import userIcon1 from "./assets/icons/user-icon1.png";
import userIcon2 from "./assets/icons/user-icon2.png"; import userIcon2 from "./assets/icons/user-icon2.png";
import defaultNew from "../assets/images/default-icon-news.png"
import { import {
getBillBackground, getBillBackground,
......
...@@ -269,7 +269,7 @@ ...@@ -269,7 +269,7 @@
@click="handleClickToNewsDetail(news)" @click="handleClickToNewsDetail(news)"
> >
<div class="left"> <div class="left">
<img :src="getProxyUrl(news.newsImage) || News1" alt="" referrerpolicy="no-referrer" @error="e => e.target.src = News1" /> <img :src="getProxyUrl(news.newsImage) || defaultNew" alt="" referrerpolicy="no-referrer" @error="e => e.target.src = News1" />
</div> </div>
<div class="right"> <div class="right">
<div class="right-top"> <div class="right-top">
...@@ -316,12 +316,12 @@ ...@@ -316,12 +316,12 @@
</div> </div>
<div class="box5-header-title">{{ "涉华法案数量" }}</div> <div class="box5-header-title">{{ "涉华法案数量" }}</div>
</div> </div>
<div class="box5-header-right"> <!-- <div class="box5-header-right">
<div class="header-right-icon"> <div class="header-right-icon">
<img src="./assets/images/tips-icon.png" alt="" /> <img src="./assets/images/tips-icon.png" alt="" />
</div> </div>
<div class="header-right-text">{{ "数据来源:美国国会官方网站" }}</div> <div class="header-right-text">{{ "数据来源:美国国会官方网站" }}</div>
</div> </div> -->
</div> </div>
<div class="box5-select"> <div class="box5-select">
<el-select <el-select
...@@ -352,12 +352,12 @@ ...@@ -352,12 +352,12 @@
<img src="./assets/images/box6-header-icon.png" alt="" /> <img src="./assets/images/box6-header-icon.png" alt="" />
</div> </div>
<div class="header-title">{{ "关键条款" }}</div> <div class="header-title">{{ "关键条款" }}</div>
<div class="box6-header-right"> <!-- <div class="box6-header-right">
<div class="header-right-icon"> <div class="header-right-icon">
<img src="./assets/images/tips-icon.png" alt="" /> <img src="./assets/images/tips-icon.png" alt="" />
</div> </div>
<div class="header-right-text">{{ "数据来源:美国国会官方网站" }}</div> <div class="header-right-text">{{ "数据来源:美国国会官方网站" }}</div>
</div> </div> -->
</div> </div>
<div class="box6-main" id="wordCloudChart"></div> <div class="box6-main" id="wordCloudChart"></div>
</div> </div>
...@@ -670,10 +670,12 @@ ...@@ -670,10 +670,12 @@
<div class="item-left">{{ "最新动议:" }}</div> <div class="item-left">{{ "最新动议:" }}</div>
<div class="item-right">{{ item.zxdy }}</div> <div class="item-right">{{ item.zxdy }}</div>
</div> </div>
<!-- <div class="item"> <div class="item">
<div class="item-left">{{ "法案进展:" }}</div> <div class="item-left">{{ "法案进展:" }}</div>
<div class="item-right2"></div> <div class="item-right2">
</div> --> <div class="tag" v-for="(val, idx) in [...item.progress].reverse()" :key="idx" :style="{ zIndex: item.progress.length - idx }">{{ val }}</div>
</div>
</div>
</div> </div>
</div> </div>
<div class="right-footer"> <div class="right-footer">
...@@ -749,6 +751,7 @@ import bill10 from "./assets/images/bill10.png"; ...@@ -749,6 +751,7 @@ import bill10 from "./assets/images/bill10.png";
import bill11 from "./assets/images/bill11.png"; import bill11 from "./assets/images/bill11.png";
import bill12 from "./assets/images/bill12.png"; import bill12 from "./assets/images/bill12.png";
import defaultNew from "../assets/images/default-icon-news.png"
import News1 from "./assets/images/news1.png"; import News1 from "./assets/images/news1.png";
import News2 from "./assets/images/news2.png"; import News2 from "./assets/images/news2.png";
import News3 from "./assets/images/news3.png"; import News3 from "./assets/images/news3.png";
...@@ -3415,6 +3418,73 @@ onUnmounted(() => {}); ...@@ -3415,6 +3418,73 @@ onUnmounted(() => {});
font-weight: 400; font-weight: 400;
} }
} }
.item-right2 {
margin-left: 10px;
display: flex;
align-items: center;
.tag {
height: 24px;
line-height: 22px;
padding: 0 10px 0 30px;
background: rgba(255, 255, 255, 1);
color: rgb(95, 101, 108);
border-top: 1px solid rgb(234, 236, 238);
border-bottom: 1px solid rgb(234, 236, 238);
position: relative;
margin-left: -10px;
font-family: "Microsoft YaHei";
font-size: 14px;
font-weight: 400;
&::after {
content: '';
position: absolute;
top: 50%;
right: -8.485px;
width: 16.97px;
height: 16.97px;
background: inherit;
border-top: 1px solid rgb(234, 236, 238);
border-right: 1px solid rgb(234, 236, 238);
transform: translateY(-50%) rotate(45deg);
z-index: 1;
box-shadow: 2px -2px 2px rgba(0,0,0,0.05);
box-sizing: border-box;
}
&:first-child {
margin-left: 0;
padding-left: 10px;
border-left: 1px solid rgb(234, 236, 238);
border-radius: 4px 0 0 4px;
}
&:last-child {
background: rgb(59, 65, 75);
color: rgba(255, 255, 255, 1);
border-color: rgb(59, 65, 75);
padding-right: 10px;
border-radius: 0;
border-right: none;
&::after {
display: block;
border-color: rgb(59, 65, 75);
box-shadow: none;
}
}
&:first-child:last-child {
margin-left: 0;
padding: 0 10px;
border-radius: 4px 0 0 4px;
background: rgb(59, 65, 75);
color: rgba(255, 255, 255, 1);
border: 1px solid rgb(59, 65, 75);
border-right: none;
}
}
}
} }
} }
} }
......
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
</div> </div>
<div class="text">{{ "分析报告" }}</div> <div class="text">{{ "分析报告" }}</div>
</div> </div>
<div class="btn4"> <!-- <div class="btn4">
<div class="icon"> <div class="icon">
<img src="./assets/icons/btn-icon4.png" alt="" /> <img src="./assets/icons/btn-icon4.png" alt="" />
</div> </div>
...@@ -70,7 +70,7 @@ ...@@ -70,7 +70,7 @@
<div class="icon1"> <div class="icon1">
<img src="./assets/icons/btn-icon5.png" alt="" /> <img src="./assets/icons/btn-icon5.png" alt="" />
</div> </div>
</div> </div> -->
</div> </div>
</div> </div>
</div> </div>
......
<template>
<el-tooltip
effect="dark"
:content="content"
popper-class="common-prompt-popper"
placement="top"
:show-after="500"
>
<div class="text-ellipsis">
<slot>{{ content }}</slot>
</div>
</el-tooltip>
</template>
<script setup>
defineProps({
content: {
type: String,
default: ""
}
});
</script>
<style scoped>
.text-ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
cursor: pointer;
}
</style>
<style>
.common-prompt-popper.el-popper {
padding: 8px 16px !important;
border-radius: 10px !important;
background-color: rgb(59, 65, 75) !important;
font-size: 16px !important;
font-weight: 400 !important;
font-family: "Microsoft YaHei" !important;
line-height: 30px !important;
color: #fff !important;
border: none !important;
}
.common-prompt-popper.el-popper .el-popper__arrow::before {
background-color: rgb(59, 65, 75) !important;
border-color: rgb(59, 65, 75) !important;
}
</style>
...@@ -87,7 +87,15 @@ ...@@ -87,7 +87,15 @@
> >
<div class="node-content" xmlns="http://www.w3.org/1999/xhtml"> <div class="node-content" xmlns="http://www.w3.org/1999/xhtml">
<div class="date">{{ node.formattedDate }}</div> <div class="date">{{ node.formattedDate }}</div>
<el-tooltip
effect="dark"
:content="node.actionTitle"
popper-class="common-prompt-popper"
placement="top"
:show-after="500"
>
<div class="title">{{ node.actionTitle }}</div> <div class="title">{{ node.actionTitle }}</div>
</el-tooltip>
<div class="votes" v-if="node.voteString">{{ node.voteString }}</div> <div class="votes" v-if="node.voteString">{{ node.voteString }}</div>
</div> </div>
</foreignObject> </foreignObject>
...@@ -340,3 +348,23 @@ export default { ...@@ -340,3 +348,23 @@ export default {
} }
} }
</style> </style>
<style>
.common-prompt-popper.el-popper {
padding: 8px 16px !important;
border-radius: 10px !important;
background-color: rgb(59, 65, 75) !important;
font-size: 16px !important;
font-weight: 400 !important;
font-family: "Microsoft YaHei" !important;
line-height: 30px !important;
color: #fff !important;
border: none !important;
max-width: 600px;
}
.common-prompt-popper.el-popper .el-popper__arrow::before {
background-color: rgb(59, 65, 75) !important;
border-color: rgb(59, 65, 75) !important;
}
</style>
\ No newline at end of file
...@@ -21,12 +21,12 @@ ...@@ -21,12 +21,12 @@
<div class="box1-right"> <div class="box1-right">
<div class="box1-right-item"> <div class="box1-right-item">
<div class="item-left">提案人:</div> <div class="item-left">提案人:</div>
<div class="item-right">{{basicInfo.tarName}}</div> <div class="item-right">{{ basicInfo.tarName }}</div>
</div> </div>
<div class="box1-right-item"> <div class="box1-right-item">
<div class="item-left">提出时间:</div> <div class="item-left">提出时间:</div>
<div class="item-right">{{basicInfo.introductionDate}}</div> <div class="item-right">{{ basicInfo.introductionDate }}</div>
</div> </div>
<div class="box1-right-item"> <div class="box1-right-item">
<div class="item-left">相关领域:</div> <div class="item-left">相关领域:</div>
...@@ -44,13 +44,14 @@ ...@@ -44,13 +44,14 @@
<div class="box1-right-item"> <div class="box1-right-item">
<div class="item-left">委员会报告:</div> <div class="item-left">委员会报告:</div>
<div class="item-right2" v-if="basicInfo.reportList"> <div class="item-right2" v-if="basicInfo.reportList">
<div class="right2-item" v-for="item,index in basicInfo.reportList" :key="index">{{ item }}</div> <div class="right2-item" v-for="(item, index) in basicInfo.reportList" :key="index">
{{ item }}
</div>
</div> </div>
</div> </div>
<div class="box1-right-item"> <div class="box1-right-item">
<div class="item-left">表决记录:</div> <div class="item-left">表决记录:</div>
<div class="item-right3">{{`全程共进行${basicInfo.votetotal}次唱名表决`}}</div> <div class="item-right3">{{ `全程共进行${basicInfo.votetotal}次唱名表决` }}</div>
</div> </div>
<div class="box1-right-item"> <div class="box1-right-item">
<div class="item-left">最近状态:</div> <div class="item-left">最近状态:</div>
...@@ -59,18 +60,17 @@ ...@@ -59,18 +60,17 @@
<div class="box1-right-item"> <div class="box1-right-item">
<div class="item-left">立案流程:</div> <div class="item-left">立案流程:</div>
<div class="item-right4"> <div class="item-right4">
<div class="step" v-for="(item, index) in basicInfo.stageList" :key="index"> <div
<div class="step-box" v-if="!item.active"> class="step"
{{ item }} v-for="(item, index) in basicInfo.stageList ? [...basicInfo.stageList].reverse() : []"
<div class="right-arrow"> :key="index"
<img src="./assets/icons/arrow-right.png" alt="" /> :style="{ zIndex: (basicInfo.stageList?.length || 0) - index }"
</div> >
</div> <div
<div class="step-box-active" v-else> class="step-box"
:class="{ 'step-box-active': index === (basicInfo.stageList?.length || 0) - 1 }"
>
{{ item }} {{ item }}
<div class="right-arrow">
<img src="./assets/icons/arrow-right.png" alt="" />
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -177,7 +177,7 @@ ...@@ -177,7 +177,7 @@
</div> --> </div> -->
<div class="info-box"> <div class="info-box">
<div class="info-left"> <div class="info-left">
<img src="./assets/images/usr1.png" alt="" /> <img :src="defaultAvatar" alt="" />
<div class="usr-icon1"> <div class="usr-icon1">
<img src="./assets/images/usr-icon1.png" alt="" /> <img src="./assets/images/usr-icon1.png" alt="" />
</div> </div>
...@@ -372,6 +372,7 @@ import { onMounted, ref } from "vue"; ...@@ -372,6 +372,7 @@ import { onMounted, ref } from "vue";
import WordCloudMap from "./WordCloudMap.vue"; import WordCloudMap from "./WordCloudMap.vue";
import STimeline from "./STimeline.vue"; import STimeline from "./STimeline.vue";
import { getBillInfo, getBillPerson, getBillEvent, getBillDyqk } from "@/api/bill"; import { getBillInfo, getBillPerson, getBillEvent, getBillDyqk } from "@/api/bill";
import defaultAvatar from "../assets/images/default-icon1.png";
const box2BtnActive = ref(1); const box2BtnActive = ref(1);
const handleClcikBox2Btn = index => { const handleClcikBox2Btn = index => {
...@@ -535,7 +536,7 @@ const handleClickMore2 = () => { ...@@ -535,7 +536,7 @@ const handleClickMore2 = () => {
// 获取基本信息 // 获取基本信息
const basicInfo = ref({}) const basicInfo = ref({});
const handleGetBasicInfo = async () => { const handleGetBasicInfo = async () => {
const params = { const params = {
...@@ -544,8 +545,10 @@ const handleGetBasicInfo = async () => { ...@@ -544,8 +545,10 @@ const handleGetBasicInfo = async () => {
try { try {
const res = await getBillInfo(params); const res = await getBillInfo(params);
console.log("基本信息", res); console.log("基本信息", res);
basicInfo.value = res.data basicInfo.value = res.data;
basicInfo.value.stageList.reverse() // if (basicInfo.value.stageList) {
// basicInfo.value.stageList.reverse();
// }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
...@@ -686,10 +689,10 @@ onMounted(() => { ...@@ -686,10 +689,10 @@ onMounted(() => {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
gap: 4px; gap: 4px;
.icon{ .icon {
width: 28px; width: 28px;
height: 28px; height: 28px;
img{ img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
...@@ -794,88 +797,85 @@ onMounted(() => { ...@@ -794,88 +797,85 @@ onMounted(() => {
text-align: left; text-align: left;
} }
.item-right4 { .item-right4 {
// width: 500px;
margin-top: -4px; margin-top: -4px;
height: 28px; height: 28px;
display: flex; display: flex;
padding-left: -10px;
border: 2px solid #5f656c;
border-right: none;
.step { .step {
background: rgb(241, 241, 242); height: 28px;
height: 24px; line-height: 26px;
line-height: 24px;
// width: 98px;
font-size: 14px; font-size: 14px;
text-align: center; text-align: center;
position: relative; position: relative;
background: transparent;
.step-box { .step-box {
padding: 4px 10px; padding: 0 15px 0 30px;
color: #333; color: rgb(59, 65, 75);
position: relative; position: relative;
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
line-height: 14px; margin-left: -10px;
margin-left: 10px; background: rgba(243, 243, 244, 1);
.right-arrow { border-top: 1px solid rgb(59, 65, 75);
position: absolute; border-bottom: 1px solid rgb(59, 65, 75);
right: -21px; border-right: none;
top: -2px;
z-index: 99;
width: 30px;
height: 28px;
img {
width: 100%;
height: 100%; height: 100%;
box-sizing: border-box;
&::after {
content: "";
position: absolute;
top: 50%;
right: -9.9px;
width: 19.8px;
height: 19.8px;
background: inherit;
border-top: 1px solid rgb(59, 65, 75);
border-right: 1px solid rgb(59, 65, 75);
transform: translateY(-50%) rotate(45deg);
z-index: 1;
box-sizing: border-box;
}
} }
&:first-child {
.step-box {
margin-left: 0;
padding-left: 10px;
border-left: 1px solid rgb(59, 65, 75);
} }
} }
.step-box-active { .step-box-active {
padding: 4 10px; color: rgba(255, 255, 255, 1);
color: #fff; background: rgb(206, 79, 81);
background: #ce4f51; border-color: rgba(52, 24, 24, 1);
position: relative;
z-index: 100;
&::after { &::after {
content: ""; /* 必须有内容才能使用伪元素 */ border-color: rgba(52, 24, 24, 1);
position: absolute; /* 绝对定位 */
top: 9px; /* 在父元素下方 */
right: -11px; /* 左对齐 */
width: 0; /* 设置宽度为0 */
height: 0; /* 设置高度为0 */
border-left: 20px solid transparent; /* 左边框设置为透明 */
border-right: 20px solid #ce4f51; /* 右边框设置为蓝色,形成三角形的一边 */
border-top: 20px solid transparent; /* 上边框设置为与父元素相同的颜色,形成三角形底部与父元素相连 */
transform: rotate(-45deg);
z-index: 99;
} }
.right-arrow {
position: absolute;
right: -22px;
top: -4px;
z-index: 99;
width: 32px;
height: 32px;
img {
width: 100%;
height: 100%;
} }
&:last-child {
.step-box {
padding-right: 15px;
border-right: 1px solid rgb(59, 65, 75);
&::after {
display: none;
} }
} }
// .step-box { .step-box-active {
// z-index: 99; border-right: none;
// width: 96px; &::after {
// background: url("./assets/images/step-box.png") 100% 100%; display: block;
// color: #333; right: -9.9px;
// } }
// .step-box-active { }
// width: 90px; }
// background: url("./assets/images/step-box-active.png") 100%
// 100%;
// color: #fff;
// }
} }
} }
} }
...@@ -1071,13 +1071,17 @@ onMounted(() => { ...@@ -1071,13 +1071,17 @@ onMounted(() => {
margin-top: 25px; margin-top: 25px;
display: flex; display: flex;
.info-left { .info-left {
img {
width: 128px;
height: 128px;
}
position: relative; position: relative;
.usr-icon1 { .usr-icon1 {
position: absolute; position: absolute;
bottom: 24px; bottom: 0px;
left: 20px; left: 18px;
width: 32px; width: 48px;
height: 32px; height: 48px;
border-radius: 16px; border-radius: 16px;
background: rgba(255, 255, 255, 0.9); background: rgba(255, 255, 255, 0.9);
box-sizing: border-box; box-sizing: border-box;
...@@ -1089,10 +1093,10 @@ onMounted(() => { ...@@ -1089,10 +1093,10 @@ onMounted(() => {
} }
.usr-icon2 { .usr-icon2 {
position: absolute; position: absolute;
bottom: 24px; bottom: 0px;
right: 20px; right: 18px;
width: 32px; width: 48px;
height: 32px; height: 48px;
border-radius: 16px; border-radius: 16px;
background: rgba(255, 255, 255, 0.9); background: rgba(255, 255, 255, 0.9);
box-sizing: border-box; box-sizing: border-box;
......
...@@ -36,14 +36,30 @@ ...@@ -36,14 +36,30 @@
<div class="left-main-item" v-for="(term, index) in mainTermsList" :key="index"> <div class="left-main-item" v-for="(term, index) in mainTermsList" :key="index">
<div class="id">{{ (currentPage - 1) * pageSize + index + 1 }}</div> <div class="id">{{ (currentPage - 1) * pageSize + index + 1 }}</div>
<div class="info"> <div class="info">
<el-tooltip
effect="dark"
:content="`第${term.tkxh}条. ${term.fynr}`"
popper-class="bill-template-popper"
placement="top"
:show-after="500"
>
<div class="title"> <div class="title">
<span class="title-active">{{ term.tkxh }}条.</span> <span class="title-active">{{ term.tkxh }}条.</span>
{{ term.fynr }} {{ term.fynr }}
</div> </div>
</el-tooltip>
<el-tooltip
effect="dark"
:content="`Sec.${term.tkxh} ${term.ywnr}`"
popper-class="bill-template-popper"
placement="top"
:show-after="500"
>
<div class="content"> <div class="content">
<span class="content-active">Sec.{{ term.tkxh }}</span> <span class="content-active">Sec.{{ term.tkxh }}</span>
{{ term.ywnr }} {{ term.ywnr }}
</div> </div>
</el-tooltip>
</div> </div>
<div class="tags-box"> <div class="tags-box">
<div <div
...@@ -62,9 +78,9 @@ ...@@ -62,9 +78,9 @@
{{ val }} {{ val }}
</div> </div>
</div> </div>
<div class="open"> <!-- <div class="open">
<img src="./assets/icons/open-icon.png" alt="" /> <img src="./assets/icons/open-icon.png" alt="" />
</div> </div> -->
</div> </div>
</div> </div>
<div class="left-footer"> <div class="left-footer">
...@@ -842,3 +858,23 @@ onMounted(async () => { ...@@ -842,3 +858,23 @@ onMounted(async () => {
font-size: 16px !important; font-size: 16px !important;
} }
</style> </style>
<style>
.bill-template-popper.el-popper {
padding: 8px 16px !important;
border-radius: 10px !important;
background-color: rgb(59, 65, 75) !important;
font-size: 16px !important;
font-weight: 400 !important;
font-family: "Microsoft YaHei" !important;
line-height: 30px !important;
color: #fff !important;
border: none !important;
max-width: 600px;
}
.bill-template-popper.el-popper .el-popper__arrow::before {
background-color: rgb(59, 65, 75) !important;
border-color: rgb(59, 65, 75) !important;
}
</style>
<template>
<el-tooltip
effect="dark"
:content="content"
popper-class="common-prompt-popper"
placement="top"
:show-after="500"
>
<div class="text-ellipsis">
<slot>{{ content }}</slot>
</div>
</el-tooltip>
</template>
<script setup>
defineProps({
content: {
type: String,
default: ""
}
});
</script>
<style scoped>
.text-ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
cursor: pointer;
}
</style>
<style>
.common-prompt-popper.el-popper {
padding: 8px 16px !important;
border-radius: 10px !important;
background-color: rgb(59, 65, 75) !important;
font-size: 16px !important;
font-weight: 400 !important;
font-family: "Microsoft YaHei" !important;
line-height: 30px !important;
color: #fff !important;
border: none !important;
}
.common-prompt-popper.el-popper .el-popper__arrow::before {
background-color: rgb(59, 65, 75) !important;
border-color: rgb(59, 65, 75) !important;
}
</style>
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
</template> </template>
<script setup> <script setup>
defineProps({ defineProps({
title: { title: {
type: String, type: String,
......
...@@ -12,13 +12,22 @@ ...@@ -12,13 +12,22 @@
<span class="news-source">{{ item.source }}</span> <span class="news-source">{{ item.source }}</span>
</div> </div>
</div> </div>
<el-tooltip
effect="dark"
:content="item.description"
popper-class="common-prompt-popper"
placement="top"
:show-after="500"
>
<div class="news-description">{{ item.description }}</div> <div class="news-description">{{ item.description }}</div>
</el-tooltip>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import CommonPrompt from "../commonPrompt/index.vue";
import newsImg from "@/assets/images/news-img.png"; import newsImg from "@/assets/images/news-img.png";
const props = defineProps({ const props = defineProps({
listData: { listData: {
...@@ -101,6 +110,9 @@ const handleItemClick = item => { ...@@ -101,6 +110,9 @@ const handleItemClick = item => {
line-height: 24px; line-height: 24px;
flex: 1; flex: 1;
margin-right: 12px; margin-right: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.news-meta { .news-meta {
......
...@@ -75,6 +75,7 @@ ...@@ -75,6 +75,7 @@
:quantity="item.postCount" :quantity="item.postCount"
unit="次" unit="次"
:color="infoListColor[index]" :color="infoListColor[index]"
@click="handleToEntityListNoId(item)"
/> />
</div> </div>
</div> </div>
...@@ -84,7 +85,7 @@ ...@@ -84,7 +85,7 @@
<el-col :span="16"> <el-col :span="16">
<custom-container titleType="primary" title="最新出口管制政策" :titleIcon="houseIcon" height="450px"> <custom-container titleType="primary" title="最新出口管制政策" :titleIcon="houseIcon" height="450px">
<template #header-right> <template #header-right>
<el-button type="primary" @click="handleToDetail" link> <el-button type="primary" @click="handleToEntityList()" link>
{{ "查看详情 >" }} {{ "查看详情 >" }}
</el-button> </el-button>
</template> </template>
...@@ -108,6 +109,7 @@ ...@@ -108,6 +109,7 @@
:interval="3000" :interval="3000"
arrow="never" arrow="never"
indicator-position="none" indicator-position="none"
@change="handleCarouselChange"
> >
<el-carousel-item v-for="(item, index) in entitiesDataInfoList" :key="item.id + index"> <el-carousel-item v-for="(item, index) in entitiesDataInfoList" :key="item.id + index">
<div> <div>
...@@ -128,19 +130,10 @@ ...@@ -128,19 +130,10 @@
<span class="box1-top-content-item-title">· 涉及领域:</span> <span class="box1-top-content-item-title">· 涉及领域:</span>
<div <div
class="box1-top-content-item-tags" class="box1-top-content-item-tags"
v-for="domainItem in item.domains" v-for="(domainItem, index) in item.domains"
:key="domainItem" :key="domainItem"
> >
<el-tag <el-tag :type="getTagType(domainItem)">{{ domainItem }}</el-tag>
:type="
domainItem === '航空航天'
? 'primary'
: item === '人工智能'
? 'danger'
: 'info'
"
>{{ domainItem }}</el-tag
>
</div> </div>
</div> </div>
</div> </div>
...@@ -188,15 +181,16 @@ ...@@ -188,15 +181,16 @@
</custom-container> </custom-container>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<custom-container titleType="danger" title="风险信号" :headerNum="5" :titleIcon="dangerIcon" height="450px"> <custom-container
titleType="danger"
title="风险信号"
:headerNum="warningList.length"
:titleIcon="dangerIcon"
height="450px"
>
<template #default> <template #default>
<div class="box2-main"> <div class="box2-main">
<div <div class="box2-main-item" v-for="(item, index) in warningList" :key="index">
class="box2-main-item"
v-for="(item, index) in warningList"
:key="index"
@click="handleToDetail"
>
<div <div
class="item-left" class="item-left"
:class="{ :class="{
...@@ -321,16 +315,9 @@ ...@@ -321,16 +315,9 @@
<div <div
style="display: flex; justify-content: center; align-items: center; gap: 5px" style="display: flex; justify-content: center; align-items: center; gap: 5px"
> >
<el-tag <el-tag v-for="tag in scope.row.tags" :key="tag" :type="getTagType(tag)">{{
v-for="tag in scope.row.tags" tag
:key="tag" }}</el-tag>
:type="
tag === '通信网络'
? 'primary'
: TAGTYPE[Math.floor(Math.random() * 5)]
"
>{{ tag }}</el-tag
>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
...@@ -357,16 +344,9 @@ ...@@ -357,16 +344,9 @@
<div <div
style="display: flex; justify-content: center; align-items: center; gap: 5px" style="display: flex; justify-content: center; align-items: center; gap: 5px"
> >
<el-tag <el-tag v-for="tag in scope.row.tags" :key="tag" :type="getTagType(tag)">{{
v-for="tag in scope.row.tags" tag
:key="tag" }}</el-tag>
:type="
tag === '通信网络'
? 'primary'
: TAGTYPE[Math.floor(Math.random() * 5)]
"
>{{ tag }}</el-tag
>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
...@@ -391,16 +371,9 @@ ...@@ -391,16 +371,9 @@
<el-table-column label="重点领域" width="180"> <el-table-column label="重点领域" width="180">
<template #default="scope"> <template #default="scope">
<div style="display: flex; align-items: center; gap: 5px"> <div style="display: flex; align-items: center; gap: 5px">
<el-tag <el-tag v-for="tag in scope.row.tags" :key="tag" :type="getTagType(tag)">{{
v-for="tag in scope.row.tags" tag
:key="tag" }}</el-tag>
:type="
tag === '通信网络'
? 'primary'
: TAGTYPE[Math.floor(Math.random() * 5)]
"
>{{ tag }}</el-tag
>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
...@@ -414,7 +387,7 @@ ...@@ -414,7 +387,7 @@
<el-row :gutter="20" style="width: 1600px; margin: 0 auto"> <el-row :gutter="20" style="width: 1600px; margin: 0 auto">
<el-col :span="8"> <el-col :span="8">
<custom-container title="制裁领域分" :titleIcon="radarIcon" height="480px"> <custom-container title="制裁领域分" :titleIcon="radarIcon" height="480px">
<template #header-right> <template #header-right>
<el-checkbox v-model="domainChecked" label="50%规则" size="large" /> <el-checkbox v-model="domainChecked" label="50%规则" size="large" />
</template> </template>
...@@ -437,6 +410,18 @@ ...@@ -437,6 +410,18 @@
<el-row :gutter="20" style="width: 1600px; margin: 0 auto"> <el-row :gutter="20" style="width: 1600px; margin: 0 auto">
<CustomTitle id="position4" title="资源库" style="margin-top: 54px" /> <CustomTitle id="position4" title="资源库" style="margin-top: 54px" />
<div class="resource-tabs">
<div
v-for="tab in resourceTabs"
:key="tab.value"
class="resource-tab-item"
:class="{ active: activeResourceTab === tab.value, disabled: tab.disabled }"
@click="handleResourceTabClick(tab)"
>
{{ tab.label }}
</div>
</div>
<template v-if="activeResourceTab === 'entity'">
<el-col :span="8"> <el-col :span="8">
<custom-container title="历次制裁过程" :titleIcon="listIcon" height="845px"> <custom-container title="历次制裁过程" :titleIcon="listIcon" height="845px">
<template #default> <template #default>
...@@ -445,7 +430,10 @@ ...@@ -445,7 +430,10 @@
<div class="box4-item" v-for="(item, idx) in sanctionProcessList" :key="item.title"> <div class="box4-item" v-for="(item, idx) in sanctionProcessList" :key="item.title">
<div class="box4-item-left"> <div class="box4-item-left">
<el-image :src="dotIcon" alt="图片" class="box4-item-left-icon" /> <el-image :src="dotIcon" alt="图片" class="box4-item-left-icon" />
<div class="box4-item-left-line" v-if="idx + 1 != sanctionProcessList.length"></div> <div
class="box4-item-left-line"
v-if="idx + 1 != sanctionProcessList.length"
></div>
</div> </div>
<div class="box4-item-right"> <div class="box4-item-right">
<div class="box4-item-right-header" @click="handleSanc(item)"> <div class="box4-item-right-header" @click="handleSanc(item)">
...@@ -454,14 +442,25 @@ ...@@ -454,14 +442,25 @@
> >
<span class="box4-item-right-header-desc">{{ item.desc }}</span> <span class="box4-item-right-header-desc">{{ item.desc }}</span>
</div> </div>
<el-tooltip
effect="dark"
:content="item.content"
popper-class="common-prompt-popper"
placement="top"
:show-after="500"
>
<div class="box4-item-right-content"> <div class="box4-item-right-content">
{{ item.content }} {{ item.content }}
</div> </div>
</el-tooltip>
</div> </div>
</div> </div>
</div> </div>
<div class="box4-footer" :style="{ marginTop: sanctionProcessList.length > 0 ? '0px' : 'auto' }"> <div
<el-button type="primary" link :icon="DownRight" @click="handleGetMore" class="box4-footer"
:style="{ marginTop: sanctionProcessList.length > 0 ? '0px' : 'auto' }"
>
<el-button type="primary" link @click="handleGetMore"
>查看更多 >查看更多
<el-icon><DArrowRight /></el-icon> <el-icon><DArrowRight /></el-icon>
</el-button> </el-button>
...@@ -508,7 +507,7 @@ ...@@ -508,7 +507,7 @@
)?.[0] )?.[0]
}} }}
</div> </div>
{{ scope.row.name }} <CommonPrompt :content="scope.row.name" style="flex: 1; overflow: hidden" />
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
...@@ -516,14 +515,9 @@ ...@@ -516,14 +515,9 @@
<el-table-column prop="domains" label="涉及领域" min-width="150"> <el-table-column prop="domains" label="涉及领域" min-width="150">
<template #default="scope"> <template #default="scope">
<div class="domain-tags"> <div class="domain-tags">
<el-tag <el-tag v-for="tag in scope.row.domains" :key="tag" :type="getTagType(tag)">{{
v-for="tag in scope.row.domains" tag
:key="tag" }}</el-tag>
:type="
tag === '通信网络' ? 'primary' : tagsType[Math.floor(Math.random() * 5)]
"
>{{ tag }}</el-tag
>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
...@@ -594,6 +588,79 @@ ...@@ -594,6 +588,79 @@
</template> </template>
</custom-container> </custom-container>
</el-col> </el-col>
</template>
<template v-if="activeResourceTab === 'all'">
<el-col :span="24">
<!-- <div style="min-height: 500px; display: flex; justify-content: center; align-items: center; background: #fff; border-radius: 4px;">
暂无内容
</div> -->
<div class="all-content">
<div class="left">
<div class="title">
<div class="box"></div>
<div class="text">科技领域</div>
</div>
<div class="left-main">
<el-checkbox-group v-model="checkedTech">
<div class="checkbox-grid">
<el-checkbox v-for="item in techOptions" :key="item.value" :label="item.value">{{
item.label
}}</el-checkbox>
</div>
</el-checkbox-group>
</div>
<div class="title">
<div class="box"></div>
<div class="text">制裁时间</div>
</div>
<div class="left-main">
<el-checkbox-group v-model="checkedTime">
<div class="checkbox-grid">
<el-checkbox v-for="item in timeOptions" :key="item" :label="item">{{
item
}}</el-checkbox>
</div>
</el-checkbox-group>
</div>
</div>
<div class="right">
<div class="right-title">
<img :src="icon01" alt="" />
<div>出口管制制裁措施</div>
</div>
<div class="right-main">
<div class="sanction-list" v-for="item in sanctionList" :key="item.id">
<div class="time">
<div class="year">{{ item.year }}</div>
<div class="date">{{ item.dateStr }}</div>
</div>
<img :src="item.icon || comTitle" alt="" />
<div class="main">
<div class="main-title" @click="handleTitleClick(item)">{{ item.title }}</div>
<div class="main-desc">{{ item.desc }}</div>
<div class="tag-box">
<div v-for="tag in item.tags" :key="tag" class="tag-item">{{ tag }}</div>
</div>
<div :class="{ 'count-tag': item.countTag }">{{ item.countTag }}</div>
</div>
</div>
</div>
<div class="right-footer">
<div class="total-count">{{ totalAll }}项调查</div>
<el-pagination
v-model:current-page="currentPageAll"
:page-size="pageSizeAll"
:total="totalAll"
layout="prev, pager, next"
background
@current-change="handlePageChangeAll"
/>
</div>
</div>
</div>
</el-col>
</template>
</el-row> </el-row>
</div> </div>
<el-dialog v-model="dialogVisible" width="800" :before-close="handleClose"> <el-dialog v-model="dialogVisible" width="800" :before-close="handleClose">
...@@ -645,7 +712,6 @@ import setChart from "@/utils/setChart"; ...@@ -645,7 +712,6 @@ import setChart from "@/utils/setChart";
import { ElMessage, ElMessageBox } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus";
import { DArrowRight, Warning, Search } from "@element-plus/icons-vue"; import { DArrowRight, Warning, Search } from "@element-plus/icons-vue";
// import router from "@/router/index";
import EChart from "@/components/Chart/index.vue"; import EChart from "@/components/Chart/index.vue";
import { TAGTYPE } from "@/public/constant"; import { TAGTYPE } from "@/public/constant";
...@@ -659,6 +725,7 @@ import InfoCard from "./components/info.vue"; ...@@ -659,6 +725,7 @@ import InfoCard from "./components/info.vue";
import CustomTitle from "./components/title.vue"; import CustomTitle from "./components/title.vue";
import NewsList from "./components/news.vue"; import NewsList from "./components/news.vue";
import MessageBubble from "./components/dialog.vue"; import MessageBubble from "./components/dialog.vue";
import CommonPrompt from "./commonPrompt/index.vue";
import trumpAvatar from "@/assets/images/icon-trump.png"; import trumpAvatar from "@/assets/images/icon-trump.png";
import elongAvatar from "@/assets/images/icon-elong.png"; import elongAvatar from "@/assets/images/icon-elong.png";
...@@ -673,6 +740,7 @@ import qushiIcon from "./assets/images/icon-qushi.png"; ...@@ -673,6 +740,7 @@ import qushiIcon from "./assets/images/icon-qushi.png";
import listIcon from "./assets/images/icon-list.png"; import listIcon from "./assets/images/icon-list.png";
import dotIcon from "./assets/images/info2-icon.png"; import dotIcon from "./assets/images/info2-icon.png";
import entityIcon from "./assets/images/icon-entity.png"; import entityIcon from "./assets/images/icon-entity.png";
import comTitle from "./assets/images/panel1_1.png";
import newsImg from "@/assets/images/news-img.png"; import newsImg from "@/assets/images/news-img.png";
import newsImg1 from "@/assets/images/newsImg1.png"; import newsImg1 from "@/assets/images/newsImg1.png";
...@@ -691,6 +759,7 @@ import bill9 from "./assets/images/bill9.png"; ...@@ -691,6 +759,7 @@ import bill9 from "./assets/images/bill9.png";
import bill10 from "./assets/images/bill10.png"; import bill10 from "./assets/images/bill10.png";
import bill11 from "./assets/images/bill11.png"; import bill11 from "./assets/images/bill11.png";
import bill12 from "./assets/images/bill12.png"; import bill12 from "./assets/images/bill12.png";
import icon01 from "./assets/images/jianzhu.png";
import { import {
getEntitiesDataCount, getEntitiesDataCount,
...@@ -703,8 +772,44 @@ import { ...@@ -703,8 +772,44 @@ import {
getSanDomainCount, getSanDomainCount,
getRiskSignal, getRiskSignal,
getSocialMediaInfo, getSocialMediaInfo,
getNewsInfo getNewsInfo,
getExportControlList
} from "@/api/exportControl"; } from "@/api/exportControl";
const sanctionList = ref([]);
const techOptions = [
{ label: "全部领域", value: 0 },
{ label: "人工智能", value: 1 },
{ label: "生物科技", value: 2 },
{ label: "新一代信息技术", value: 3 },
{ label: "量子科技", value: 4 },
{ label: "新能源", value: 5 },
{ label: "集成电路", value: 6 },
{ label: "海洋", value: 7 },
{ label: "先进制造", value: 8 },
{ label: "新材料", value: 9 },
{ label: "航空航天", value: 10 },
{ label: "深海", value: 11 },
{ label: "极地", value: 12 },
{ label: "太空", value: 13 },
{ label: "核", value: 14 }
];
const timeOptions = ["全部时间", "2025年", "2024年", "2023年", "2022年", "2021年"];
const checkedTech = ref([0]);
const checkedTime = ref(["全部时间"]);
// 跳转到单条制裁页面,单独打开一个新页面
const handleTitleClick = item => {
const route = router.resolve({
path: "/exportControl/singleSanction",
query: {
id: item.id
}
});
window.open(route.href, "_blank");
};
import { getMultipleBarChart_m } from "./utils/charts"; import { getMultipleBarChart_m } from "./utils/charts";
import { formatAnyDateToChinese } from "./utils"; import { formatAnyDateToChinese } from "./utils";
import _ from "lodash"; import _ from "lodash";
...@@ -721,7 +826,34 @@ const handleCompClick = item => { ...@@ -721,7 +826,34 @@ const handleCompClick = item => {
window.open(route.href, "_blank"); window.open(route.href, "_blank");
}; };
const tagsType = ["primary", "success", "info", "warning", "danger"]; const tagsType = ["primary", "success", "warning", "danger"];
const getTagType = tag => {
if (!tag) return "info";
const strTag = String(tag).trim();
const tagColorMap = {
通信网络: "primary",
人工智能: "danger",
集成电路: "warning",
量子科技: "success",
生物技术: "info",
新一代信息技术: "primary",
新能源: "success",
航空航天: "primary",
先进制造: "warning",
海洋: "info",
新材料: "danger",
深海: "primary",
极地: "info",
: "danger",
其他: "info"
};
if (tagColorMap[strTag]) {
return tagColorMap[strTag];
}
const hash = strTag.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0);
return TAGTYPE[hash % TAGTYPE.length];
};
//数据定义 //数据定义
const entitiesDataInfoList = shallowRef([]); const entitiesDataInfoList = shallowRef([]);
...@@ -781,21 +913,12 @@ onMounted(async () => { ...@@ -781,21 +913,12 @@ onMounted(async () => {
tags: item.domain tags: item.domain
}; };
}).slice(0, 5); }).slice(0, 5);
console.log("countDomainByYear", countDomainByYear);
// 整理柱状图数据并应用到趋势图 // 整理柱状图数据并应用到趋势图
if (countDomainByYear && countDomainByYear[0].yearDomainCount) { if (countDomainByYear && countDomainByYear[0].yearDomainCount) {
trendOption.value = processYearDomainCountData(countDomainByYear[0].yearDomainCount); trendOption.value = processYearDomainCountData(countDomainByYear[0].yearDomainCount);
} }
// trendOption.value = getMultipleBarChart_m(countDomainByYear);
// sanctionProcessList.value = _.map(_.slice(sanctionsInfoCount, 0, 5), item => {
// return {
// title: item.tittle,
// desc: `${item.entitiesChNum} 家中国实体`,
// content:
// item.sanReason ||
// "2025年3月25日,美国商务部工业与安全局以从事有悖于美国国家安全和外交政策利益的活动为由,宣布将来自中国的54家实体新增至“实体清单”。"
// };
// });
await fetchRiskSignals("0103"); await fetchRiskSignals("0103");
// 获取社交媒体信息 // 获取社交媒体信息
await fetchSocialMediaInfo(); await fetchSocialMediaInfo();
...@@ -805,17 +928,8 @@ onMounted(async () => { ...@@ -805,17 +928,8 @@ onMounted(async () => {
await fetchSanctionProcess(sanctionPage.value, 10); await fetchSanctionProcess(sanctionPage.value, 10);
// 获取雷达图数据 // 获取雷达图数据
await fetchRadarData(domainChecked.value); await fetchRadarData(domainChecked.value);
// console.log("entitiesList entitiesList", entityBody); // 获取出口管制制裁措施
// entitiesList.value = _.map(entityBody.content, item => { await fetchSanctionList();
// return {
// ...item,
// name: item.entityNameZh,
// enName: item.entityName,
// domains: item.techDomains,
// sanctionDate: item.startTime
// };
// });
// console.log("entitiesList entitiesList 1", entitiesList.value);
} catch (err) { } catch (err) {
console.log(err); console.log(err);
} }
...@@ -892,24 +1006,58 @@ const handleBackHome = () => { ...@@ -892,24 +1006,58 @@ const handleBackHome = () => {
}); });
}; };
const carouselRef = ref(null); const carouselRef = ref(null);
const handleToDetail = () => { // const handleToDetail = () => {
let activeIndex = 0; // let activeIndex = 0;
if (carouselRef.value) { // if (carouselRef.value) {
activeIndex = carouselRef.value.activeIndex; // activeIndex = carouselRef.value.activeIndex;
} // }
console.log("当前 Carousel 激活索引:", activeIndex); // console.log("当前 Carousel 激活索引:", activeIndex);
// // 使用当前激活项的数据
// const currentItem = entitiesDataInfoList.value[activeIndex];
// if (currentItem) {
// const route = router.resolve({
// path: "/exportControlAnalysis",
// query: {
// startTime: currentItem.postDate
// }
// });
// window.open(route.href, "_blank");
// }
// };
const currentCarouselIndex = ref(0);
const handleCarouselChange = index => {
currentCarouselIndex.value = index;
};
// 使用当前激活项的数据 // 跳转到V2.0单次制裁
const currentItem = entitiesDataInfoList.value[activeIndex]; const handleToEntityList = item => {
if (currentItem) { let id = item?.id;
const route = router.resolve({ if (!id) {
path: "/exportControlAnalysis", const currentItem = entitiesDataInfoList.value[currentCarouselIndex.value];
id = currentItem?.id;
}
const routeData = router.resolve({
path: "/exportControl/singleSanction",
query: { query: {
startTime: currentItem.postDate id: id
} }
}); });
window.open(route.href, "_blank"); // 打开一个新页面
window.open(routeData.href, "_blank");
};
// 跳转到V2.0实体清单无ID
const handleToEntityListNoId = item => {
if (item.nameZh == "实体清单") {
const routeData = router.resolve({
path: "/exportControl/entityList"
});
// 打开一个新页面
window.open(routeData.href, "_blank");
}else {
return
} }
}; };
...@@ -920,43 +1068,43 @@ const searchKey = ref(""); ...@@ -920,43 +1068,43 @@ const searchKey = ref("");
const infoListColor = ref(["rgba(206, 79, 81, 1)", "rgba(132, 136, 142, 1)", "rgba(132, 136, 142, 1)", "rgba(132, 136, 142, 1)"]); const infoListColor = ref(["rgba(206, 79, 81, 1)", "rgba(132, 136, 142, 1)", "rgba(132, 136, 142, 1)", "rgba(132, 136, 142, 1)"]);
const infoList = ref([]); const infoList = ref([]);
const customNewsData = ref([ // const customNewsData = ref([
{ // {
image: newsImg, // image: newsImg,
title: "美国智库激辩人工智能监管路径", // title: "美国智库激辩人工智能监管路径",
time: "11-4", // time: "11-4",
source: "华盛顿邮报", // source: "华盛顿邮报",
description: "各方就AI监管模式展开讨论。有观点认为碎片化州级监管比全面联邦法规更灵活,也有分析..." // description: "各方就AI监管模式展开讨论。有观点认为碎片化州级监管比全面联邦法规更灵活,也有分析..."
}, // },
{ // {
image: newsImg, // image: newsImg,
title: "布鲁金斯学会称美国低估中国在“印太”战略", // title: "布鲁金斯学会称美国低估中国在“印太”战略",
time: "11-3", // time: "11-3",
source: "纽约时报", // source: "纽约时报",
description: "分析认为,当美国注意力被其他地区分散时,中国通过外交、发展和防务多管齐下,系统性......" // description: "分析认为,当美国注意力被其他地区分散时,中国通过外交、发展和防务多管齐下,系统性......"
}, // },
{ // {
image: newsImg1, // image: newsImg1,
title: "五角大楼指令引发智库与国防部“脱钩”震荡", // title: "五角大楼指令引发智库与国防部“脱钩”震荡",
time: "11-3", // time: "11-3",
source: "洛杉矶时报", // source: "洛杉矶时报",
description: "美国国防部长下令全面暂停部内人员参与所有智库活动,标志着传统的“旋转门”关系发生......" // description: "美国国防部长下令全面暂停部内人员参与所有智库活动,标志着传统的“旋转门”关系发生......"
}, // },
{ // {
image: newsImg, // image: newsImg,
title: "CSIS建议美国建立跨机构AI基准测试体系", // title: "CSIS建议美国建立跨机构AI基准测试体系",
time: "11-3", // time: "11-3",
source: "福克斯新闻网", // source: "福克斯新闻网",
description: "指出美国《人工智能行动计划》忽视了基准测试这一关键环节,建议由国家标准与技术研究........." // description: "指出美国《人工智能行动计划》忽视了基准测试这一关键环节,建议由国家标准与技术研究........."
}, // },
{ // {
image: newsImg1, // image: newsImg1,
title: "美国智库激辩人工智能监管路径", // title: "美国智库激辩人工智能监管路径",
time: "11-4", // time: "11-4",
source: "华盛顿邮报", // source: "华盛顿邮报",
description: "各方就AI监管模式展开讨论。有观点认为碎片化州级监管比全面联邦法规更灵活,也有分析..." // description: "各方就AI监管模式展开讨论。有观点认为碎片化州级监管比全面联邦法规更灵活,也有分析..."
} // }
]); // ]);
// 雷达图 // 雷达图
const domainChecked = ref(false); const domainChecked = ref(false);
...@@ -967,22 +1115,13 @@ const radarOption = ref({ ...@@ -967,22 +1115,13 @@ const radarOption = ref({
legend: { legend: {
top: "0%", top: "0%",
icon: "circle", icon: "circle",
data: ["实体清单", "商业管制清单", "关键和新型技术清单"] data: []
},
grid: {
top: "15%",
containLabel: true
}, },
radar: { radar: {
radius: "55%",
center: ["50%", "55%"],
// shape: 'circle', // shape: 'circle',
indicator: [ indicator: [],
{ name: "集成电路", max: 6500 },
{ name: "生物科技", max: 16000 },
{ name: "人工智能", max: 30000 },
{ name: "通信网络", max: 38000 },
{ name: "量子科技", max: 52000 },
{ name: "能源领域", max: 25000 }
],
axisName: { axisName: {
formatter: "{value}", formatter: "{value}",
color: "rgba(59, 65, 75, 1)", color: "rgba(59, 65, 75, 1)",
...@@ -994,29 +1133,7 @@ const radarOption = ref({ ...@@ -994,29 +1133,7 @@ const radarOption = ref({
{ {
name: "", name: "",
type: "radar", type: "radar",
data: [ data: []
{
value: [4200, 3000, 20000, 35000, 50000, 18000],
name: "实体清单",
areaStyle: {
color: "rgba(10, 87, 166, 0.2)"
}
},
{
value: [5000, 14000, 28000, 26000, 42000, 21000],
name: "商业管制清单",
areaStyle: {
color: "rgba(206, 79, 81, 0.2)"
}
},
{
value: [4000, 14000, 18000, 21000, 32000, 10000],
name: "关键和新型技术清单",
areaStyle: {
color: "rgba(250, 140, 22, 0.2)"
}
}
]
} }
] ]
}); });
...@@ -1067,7 +1184,10 @@ const fetchRadarData = async checked => { ...@@ -1067,7 +1184,10 @@ const fetchRadarData = async checked => {
}); });
// 更新雷达图指标 // 更新雷达图指标
const maxValue = Math.max(...seriesData.flatMap(item => item.value)) * 1.2; let maxValue = Math.max(...seriesData.flatMap(item => item.value)) * 1.2;
// 向上取整到最近的100的倍数,避免小数导致 ticks 不可读警告
maxValue = Math.ceil(maxValue / 100) * 100;
const indicators = domainNames.map(name => ({ const indicators = domainNames.map(name => ({
name: name, name: name,
max: maxValue || 100 // 防止max为0的情况 max: maxValue || 100 // 防止max为0的情况
...@@ -1104,6 +1224,121 @@ const currentPage = ref(1); // 默认显示第5页 ...@@ -1104,6 +1224,121 @@ const currentPage = ref(1); // 默认显示第5页
const pageSize = ref(10); const pageSize = ref(10);
const total = ref(0); const total = ref(0);
// 全部制裁分页
const currentPageAll = ref(1);
const pageSizeAll = ref(10);
const totalAll = ref(0);
const fetchSanctionList = async () => {
try {
const techDomains = checkedTech.value.includes(0) ? null : checkedTech.value.map(String);
let years = null;
if (!checkedTime.value.includes("全部时间")) {
years = checkedTime.value
.map(t => {
const match = t.match(/(\d{4})/);
return match ? parseInt(match[1]) : null;
})
.filter(y => y !== null);
if (years.length === 0) years = null;
}
const params = {
pageNum: currentPageAll.value,
pageSize: pageSizeAll.value,
techDomains: techDomains,
years: years,
isCn: false,
typeName: ""
};
const res = await getExportControlList(params);
if (res && res.content) {
sanctionList.value = res.content.map(item => {
const tags = Array.isArray(item.techDomains) ? item.techDomains : item.techDomain ? [item.techDomain] : [];
const fullTime = item.startTime ? formatAnyDateToChinese(item.startTime) : item.publishDate || item.date;
let year = "";
let dateStr = fullTime;
if (typeof fullTime === "string" && fullTime.includes("年")) {
const parts = fullTime.split("年");
year = parts[0];
dateStr = parts[1].replace(/\s+/g, "");
}
return {
...item,
time: fullTime,
year,
dateStr,
title: item.entityNameZh || item.entityName || item.title || item.name,
desc: item.sanReason || item.description || item.summary || item.content,
tags: tags,
countTag: item.ruleOrgCount ? `${item.ruleOrgCount}家关联实体` : item.countTag || ""
};
});
totalAll.value = res.totalElements;
}
} catch (error) {}
};
const handlePageChangeAll = val => {
currentPageAll.value = val;
fetchSanctionList();
};
watch(
checkedTech,
(newVal, oldVal) => {
let isModified = false;
if (newVal.includes(0)) {
if (!oldVal.includes(0)) {
checkedTech.value = [0];
isModified = true;
} else if (newVal.length > 1) {
checkedTech.value = newVal.filter(v => v !== 0);
isModified = true;
}
} else if (newVal.length === 0) {
checkedTech.value = [0];
isModified = true;
}
if (isModified) return;
currentPageAll.value = 1;
fetchSanctionList();
},
{ deep: true }
);
watch(
checkedTime,
(newVal, oldVal) => {
let isModified = false;
if (newVal.includes("全部时间")) {
if (!oldVal.includes("全部时间")) {
checkedTime.value = ["全部时间"];
isModified = true;
} else if (newVal.length > 1) {
checkedTime.value = newVal.filter(v => v !== "全部时间");
isModified = true;
}
} else if (newVal.length === 0) {
checkedTime.value = ["全部时间"];
isModified = true;
}
if (isModified) return;
currentPageAll.value = 1;
fetchSanctionList();
},
{ deep: true }
);
// 获取实体清单数据 // 获取实体清单数据
const fetchEntitiesList = async (page = 1, size = 10) => { const fetchEntitiesList = async (page = 1, size = 10) => {
try { try {
...@@ -1111,7 +1346,7 @@ const fetchEntitiesList = async (page = 1, size = 10) => { ...@@ -1111,7 +1346,7 @@ const fetchEntitiesList = async (page = 1, size = 10) => {
if (res) { if (res) {
entitiesList.value = res.content.map(item => ({ entitiesList.value = res.content.map(item => ({
...item, ...item,
name: item.entityNameZh, name: item.entityNameZh || item.entityName,
enName: item.entityName, enName: item.entityName,
domains: item.techDomains, domains: item.techDomains,
sanctionDate: item.startTime sanctionDate: item.startTime
...@@ -1175,6 +1410,22 @@ const handlePageChange = page => { ...@@ -1175,6 +1410,22 @@ const handlePageChange = page => {
}; };
const searchKeyword = ref(""); const searchKeyword = ref("");
// 资源库 Tab 数据
const resourceTabs = [
{ label: "全部制裁", value: "all", disabled: false },
{ label: "实体清单", value: "entity", disabled: false },
{ label: "商业管制清单", value: "commerce", disabled: true },
{ label: "关键与新兴技术清单", value: "tech", disabled: true },
{ label: "军事最终用户清单", value: "military", disabled: true }
];
const activeResourceTab = ref("all");
const handleResourceTabClick = tab => {
if (tab.disabled) return;
activeResourceTab.value = tab.value;
};
const strengthLabels = { const strengthLabels = {
strong: "强", strong: "强",
medium: "中", medium: "中",
...@@ -1226,10 +1477,10 @@ const fetchNewsInfo = async () => { ...@@ -1226,10 +1477,10 @@ const fetchNewsInfo = async () => {
newsList.value = data.map(item => ({ newsList.value = data.map(item => ({
...item, ...item,
avatar: item.newsImage, avatar: item.newsImage,
name: item.newsTitle,
time: formatTime(item.newsDate), time: formatTime(item.newsDate),
source: item.newsOrg, source: item.newsOrg,
description: item.newsContent description: item.newsContent,
title: item.newsTitle
})); }));
} }
} catch (err) { } catch (err) {
...@@ -1238,7 +1489,7 @@ const fetchNewsInfo = async () => { ...@@ -1238,7 +1489,7 @@ const fetchNewsInfo = async () => {
}; };
const handlePerClick = item => { const handlePerClick = item => {
console.log("点击了社交媒体消息:", item); // console.log("点击了社交媒体消息:", item);
const route = router.resolve({ const route = router.resolve({
path: "/characterPage", path: "/characterPage",
query: { query: {
...@@ -1248,118 +1499,31 @@ const handlePerClick = item => { ...@@ -1248,118 +1499,31 @@ const handlePerClick = item => {
window.open(route.href, "_blank"); window.open(route.href, "_blank");
}; };
// 处理点击社交媒体消息的方法 // 处理点击社交媒体消息的方法
const handleInfoClick = item => { // const handleInfoClick = item => {
console.log("点击了社交媒体消息的更多信息:", item); // console.log("点击了社交媒体消息的更多信息:", item);
// 这里可以添加打开详情页的逻辑 // // 这里可以添加打开详情页的逻辑
ElMessageBox.alert(`${item.content}`, "信息详情", { // ElMessageBox.alert(`${item.content}`, "信息详情", {
confirmButtonText: "确定", // confirmButtonText: "确定",
callback: action => { // callback: action => {
ElMessage({ // ElMessage({
type: "info", // type: "info",
message: `action: ${action}` // message: `action: ${action}`
}); // });
} // }
}); // });
}; // };
// 添加格式化时间的方法 // 添加格式化时间的方法
const formatTime = dateString => { const formatTime = dateString => {
const date = new Date(dateString); const date = new Date(dateString);
const hours = date.getHours().toString().padStart(2, "0"); const month = (date.getMonth() + 1).toString().padStart(2, "0");
const minutes = date.getMinutes().toString().padStart(2, "0"); const day = date.getDate().toString().padStart(2, "0");
return `${hours}:${minutes}`; return `${month}-${day}`;
}; };
const warningList = ref([ const warningList = ref([]);
{
title: "关于对中华人民共和国合成阿片类药物供应链...",
time: "一天前",
status: "特别重大"
},
{
title: "关于调整汽车及汽车零部件进口的公告",
time: "一天前",
status: "特别重大"
},
{
title: "关于调整钢铁进口的公告",
time: "一天前",
status: "重大风险"
},
{
title: "关于使用互惠关税规范进口以纠正导致大规模...",
time: "一天前",
status: "重大风险"
},
{
title: "关于修订对中华人民共和国低价值进口商品适...",
time: "一天前",
status: "一般风险"
}
]);
const curBillList = ref([ const curBillList = ref([]);
{
billName: "大而美法案",
introductionDate: "2025年7月4日",
img: bill1
},
{
billName: "GENIUS稳定币法案",
introductionDate: "2025年7月5日",
img: bill2
},
{
billName: "美越贸易协议",
introductionDate: "2025年7月6日",
img: bill3
},
{
billName: "美越贸易协议",
introductionDate: "2025年7月7日",
img: bill4
},
{
billName: "汽车零部件25%关税实施规则",
introductionDate: "2025年7月10日",
img: bill5
},
{
billName: "汽车零部件25%关税实施规则",
introductionDate: "2025年7月12日",
img: bill6
},
{
billName: "小额豁免包裹政策调整",
introductionDate: "2025年7月14日",
img: bill7
},
{
billName: "NIH预算否决案",
introductionDate: "2025年7月15日",
img: bill8
},
{
billName: "得州国会选区重划法案",
introductionDate: "2025年7月17日",
img: bill9
},
{
billName: "美越贸易协议",
introductionDate: "2025年7月24日",
img: bill10
},
{
billName: "美越贸易协议",
introductionDate: "2025年8月4日",
img: bill11
},
{
billName: "美越贸易协议",
introductionDate: "2025年8月8日",
img: bill12
}
]);
const releaseTime = ref("近一年发布"); const releaseTime = ref("近一年发布");
...@@ -1414,43 +1578,43 @@ const chart1Data = ref({ ...@@ -1414,43 +1578,43 @@ const chart1Data = ref({
const handleSanc = item => { const handleSanc = item => {
console.log(item); console.log(item);
const route = router.resolve({ const route = router.resolve({
path: "/exportControlAnalysis", path: "/exportControl/singleSanction",
query: { query: {
startTime: item.postDate id: item.id
} }
}); });
window.open(route.href, "_blank"); window.open(route.href, "_blank");
}; };
// 获取热门法案 // // 获取热门法案
const handleGetHotBills = async () => { // const handleGetHotBills = async () => {
try { // try {
const res = await getHotBills(); // const res = await getHotBills();
console.log("热门法案", res); // console.log("热门法案", res);
billList.value = res.data; // billList.value = res.data;
} catch (error) { // } catch (error) {
console.error(error); // console.error(error);
} // }
}; // };
// 根据法案类型获取法案列表 // // 根据法案类型获取法案列表
const handleGetBillsByType = async () => { // const handleGetBillsByType = async () => {
const params = { // const params = {
type: activeHylyId.value // type: activeHylyId.value
}; // };
try { // try {
const res = await getBillsByType(params); // const res = await getBillsByType(params);
console.log("根据法案类型获取法案列表", res); // console.log("根据法案类型获取法案列表", res);
curBillList.value = res.data.map(item => { // curBillList.value = res.data.map(item => {
return { // return {
billId: item.billId, // billId: item.billId,
billName: item.billName, // billName: item.billName,
introductionDate: item.introductionDate, // introductionDate: item.introductionDate,
img: bill1 // img: bill1
}; // };
}); // });
} catch (error) {} // } catch (error) {}
}; // };
// 查看更多风险信号 // 查看更多风险信号
const handleToMoreRiskSignal = () => { const handleToMoreRiskSignal = () => {
...@@ -1515,6 +1679,10 @@ const handleMediaClick = item => { ...@@ -1515,6 +1679,10 @@ const handleMediaClick = item => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
* {
margin: 0;
padding: 0;
}
:deep(.el-input__wrapper) { :deep(.el-input__wrapper) {
box-shadow: none; box-shadow: none;
} }
...@@ -1572,7 +1740,7 @@ const handleMediaClick = item => { ...@@ -1572,7 +1740,7 @@ const handleMediaClick = item => {
position: absolute; position: absolute;
z-index: 9999; z-index: 9999;
right: -20px; right: -20px;
top:135px; top: 135px;
width: 24px; width: 24px;
height: 48px; height: 48px;
background: #e7f1ff; background: #e7f1ff;
...@@ -2730,4 +2898,241 @@ const handleMediaClick = item => { ...@@ -2730,4 +2898,241 @@ const handleMediaClick = item => {
letter-spacing: 0px; letter-spacing: 0px;
text-align: justify; text-align: justify;
} }
.resource-tabs {
width: 100%;
display: flex;
align-items: center;
margin-top: 6px;
margin-bottom: 36px;
padding-left: 10px;
.resource-tab-item {
margin-right: 12px;
cursor: pointer;
font-size: 20px;
color: rgb(59, 65, 75);
font-weight: 400;
font-family: "Microsoft YaHei";
padding: 8px 24px;
border-radius: 21px;
transition: all 0.3s;
&:last-child {
margin-right: 0;
}
&.active {
background: rgb(5, 95, 194);
color: #ffffff;
font-weight: 700;
}
&.disabled {
cursor: not-allowed;
color: #999999;
background: transparent;
}
&:hover:not(.active):not(.disabled) {
color: #0a57a6;
}
}
}
.all-content {
width: 100%;
height: auto;
padding-bottom: 100px;
display: flex;
justify-content: space-between;
// align-items: center;
gap: 16px;
.left {
width: 360px;
height: auto;
align-self: flex-start;
background: #fff;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
.title {
width: 100%;
height: 56px;
display: flex;
align-items: center;
padding: 14px 12px 16px 0;
.box {
width: 8px;
height: 20px;
background-color: rgb(5, 95, 194);
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
margin-right: 14px;
}
.text {
font-size: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(5, 95, 194);
}
}
.left-main {
width: 100%;
height: auto;
padding-left: 24px;
.checkbox-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
row-gap: 16px;
padding-bottom: 16px;
}
:deep(.el-checkbox) {
margin-right: 0;
height: auto;
}
:deep(.el-checkbox__label) {
font-size: 16px;
color: #666666;
font-weight: 400;
}
}
}
.right {
width: 1224px;
height: auto;
background: #fff;
border-radius: 4px;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
.right-title {
width: 100%;
height: 48px;
border-bottom: 1px solid rgb(234, 236, 238);
display: flex;
align-items: center;
img {
width: 22px;
height: 18px;
margin-left: 19px;
}
div {
font-size: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(5, 95, 194);
margin-left: 19px;
}
}
.right-main {
width: 100%;
height: auto;
padding: 24px 35px 0 20px;
.sanction-list {
width: 1169px;
padding: 0px 0 12px 0;
display: flex;
// justify-content: flex-start;
.time {
width: 80px;
// height: 50px;
// font-size: 16px;
// font-weight: 700;
// line-height: 24px;
font-family: "Microsoft YaHei";
color: rgb(5, 95, 194);
margin-right: 16px;
display: flex;
flex-direction: column;
align-items: flex-end;
.year {
font-size: 16px;
font-weight: 700;
line-height: 24px;
}
.date {
font-size: 16px;
font-weight: 700;
line-height: 24px;
}
}
img {
width: 30px;
height: 30px;
border-radius: 50%;
margin-top: 14px;
margin-right: 16px;
}
.main {
width: 1027px;
padding-top: 14px;
position: relative;
.main-title {
font-size: 20px;
font-weight: 700;
line-height: 26px;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
margin-bottom: 11px;
cursor: pointer;
}
.main-desc {
font-size: 16px;
font-weight: 400;
line-height: 30px;
font-family: "Microsoft YaHei";
color: rgb(95, 101, 108);
margin-bottom: 9px;
}
.tag-box {
display: flex;
.tag-item {
padding: 1px 8px;
margin-right: 8px;
border-radius: 4px;
font-size: 14px;
font-weight: 400;
line-height: 22px;
font-family: "Microsoft YaHei";
color: rgb(5, 95, 194);
background-color: rgba(231, 243, 255, 1);
}
}
.count-tag {
position: absolute;
padding: 2px 8px;
top: 0;
right: 0;
font-size: 16px;
font-weight: 400;
line-height: 24px;
font-family: "Microsoft YaHei";
color: rgb(206, 79, 81);
border-radius: 20px;
background-color: rgba(206, 79, 81, 0.1);
}
}
}
}
.right-footer {
width: 100%;
height: 73px;
border-top: 1px solid rgb(234, 236, 238);
padding: 0 31px;
display: flex;
justify-content: space-between;
align-items: center;
.total-count {
font-size: 16px;
font-weight: 400;
line-height: 24px;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
}
}
}
}
</style> </style>
...@@ -104,7 +104,7 @@ export function getPieOption1(data, title) { ...@@ -104,7 +104,7 @@ export function getPieOption1(data, title) {
maxSurfaceAngle: 80 maxSurfaceAngle: 80
}, },
labelLayout: function (params) { labelLayout: function (params) {
console.log("labelLayoutparams", params); // console.log("labelLayoutparams", params);
const isLeft = params.labelRect.x < 556 / 2; const isLeft = params.labelRect.x < 556 / 2;
const points = params.labelLinePoints; const points = params.labelLinePoints;
// Update the end point. // Update the end point.
...@@ -369,7 +369,7 @@ export function getMapOption() { ...@@ -369,7 +369,7 @@ export function getMapOption() {
}); });
} }
} }
console.log(res); // console.log(res);
return res; return res;
} }
...@@ -442,6 +442,13 @@ export function getMapOption() { ...@@ -442,6 +442,13 @@ export function getMapOption() {
return option; return option;
} }
export const getBarChart = (nameList, valueList, color = ["rgba(255, 159, 22, 1)", "rgba(255, 159, 22, 0)"], name) => { export const getBarChart = (nameList, valueList, color = ["rgba(255, 159, 22, 1)", "rgba(255, 159, 22, 0)"], name) => {
let maxVal = Math.max(...(valueList || [0]));
if (maxVal === 0) maxVal = 100;
else maxVal = maxVal * 1.2;
let interval = Math.ceil(maxVal / 8);
if (interval > 5) interval = Math.ceil(interval / 10) * 10;
maxVal = interval * 8;
const option = { const option = {
tooltip: { tooltip: {
trigger: "axis", trigger: "axis",
...@@ -484,6 +491,8 @@ export const getBarChart = (nameList, valueList, color = ["rgba(255, 159, 22, 1) ...@@ -484,6 +491,8 @@ export const getBarChart = (nameList, valueList, color = ["rgba(255, 159, 22, 1)
}, },
yAxis: { yAxis: {
type: "value", type: "value",
max: maxVal,
interval: interval,
axisLine: { axisLine: {
lineStyle: { lineStyle: {
type: "dashed" type: "dashed"
...@@ -582,6 +591,8 @@ export const getLineChart = (object, isPercent) => { ...@@ -582,6 +591,8 @@ export const getLineChart = (object, isPercent) => {
}, },
yAxis: { yAxis: {
type: "value", type: "value",
max: maxVal,
interval: interval,
axisLine: { axisLine: {
lineStyle: { lineStyle: {
type: "dashed" type: "dashed"
...@@ -722,7 +733,7 @@ export const getHorizontalBarChart2 = (nameList, valueList, isPer) => { ...@@ -722,7 +733,7 @@ export const getHorizontalBarChart2 = (nameList, valueList, isPer) => {
["rgba(255, 197, 61, 1)", "rgba(255, 197, 61, 0)"], ["rgba(255, 197, 61, 1)", "rgba(255, 197, 61, 0)"],
["rgba(179, 127, 235, 1)", "rgba(179, 127, 235, 0)"] ["rgba(179, 127, 235, 1)", "rgba(179, 127, 235, 0)"]
]; ];
console.log(colorList); // console.log(colorList);
const option = { const option = {
tooltip: {}, tooltip: {},
grid: { grid: {
...@@ -778,7 +789,7 @@ export const getHorizontalBarChart2 = (nameList, valueList, isPer) => { ...@@ -778,7 +789,7 @@ export const getHorizontalBarChart2 = (nameList, valueList, isPer) => {
barWidth: 8, barWidth: 8,
itemStyle: { itemStyle: {
color: function (params) { color: function (params) {
console.log("params", params); // console.log("params", params);
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [ return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ {
offset: 0, offset: 0,
...@@ -798,6 +809,17 @@ export const getHorizontalBarChart2 = (nameList, valueList, isPer) => { ...@@ -798,6 +809,17 @@ export const getHorizontalBarChart2 = (nameList, valueList, isPer) => {
return option; return option;
}; };
export const getMultipleLineChart = obj => { export const getMultipleLineChart = obj => {
let allValues = [];
obj.data.forEach(item => {
if (item.value) allValues.push(...item.value);
});
let maxVal = Math.max(...(allValues.length > 0 ? allValues : [0]));
if (maxVal === 0) maxVal = 100;
else maxVal = maxVal * 1.2;
let interval = Math.ceil(maxVal / 8);
if (interval > 5) interval = Math.ceil(interval / 10) * 10;
maxVal = interval * 8;
const color = ["rgba(19, 168, 168, 1)", "rgba(146, 84, 222, 1)", "rgba(250, 140, 22, 1)", "rgba(206, 79, 81, 1)"]; const color = ["rgba(19, 168, 168, 1)", "rgba(146, 84, 222, 1)", "rgba(250, 140, 22, 1)", "rgba(206, 79, 81, 1)"];
const option = { const option = {
title: { title: {
...@@ -859,6 +881,8 @@ export const getMultipleLineChart = obj => { ...@@ -859,6 +881,8 @@ export const getMultipleLineChart = obj => {
}, },
yAxis: { yAxis: {
type: "value", type: "value",
max: maxVal,
interval: interval,
axisLine: { axisLine: {
lineStyle: { lineStyle: {
type: "dashed" type: "dashed"
...@@ -904,22 +928,23 @@ export const getMultipleLineChart = obj => { ...@@ -904,22 +928,23 @@ export const getMultipleLineChart = obj => {
export const getMultipleBarChart_m = object => { export const getMultipleBarChart_m = object => {
const list = _.chain(object.data).filter("year").orderBy("year", "asc").value(); const list = _.chain(object.data).filter("year").orderBy("year", "asc").value();
const colors = [ const colors = [
["rgba(22, 119, 255, 1)", "rgba(22, 119, 255, 0)"], ["rgba(45, 123, 248, 1)", "rgba(45, 123, 248, 0)"],
["rgba(206, 79, 81, 1)", "rgba(206, 79, 81, 0)"], ["rgba(206, 79, 81, 1)", "rgba(206, 79, 81, 0)"],
["rgba(255, 197, 61, 1)", "rgba(255, 197, 61, 0)"], ["rgba(255, 197, 61, 1)", "rgba(255, 197, 61, 0)"],
["rgba(255, 204, 199, 1)", "rgba(255, 204, 199, 0)"], ["rgba(255, 182, 193, 1)", "rgba(255, 182, 193, 0)"],
["rgba(179, 127, 235, 1)", "rgba(179, 127, 235, 0)"], ["rgba(159, 122, 234, 1)", "rgba(159, 122, 234, 0)"],
["rgba(127, 218, 235, 1)", "rgba(127, 214, 235, 0)"] ["rgba(90, 200, 220, 1)", "rgba(90, 200, 220, 0)"]
]; ];
const names = _.map(list, "year"); const names = _.map(list, "year");
const datas = _.chain(object.domains) const datas = _.chain(object.domains)
.splice(0, 6) .splice(0, 6)
.map((name, index) => { .map((name, index) => {
console.log(_.map(list, name)); // console.log(_.map(list, name));
return { return {
name, name,
data: _.map(list, `domainNum.${name}`), data: _.map(list, `domainNum.${name}`),
type: "bar", type: "bar",
color: colors[index % colors.length][0],
barWidth: 12, barWidth: 12,
itemStyle: { itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
...@@ -932,7 +957,19 @@ export const getMultipleBarChart_m = object => { ...@@ -932,7 +957,19 @@ export const getMultipleBarChart_m = object => {
}; };
}) })
.value(); .value();
console.log("names", names); // console.log("names", names);
let allValues = [];
datas.forEach(series => {
if (series.data) allValues.push(...series.data);
});
let maxVal = Math.max(...(allValues.length > 0 ? allValues : [0]));
// if (maxVal === 0) maxVal = 100;
// else maxVal = maxVal * 1.2;
// let interval = Math.ceil(maxVal / 5);
// if (interval > 5) interval = Math.ceil(interval / 10) * 10;
// maxVal = interval * 5;
const option = { const option = {
tooltip: { tooltip: {
trigger: "axis", trigger: "axis",
...@@ -941,7 +978,11 @@ export const getMultipleBarChart_m = object => { ...@@ -941,7 +978,11 @@ export const getMultipleBarChart_m = object => {
} }
}, },
grid: { grid: {
top: 50 top: 50,
left: "1%",
right: "1%",
bottom: "3%",
containLabel: true
}, },
legend: { legend: {
// type: "scroll", // type: "scroll",
...@@ -954,7 +995,9 @@ export const getMultipleBarChart_m = object => { ...@@ -954,7 +995,9 @@ export const getMultipleBarChart_m = object => {
data: names data: names
}, },
yAxis: { yAxis: {
type: "value" type: "value",
splitNumber: 5,
alignTicks: false
}, },
series: datas series: datas
}; };
......
import * as echarts from 'echarts' import * as echarts from 'echarts'
const getMultiLineChart = (dataX, dataY1, dataY2) => { const getMultiLineChart = (dataX, dataY1, dataY2) => {
// let maxVal = Math.max(...(dataY1 || [0]), ...(dataY2 || [0]))
// if (maxVal === 0) maxVal = 100
// else maxVal = maxVal * 1.2
// let interval = Math.ceil(maxVal / 5)
// if (interval > 5) interval = Math.ceil(interval / 10) * 10
// maxVal = interval * 5
return { return {
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
...@@ -32,7 +38,9 @@ const getMultiLineChart = (dataX, dataY1, dataY2) => { ...@@ -32,7 +38,9 @@ const getMultiLineChart = (dataX, dataY1, dataY2) => {
], ],
yAxis: [ yAxis: [
{ {
type: 'value' type: 'value',
splitNumber: 5,
alignTicks: false
} }
], ],
series: [ series: [
......
<template>
<div class="data-statistics">
<div class="nav">
<div class="nav-item">
<div class="item-position"></div>
<div class="content">
<div class="info">
<div class="title">中国实体总数</div>
</div>
<div class="number">
<span class="num">{{ totalCount.cnCount }}</span>
<span class="unit"></span>
</div>
</div>
</div>
<div class="nav-item">
<div class="item-position"></div>
<div class="content">
<div class="info">
<div class="title">中国实体总数</div>
<div class="subtitle">50%规则涉及</div>
</div>
<div class="number">
<span class="num">{{ totalCount.cn50RuleCount }}</span>
<span class="unit"></span>
</div>
</div>
</div>
<div class="nav-item">
<div class="item-position"></div>
<div class="content">
<div class="info">
<div class="title">2025年新增</div>
</div>
<div class="number">
<span class="num">{{ totalCount.latestYearCount }}</span>
<span class="unit"></span>
</div>
</div>
</div>
<div class="nav-item">
<div class="item-position"></div>
<div class="content">
<div class="info">
<div class="title">近期制裁新增</div>
<div class="subtitle">2025年11月12日</div>
</div>
<div class="number">
<span class="num">{{ totalCount.latestTimeCount }}</span>
<span class="unit"></span>
</div>
</div>
</div>
</div>
<div class="main">
<div class="main-item">
<div class="title-com">
<div class="box"></div>
<div class="text">制裁实体数量变化情况</div>
<div class="right-group">
<div class="toggle-btns">
<div
class="t-btn"
:class="{ active: activeTab === 'year' }"
@click="handleTabChange('year')"
>
按年度
</div>
<div
class="t-btn"
:class="{ active: activeTab === 'sanction' }"
@click="handleTabChange('sanction')"
>
按制裁
</div>
</div>
<div class="btn">
<img src="../../assets/数据库按钮.png" alt="" />
<img src="../../assets/下载按钮.png" alt="" />
<img src="../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="echarts" ref="sanctionCountChartRef"></div>
<div class="bottom">
<div class="ai">
<div class="left">
<img :src="ai" alt="" class="icon1" />
<div class="text">美国对中国的制裁近年来呈现显著增长趋势。</div>
</div>
<div class="right">
<img :src="right" alt="" class="icon2" />
</div>
</div>
</div>
</div>
<div class="main-item">
<div class="title-com">
<div class="box"></div>
<div class="text">制裁实体地域分布情况</div>
<div class="right-group">
<el-select
v-model="regionTime"
class="time-select"
placeholder="请选择"
@change="getRegionCountData"
>
<el-option v-for="item in timeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<div class="btn">
<img src="../../assets/数据库按钮.png" alt="" />
<img src="../../assets/下载按钮.png" alt="" />
<img src="../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="map-wrapper">
<div class="map-chart" ref="mapChartRef"></div>
<div class="rank-list">
<div class="rank-item" v-for="(item, index) in rankData" :key="index">
<div class="rank-index" :class="'rank-' + (index + 1)">{{ index + 1 }}</div>
<div class="rank-name">{{ item.name }}</div>
<div class="rank-bar-bg">
<div
class="rank-bar-fill"
:style="{ width: (item.value / maxRankValue) * 100 + '%', background: getBarColor(index) }"
></div>
</div>
<div class="rank-value">{{ item.value }}</div>
</div>
</div>
</div>
<div class="bottom">
<div class="ai">
<div class="left">
<img :src="ai" alt="" class="icon1" />
<div class="text">我国被制裁实体多分布于沿海经济活跃省份。</div>
</div>
<div class="right">
<img :src="right" alt="" class="icon2" />
</div>
</div>
</div>
</div>
<div class="main-item">
<div class="title-com">
<div class="box"></div>
<div class="text">制裁实体领域分布情况</div>
<div class="right-group">
<el-select v-model="domainTime" class="time-select" placeholder="请选择" @change="getDomainCountData">
<el-option v-for="item in timeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<div class="btn">
<img src="../../assets/数据库按钮.png" alt="" />
<img src="../../assets/下载按钮.png" alt="" />
<img src="../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="echarts" ref="domainChartRef"></div>
<div class="bottom">
<div class="ai">
<div class="left">
<img :src="ai" alt="" class="icon1" />
<div class="text">美国对中国的制裁集中在半导体、人工智能等领域。</div>
</div>
<div class="right">
<img :src="right" alt="" class="icon2" />
</div>
</div>
</div>
</div>
<div class="main-item">
<div class="title-com">
<div class="box"></div>
<div class="text">制裁实体类型分布情况</div>
<div class="right-group">
<el-select v-model="typeTime" class="time-select" placeholder="请选择" @change="getTypeCountData">
<el-option v-for="item in timeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<div class="btn">
<img src="../../assets/数据库按钮.png" alt="" />
<img src="../../assets/下载按钮.png" alt="" />
<img src="../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="echarts" ref="typeChartRef"></div>
<div class="bottom">
<div class="ai">
<div class="left">
<img :src="ai" alt="" class="icon1" />
<div class="text">我国被制裁实体以企业、科研院所和高校为主。</div>
</div>
<div class="right">
<img :src="right" alt="" class="icon2" />
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, computed } from "vue";
import * as echarts from "echarts";
import chinaJson from "../../../utils/China.json";
import ai from "./assets/ai.png";
import right from "./assets/right.png";
import { getTotalCount, getSanctionCountChange, getRegionCount, getTechDomainCount, getEntityTypeCount } from "@/api/exportControlV2.0";
// 实体清单-数据统计-制裁实体类型分布情况
const typeData = ref([]);
const getTypeCountData = async () => {
// 参数
const param = {};
if (typeTime.value !== "all") {
param.startDate = `${typeTime.value}-01-01`;
param.endDate = `${typeTime.value}-12-31`;
}
try {
const res = await getEntityTypeCount(param);
if(res && res.code === 200) {
const data = res.data || [];
typeData.value = data.map(item => ({
name: item.name,
value: item.count || item.value
}));
updateTypeChart();
}
} catch (error) {
console.error("获取实体清单-数据统计-制裁实体类型分布情况失败:", error);
}
};
// 实体清单-数据统计-制裁实体领域分布情况
const domainData = ref([]);
const getDomainCountData = async () => {
// 参数
const param = {};
if (domainTime.value !== "all") {
param.startDate = `${domainTime.value}-01-01`;
param.endDate = `${domainTime.value}-12-31`;
}
try {
const res = await getTechDomainCount(param);
if(res && res.code === 200) {
const data = res.data || [];
domainData.value = data.map(item => ({
name: item.name,
value: item.count || item.value
}));
updateDomainChart();
}
} catch (error) {
console.error("获取实体清单-数据统计-制裁实体领域分布情况失败:", error);
}
};
// 实体清单-数据统计-制裁实体地域分布情况
const rankData = ref([]);
const maxRankValue = computed(() => {
if (!rankData.value || rankData.value.length === 0) return 50;
return Math.max(...rankData.value.map(item => item.value)) || 50;
});
// 获取实体清单-数据统计-制裁实体地域分布情况
const getRegionCountData = async () => {
// 参数
const param = {};
if (regionTime.value !== "all") {
param.startDate = `${regionTime.value}-01-01`;
param.endDate = `${regionTime.value}-12-31`;
}
try {
const res = await getRegionCount(param);
if(res && res.code === 200) {
const data = res.data || [];
rankData.value = data.map(item => ({
name: item.name,
value: item.count,
lon: item.lon,
lat: item.lat
}));
// Sort by value descending
rankData.value.sort((a, b) => b.value - a.value);
updateMapChart();
}
} catch (error) {
console.error("获取实体清单-数据统计-制裁实体地域分布情况失败:", error);
}
};
// 实体清单-数据统计-制裁实体数量变化情况
const sanctionCountChange = ref([]);
// 获取实体清单-数据统计-制裁实体数量变化情况
const getSanctionCountChangeData = async () => {
// 参数
const param = {
countType: activeTab.value === "year" ? "year" : "record"
};
try {
const res = await getSanctionCountChange(param);
sanctionCountChange.value = res.data || [];
updateSanctionCountChart();
} catch (error) {
console.error("获取实体清单-数据统计-制裁实体数量变化情况失败:", error);
}
};
// 实体清单-数据统计-总量统计
const totalCount = ref(0);
// 获取实体清单-数据统计-总量统计
const getTotalCountData = async () => {
try {
const res = await getTotalCount();
totalCount.value = res.data || 0;
} catch (error) {
console.error("获取实体清单-数据统计-总量统计失败:", error);
}
};
const activeTab = ref("year");
const handleTabChange = tab => {
activeTab.value = tab;
getSanctionCountChangeData();
};
const regionTime = ref("all");
const domainTime = ref("all");
const typeTime = ref("all");
const timeOptions = [
{ label: "全部时间", value: "all" },
{ label: "2025年", value: "2025" },
{ label: "2024年", value: "2024" },
{ label: "2023年", value: "2023" },
{ label: "2022年", value: "2022" },
{ label: "2021年", value: "2021" },
{ label: "2020年", value: "2020" },
{ label: "2019年", value: "2019" },
{ label: "2018年", value: "2018" },
{ label: "2017年", value: "2017" },
{ label: "2016年", value: "2016" },
{ label: "2015年", value: "2015" }
];
const sanctionCountChartRef = ref(null);
const mapChartRef = ref(null);
const domainChartRef = ref(null);
const typeChartRef = ref(null);
const getBarColor = index => {
if (index === 0) return "linear-gradient(90deg, rgba(255, 77, 79, 0) 0%, rgba(255, 77, 79, 1) 100%)";
if (index === 1 || index === 2) return "linear-gradient(90deg, rgba(255, 172, 77, 0) 0%, rgba(255, 172, 77, 1) 100%)";
return "linear-gradient(90deg, rgba(5, 95, 194, 0) 0%, rgba(5, 95, 194, 1) 100%)";
};
const updateSanctionCountChart = () => {
if (!sanctionCountChartRef.value) return;
let chart = echarts.getInstanceByDom(sanctionCountChartRef.value);
if (!chart) {
chart = echarts.init(sanctionCountChartRef.value);
}
const data = sanctionCountChange.value || [];
// 按日期/年份排序 (从小到大)
data.sort((a, b) => {
const dateA = a.year || a.name || a.date || "";
const dateB = b.year || b.name || b.date || "";
// 简单的字符串或数字比较,如果需要复杂的日期解析可以使用 new Date()
if (dateA < dateB) return -1;
if (dateA > dateB) return 1;
return 0;
});
const xData = data.map(item => item.year || item.name || item.date || "");
const yData = data.map(item => item.count || item.value || item.num || 0);
const option = {
grid: {
top: "15%",
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true
},
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow"
}
},
xAxis: {
type: "category",
data: xData.length ? xData : ["2016", "2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024", "2025"],
axisTick: { show: false },
axisLine: { show: false },
axisLabel: {
color: "#909399",
fontFamily: "Microsoft YaHei",
interval: 0,
rotate: xData.length > 10 ? 30 : 0 // 如果数据较多,旋转标签
}
},
yAxis: {
type: "value",
splitLine: {
lineStyle: {
type: "dashed",
color: "#E6EBF5"
}
},
axisLabel: {
color: "#909399",
fontFamily: "Microsoft YaHei"
}
},
series: [
{
data: yData.length ? yData : [200, 250, 300, 400, 500, 600, 610, 650, 750, 850],
type: "bar",
barWidth: 16,
itemStyle: {
borderRadius: [20, 20, 0, 0],
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "rgb(5, 95, 194)" },
{ offset: 1, color: "rgba(5, 95, 194, 0)" }
])
}
}
]
};
chart.setOption(option);
window.addEventListener("resize", () => {
chart.resize();
});
};
const initSanctionCountChart = () => {
updateSanctionCountChart();
};
let mapChartInstance = null;
const updateMapChart = () => {
if (!mapChartRef.value) return;
if (!mapChartInstance) {
mapChartInstance = echarts.init(mapChartRef.value);
echarts.registerMap("china", chinaJson);
window.addEventListener("resize", () => {
mapChartInstance && mapChartInstance.resize();
});
}
const option = {
tooltip: {
show: false
},
geo: {
map: "china",
roam: true,
center: [105, 36],
zoom: 1.6,
top: "10%",
bottom: "10%",
label: {
normal: {
show: false
}
},
itemStyle: {
areaColor: "#eff6ff",
borderColor: "#8cbaff",
borderWidth: 1
},
emphasis: {
itemStyle: {
areaColor: "#dbeafe"
}
}
},
series: [
{
type: "scatter",
coordinateSystem: "geo",
data: rankData.value.map(item => ({
name: item.name,
value: [item.lon, item.lat, item.value]
})),
symbolSize: 8,
itemStyle: {
color: "#ff4d4f",
shadowBlur: 10,
shadowColor: "rgba(255, 77, 79, 0.5)"
},
tooltip: {
show: true,
formatter: params => {
return `${params.name} ${params.value[2]}家`;
}
}
}
]
};
mapChartInstance.setOption(option);
};
const initMapChart = () => {
updateMapChart();
};
const updateDomainChart = () => {
if (!domainChartRef.value) return;
let chart = echarts.getInstanceByDom(domainChartRef.value);
if (!chart) {
chart = echarts.init(domainChartRef.value);
}
let data = domainData.value.length ? [...domainData.value] : [];
// 方案一:合并“其他”项
// 按数值大小降序排序
data.sort((a, b) => b.value - a.value);
const TOP_N = 6; // 保留前6项
let totalValue = 0;
if (data.length > 0) {
totalValue = data.reduce((sum, item) => sum + (item.value || 0), 0);
}
if (data.length > TOP_N) {
const topData = data.slice(0, TOP_N);
const otherData = data.slice(TOP_N);
const otherValue = otherData.reduce((sum, item) => sum + (item.value || 0), 0);
if (otherValue > 0) {
topData.push({
name: "其他",
value: otherValue,
// 将详细数据存入 extra 字段,供 tooltip 使用
extra: otherData
});
}
data = topData;
}
const option = {
tooltip: {
trigger: "item",
formatter: params => {
// 如果是“其他”项,展示详细列表
if (params.name === "其他" && params.data.extra) {
let listStr = params.data.extra
.map(item => {
const percent = totalValue ? ((item.value / totalValue) * 100).toFixed(2) : 0;
return `<div style="display:flex;justify-content:space-between;gap:10px;"><span>${item.name}</span><span style="font-weight:bold">${item.value}家 (${percent}%)</span></div>`;
})
.join("");
return `<div style="text-align:left;">
<div style="font-weight:bold;margin-bottom:5px;">其他 (${params.value}${params.percent}%)</div>
${listStr}
</div>`;
}
// 默认展示
return `${params.name}: ${params.value} (${params.percent}%)`;
}
},
color: [
"#60acfc", // 集成电路 - 蓝色
"#feb64d", // 人工智能 - 橙色
"#5bc49f", // 通信网络 - 青色
"#959cf3", // 量子科技 - 淡蓝紫
"#ff7c7c", // 先进制造 - 红色
"#b689ea", // 新材料 - 紫色
"#32d3eb" // 航空航天 - 浅蓝
],
series: [
{
name: "制裁实体领域分布",
type: "pie",
radius: [73.5, 89.5],
center: ["50%", "50%"],
startAngle: 0,
data: data,
label: {
show: true,
alignTo: "edge",
minMargin: 5,
edgeDistance: 10,
formatter: params => {
return "{name|" + params.name + "}\n{value|" + params.value + "家 " + params.percent + "%}";
},
rich: {
name: {
fontSize: 18,
fontWeight: 700,
color: "rgb(59, 65, 75)",
padding: [0, 0, 5, 0],
fontFamily: "Microsoft YaHei",
lineHeight: 26
},
value: {
fontSize: 16,
fontWeight: 400,
color: "rgb(95, 101, 108)",
fontFamily: "Microsoft YaHei",
lineHeight: 24,
padding: [5, 0, 0, 0]
}
}
},
labelLine: {
show: true,
length: 15,
length2: 0,
maxSurfaceAngle: 80,
lineStyle: {
width: 1.1
}
},
labelLayout: {
hideOverlap: true
},
itemStyle: {
borderWidth: 0
}
}
]
};
chart.setOption(option);
window.addEventListener("resize", () => {
chart.resize();
});
};
const initDomainChart = () => {
updateDomainChart();
};
const updateTypeChart = () => {
if (!typeChartRef.value) return;
let chart = echarts.getInstanceByDom(typeChartRef.value);
if (!chart) {
chart = echarts.init(typeChartRef.value);
}
let data = typeData.value.length ? [...typeData.value] : [
{ value: 50, name: "企业" },
{ value: 32, name: "高校" },
{ value: 32, name: "科研院所" }
];
// 2. 聚合逻辑:保留前5项,其余合并为“其他”
data.sort((a, b) => b.value - a.value);
const TOP_N = 5;
let totalValue = 0;
if (data.length > 0) {
totalValue = data.reduce((sum, item) => sum + (item.value || 0), 0);
}
if (data.length > TOP_N) {
const topData = data.slice(0, TOP_N);
const otherData = data.slice(TOP_N);
const otherValue = otherData.reduce((sum, item) => sum + (item.value || 0), 0);
if (otherValue > 0) {
topData.push({
name: "其他",
value: otherValue,
extra: otherData
});
}
data = topData;
}
const option = {
tooltip: {
trigger: "item",
formatter: params => {
if (params.name === "其他" && params.data.extra) {
let listStr = params.data.extra
.map(item => {
const percent = totalValue ? ((item.value / totalValue) * 100).toFixed(2) : 0;
return `<div style="display:flex;justify-content:space-between;gap:10px;"><span>${item.name}</span><span style="font-weight:bold">${item.value}家 (${percent}%)</span></div>`;
})
.join("");
return `<div style="text-align:left;">
<div style="font-weight:bold;margin-bottom:5px;">其他 (${params.value}${params.percent}%)</div>
${listStr}
</div>`;
}
return `${params.name}: ${params.value} (${params.percent}%)`;
}
},
color: [
"#3B82F6", // 企业 - 蓝色
"#feb64d", // 高校 - 橙色
"#ff9f9f" // 科研院所 - 粉红
],
series: [
{
name: "制裁实体类型分布",
type: "pie",
radius: [73.5, 89.5],
center: ["50%", "50%"],
startAngle: -90,
data: data,
label: {
show: true,
alignTo: "edge",
minMargin: 5,
edgeDistance: 10,
formatter: params => {
return "{name|" + params.name + "}\n{value|" + params.value + "家 " + params.percent + "%}";
},
rich: {
name: {
fontSize: 18,
fontWeight: 700,
color: "rgb(59, 65, 75)",
padding: [0, 0, 5, 0],
fontFamily: "Microsoft YaHei",
lineHeight: 26
},
value: {
fontSize: 16,
fontWeight: 400,
color: "rgb(95, 101, 108)",
fontFamily: "Microsoft YaHei",
lineHeight: 24,
padding: [5, 0, 0, 0]
}
}
},
labelLine: {
show: true,
length: 15,
length2: 0,
maxSurfaceAngle: 80,
lineStyle: {
width: 1
}
},
labelLayout: function (params) {
const isLeft = params.labelRect.x < chart.getWidth() / 2;
const points = params.labelLinePoints;
// Update the end point.
points[2][0] = isLeft ? params.labelRect.x : params.labelRect.x + params.labelRect.width;
return {
labelLinePoints: points
};
},
itemStyle: {
borderWidth: 0
}
}
]
};
chart.setOption(option);
window.addEventListener("resize", () => {
chart.resize();
});
};
const initTypeChart = () => {
updateTypeChart();
};
onMounted(() => {
// initSanctionCountChart();
initMapChart();
initDomainChart();
initTypeChart();
// 获取实体清单-数据统计-总量统计
getTotalCountData();
// 获取实体清单-数据统计-制裁实体数量变化情况
getSanctionCountChangeData();
// 获取实体清单-数据统计-制裁实体地域分布情况
getRegionCountData();
// 获取实体清单-数据统计-制裁实体领域分布情况
getDomainCountData();
// 获取实体清单-数据统计-制裁实体类型分布情况
getTypeCountData();
});
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.data-statistics {
width: 1601px;
margin: 0 auto;
padding-top: 16px;
padding-bottom: 50px;
.nav {
width: 100%;
display: flex;
justify-content: space-between;
margin-bottom: 16px;
.nav-item {
width: 388px;
height: 80px;
border-radius: 10px;
background-color: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
position: relative;
.item-position {
position: absolute;
top: 14px;
left: 0;
width: 8px;
height: 52px;
background-color: rgb(5, 95, 194);
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.content {
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 30px;
padding-right: 40px;
box-sizing: border-box;
.info {
display: flex;
flex-direction: column;
justify-content: center;
.title {
font-size: 20px;
font-weight: 700;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
line-height: 26px;
}
.subtitle {
font-size: 16px;
font-weight: 400;
color: rgb(95, 101, 108);
font-family: "Microsoft YaHei";
margin-top: 5px;
line-height: 24px;
}
}
.number {
display: flex;
align-items: baseline;
.num {
font-size: 32px;
font-weight: 700;
color: #cd4246;
font-family: "Microsoft YaHei";
margin-right: 2px;
}
.unit {
font-size: 32px;
font-weight: 700;
color: #cd4246;
font-family: "Microsoft YaHei";
}
}
}
}
}
.main {
width: 100%;
height: 828px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-content: space-between;
.main-item {
width: 792px;
height: 406px;
background-color: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
border-radius: 10px;
.echarts {
width: 100%;
height: 280px;
padding: 0 20px 0 20px;
margin-bottom: 20px;
}
.bottom {
width: 100%;
height: 40px;
padding: 0 19px 0 17px;
box-sizing: border-box;
.ai {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
background-color: rgba(246, 250, 255, 1);
border-radius: 4px;
border: 1px solid rgba(231, 243, 255, 1);
padding: 6px 12px;
.left {
display: flex;
align-items: center;
.icon1 {
width: 19px;
height: 20px;
margin-right: 13px;
}
.text {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(5, 95, 194);
}
}
.right {
display: flex;
align-items: center;
.icon2 {
width: 24px;
height: 24px;
}
}
}
}
}
}
}
.title-com {
width: 100%;
height: 56px;
display: flex;
align-items: center;
padding: 14px 12px 16px 0;
.box {
width: 8px;
height: 20px;
background-color: rgb(5, 95, 194);
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
margin-right: 14px;
}
.text {
font-size: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(5, 95, 194);
}
.right-group {
margin-left: auto;
display: flex;
align-items: center;
.toggle-btns {
display: flex;
gap: 8px;
margin-right: 20px;
.t-btn {
padding: 2px 8px;
border: 1px solid transparent;
border-radius: 4px;
font-size: 16px;
font-weight: 400;
line-height: 24px;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
cursor: pointer;
box-sizing: border-box;
border: 1px solid rgb(230, 231, 232);
&.active {
color: rgb(5, 95, 194);
background-color: rgba(246, 250, 255, 1);
border-color: rgb(5, 95, 194);
}
}
}
.btn {
margin-left: 0;
}
.time-select {
width: 120px;
height: 28px;
margin-right: 14px;
:deep(.el-input__wrapper) {
height: 28px;
min-height: 28px;
padding: 0 8px;
box-shadow: 0 0 0 1px #dcdfe6 inset;
}
:deep(.el-input__inner) {
height: 28px;
line-height: 28px;
font-size: 14px;
}
}
}
.btn {
width: 92px;
height: 28px;
margin-left: auto;
img {
width: 28px;
height: 28px;
cursor: pointer;
}
img:first-child {
margin-right: 4px;
}
}
}
.map-wrapper {
width: 100%;
height: 280px;
display: flex;
padding: 0 20px 0 20px;
margin-bottom: 20px;
box-sizing: border-box;
.map-chart {
flex: 1;
height: 100%;
}
.rank-list {
flex: 1;
height: 100%;
overflow-y: auto;
padding-left: 20px;
box-sizing: border-box;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 3px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
.rank-item {
height: 36px;
display: flex;
align-items: center;
margin-bottom: 4px;
font-size: 14px;
color: #333;
cursor: pointer;
&:hover {
background-color: rgba(0, 0, 0, 0.02);
}
.rank-index {
width: 24px;
height: 24px;
line-height: 24px;
text-align: center;
border-radius: 50%;
background-color: #e6f4ff;
color: #1677ff;
margin-right: 16px;
font-size: 12px;
flex-shrink: 0;
&.rank-1,
&.rank-2,
&.rank-3 {
background-color: #e6f4ff;
color: #1677ff;
}
}
.rank-name {
width: 70px;
margin-right: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex-shrink: 0;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(59, 65, 75);
}
.rank-bar-bg {
flex: 1;
height: 6px;
background-color: transparent;
border-radius: 3px;
margin-right: 10px;
position: relative;
.rank-bar-fill {
height: 100%;
border-radius: 3px;
}
}
.rank-value {
min-width: 40px;
text-align: right;
color: #666;
flex-shrink: 0;
}
}
}
}
</style>
<template>
<div class="deep-mining">
<div class="side-nav">
<div
v-for="(item, index) in activeTab"
:key="index"
class="tab-item"
:class="{ active: index === activeIndex }"
@click="activeIndex = index"
>
{{ item }}
<span v-if="index === activeIndex" class="arrow"></span>
</div>
</div>
<div class="main">
<div class="left">
<div class="title-com">
<div class="box"></div>
<div class="text">选择制裁</div>
<div class="right-group">
<div class="btn">
<img src="../../assets/数据库按钮.png" alt="" />
<img src="../../assets/下载按钮.png" alt="" />
<img src="../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="left-main">
<div class="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">
<div class="count">{{ sanctionList.length }}次制裁</div>
<!-- 暂时隐藏,说这里可能是轮播图的效果 -->
<!-- <div class="pagination">
<div class="page-btn prev">
<el-icon><ArrowLeft /></el-icon>
</div>
<div class="page-btn next">
<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)"
>
<div class="item-left">{{ item.date }}-{{ item.title }}</div>
<div class="item-right">{{ item.count }}{{ item.unit }}</div>
</div>
</div>
</div>
</div>
<div class="right">
<div class="title-com">
<div class="box"></div>
<div class="text">制裁产业链时序图</div>
<div class="right-group">
<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>
<div class="btn">
<img src="../../assets/数据库按钮.png" alt="" />
<img src="../../assets/下载按钮.png" alt="" />
<img src="../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<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>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } 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";
// 实体清单-深度挖掘-产业链中国企业实体信息查询
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(10000);
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
};
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 handleDateChange = () => {
currentPage.value = 1;
getDeepMiningSelectData();
};
// 翻页
const handlePageChange = (page) => {
if (page < 1 || page > totalPage.value) return;
currentPage.value = page;
getDeepMiningSelectData();
};
// 列表项点击事件
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([
{ 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 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);
};
onMounted(() => {
// 获取选择制裁
getDeepMiningSelectData();
// 获取产业链信息
getIndustryList();
});
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.deep-mining {
width: 1601px;
margin: 0 auto;
position: relative;
// min-height: 800px;
.side-nav {
position: absolute;
top: 27px;
right: 100%;
margin-right: 12px;
display: flex;
flex-direction: column;
gap: 16px;
.tab-item {
cursor: pointer;
padding: 4px 20px;
border-radius: 22px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
white-space: nowrap;
display: flex;
align-items: center;
&.active {
background-color: rgb(5, 95, 194);
color: #fff;
.arrow {
display: inline-block;
width: 0;
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-left: 6px solid #fff;
margin-left: 8px;
}
}
}
}
.main {
width: 100%;
padding-top: 16px;
padding-bottom: 50px;
display: flex;
justify-content: space-between;
.left {
width: 480px;
height: 828px;
background-color: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
border-radius: 10px;
.left-main {
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;
background-color: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
border-radius: 10px;
.right-main {
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;
}
}
}
}
}
}
}
}
}
.title-com {
width: 100%;
height: 56px;
display: flex;
align-items: center;
padding: 14px 12px 16px 0;
.box {
width: 8px;
height: 20px;
background-color: rgb(5, 95, 194);
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
margin-right: 14px;
}
.text {
font-size: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(5, 95, 194);
}
.right-group {
margin-left: auto;
display: flex;
align-items: center;
.toggle-btns {
display: flex;
gap: 8px;
margin-right: 20px;
.t-btn {
height: 28px;
padding: 1px 8px;
border: 1px solid transparent;
border-radius: 4px;
font-size: 16px;
font-weight: 400;
line-height: 24px;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
cursor: pointer;
box-sizing: border-box;
border: 1px solid rgb(230, 231, 232);
&.active {
color: rgb(5, 95, 194);
background-color: rgba(246, 250, 255, 1);
border-color: rgb(5, 95, 194);
}
}
}
.btn {
margin-left: 0;
}
.industry-select {
width: 160px;
height: 28px;
margin-right: 12px;
:deep(.el-input__wrapper) {
height: 28px;
min-height: 28px;
padding: 0 8px;
box-shadow: 0 0 0 1px #dcdfe6 inset;
}
:deep(.el-input__inner) {
height: 28px;
line-height: 28px;
font-size: 14px;
}
}
.time-select {
width: 120px;
height: 28px;
margin-right: 14px;
:deep(.el-input__wrapper) {
height: 28px;
min-height: 28px;
padding: 0 8px;
box-shadow: 0 0 0 1px #dcdfe6 inset;
}
:deep(.el-input__inner) {
height: 28px;
line-height: 28px;
font-size: 14px;
}
}
}
.btn {
width: 92px;
height: 28px;
margin-left: auto;
img {
width: 28px;
height: 28px;
cursor: pointer;
}
img:first-child {
margin-right: 4px;
}
}
}
</style>
<template>
<div class="impact-analysis">
影响分析
</div>
</template>
<script setup>
import { ref } from 'vue'
</script>
<style scoped lang="scss">
*{
margin: 0;
padding: 0;
}
.impact-analysis{
width: 1601px;
margin: 0 auto;
}
</style>
<template>
<div class="introduction-page">
<div class="left">
<div class="left-top">
<div class="title">
<div class="box"></div>
<div class="text">基本信息</div>
<div class="btn">
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
<div class="left-top-main">
<div class="left-top-main-title">
{{ entityInfo.description }}
</div>
<div class="left-top-main-content">
<div class="content-item">
<span class="label">常见列入原因:</span>
<span class="text"
>{{ entityInfo.commonReason }}</span
>
</div>
<div class="content-item">
<span class="label">核心限制措施:</span>
<span class="text"
>{{ entityInfo.restrictiveMeasure }}</span
>
</div>
<div class="content-item">
<span class="label">负责机构:</span>
<span class="text"
>{{ entityInfo.responsibleOrganization }}</span
>
</div>
<div class="content-item">
<span class="label">移除机制:</span>
<span class="text"
>{{ entityInfo.removalMechanism }}</span
>
</div>
</div>
</div>
</div>
<div class="left-bottom">
<div class="title">
<div class="box"></div>
<div class="text">实体清单更新历史</div>
<div class="filters">
<el-select v-model="selectedDomain" placeholder="Select" style="width: 150px; height: 32px; margin-right: 16px">
<el-option
v-for="item in domainOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-checkbox v-model="onlyChina">只看涉华动态</el-checkbox>
</div>
<div class="btn">
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
<div class="left-bottom-main">
<div class="sanction-list" v-for="item in sanctionList" :key="item.id">
<div class="time">
<div class="year">{{ item.year }}</div>
<div class="date">{{ item.date }}</div>
</div>
<img :src="item.icon || title" alt="" />
<div class="main">
<div class="main-title">{{ item.name }}</div>
<el-tooltip
effect="dark"
:content="item.summary"
popper-class="common-prompt-popper"
placement="top"
:show-after="500"
>
<div class="main-desc">{{ item.summary }}</div>
</el-tooltip>
<div class="tag-box">
<div v-for="tag in item.techDomainList" :key="tag" class="tag-item">{{ tag }}</div>
</div>
<div :class="{'count-tag': item.cnEntityCount}">{{ item.cnEntityCount ? `${item.cnEntityCount}家中国实体` : '' }}</div>
</div>
</div>
</div>
<div class="left-footer">
<div class="total-count">{{ totalAll }}项调查</div>
<el-pagination
v-model:current-page="currentPageAll"
:page-size="pageSizeAll"
:total="totalAll"
layout="prev, pager, next"
background
@current-change="handlePageChangeAll"
/>
</div>
</div>
</div>
<div class="right">
<div class="title">
<div class="box"></div>
<div class="text">发布机构</div>
<div class="btn">
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
<div class="right-main">
<div class="right-main-title">
<img :src="publishInfo.imageUrl" alt="">
<div>
<div class="title-text">{{publishInfo.orgNameZh}} ></div>
<div class="title-entext">{{publishInfo.orgName}}</div>
</div>
</div>
<!-- 关键人物 -->
<div class="right-main-key-person">
<div class="key-person-title">
<img :src="icon01" alt="">
<span>关键人物</span>
</div>
<div class="key-person-list">
<div class="person-item" v-for="(item, index) in publishInfo.personList" :key="index">
<img :src="item.imageUrl" alt="">
<div class="person-info">
<div class="name">{{ item.name }}</div>
<div class="title1">{{ item.position }}</div>
</div>
</div>
</div>
</div>
<!-- 机构动态 -->
<div class="right-main-dynamic">
<div class="dynamic-title">
<img :src="icon02" alt="">
<span>机构动态</span>
</div>
<div class="dynamic-list">
<div class="dynamic-item" v-for="(item, index) in publishOrgInfo" :key="item.newsId">
<div class="dot-line">
<div class="dot"></div>
<div class="line" v-if="index !== publishOrgInfo.length - 1"></div>
</div>
<div class="content-box">
<div class="date">{{ item.publishDate }}</div>
<div class="text">{{ item.newsContent }}</div>
</div>
</div>
</div>
<div class="more-btn" v-if="publishOrgInfo.length < dynamicTotal" @click="handleLoadMoreDynamic">
<span>查看更多</span>
<el-icon><ArrowDown /></el-icon>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from "vue";
import title from "../../../../assets/title.png"
import defaultIcon from "../../../../../assets/icons/default-avatar.png"
import icon01 from "../../assets/icon01.png"
import icon02 from "../../assets/icon02.png"
import { ArrowDown } from '@element-plus/icons-vue'
import { getEntityInfo, getPublishInfo, getPublishOrgInfo, getEntityUpdateInfo } from "@/api/exportControlV2.0.js"
const selectedDomain = ref(0);
const onlyChina = ref(false);
const domainOptions = [
{ label: '全部领域', value: 0 },
{ label: '人工智能', value: 1 },
{ label: '生物科技', value: 2 },
{ label: '新一代信息技术', value: 3 },
{ label: '量子科技', value: 4 },
{ label: '新能源', value: 5 },
{ label: '集成电路', value: 6 },
{ label: '海洋', value: 7 },
{ label: '先进制造', value: 8 },
{ label: '新材料', value: 9 },
{ label: '航空航天', value: 10 },
{ label: '深海', value: 11 },
{ label: '极地', value: 12 },
{ label: '太空', value: 13 },
{ label: '核', value: 14 }
];
// const keyPersonList = [
// { name: '杰弗里·凯斯勒', title: '副秘书', avatar: defaultIcon },
// { name: '保罗·达巴尔', title: '副部长', avatar: defaultIcon },
// { name: '朱莉娅·赫尔森斯基', title: '战略贸易副助理部长', avatar: defaultIcon },
// { name: '约翰·斯奎尔斯', title: '专利商标局局长', avatar: defaultIcon },
// ];
// const dynamicList = [
// {
// date: '2025-07-31',
// content: '美商务部发布指南,警告全球企业使用华为昇腾芯片可能违反美国出口管制。意在限制中国AI产业发展,阻碍其获得先进算力。'
// },
// {
// date: '2025-07-30',
// content: '美商务部持续对多种中国产品发起“双反”(反倾销、反补贴)调查并作出裁决,涉及产品从工业原料到日常用品,且裁定的税率普遍较高。'
// },
// {
// date: '2025-07-30',
// content: '美商务部进一步收紧对华先进半导体出口管制,将更多中国实体列入“实体清单”。限制14纳米及以下先进芯片、DRAM等对华出口'
// }
// ];
const sanctionList = ref([]);
const currentPageAll = ref(1);
const pageSizeAll = ref(10);
const totalAll = ref(0);
// 获取实体清单更新历史
const getSanctionUpdate = async () => {
// 传递的参数
// {"isCn":false,"techDomainIds":[1],"typeName":"实体清单","pageNum":1,"pageSize":10}
const data = {
isCn: onlyChina.value,
techDomainIds: selectedDomain.value ? [selectedDomain.value] : [],
typeName: "实体清单",
pageNum: currentPageAll.value,
pageSize: pageSizeAll.value
}
try {
const res = await getEntityUpdateInfo(data)
if (res && res.code === 200) {
console.log(res.data.content)
sanctionList.value = (res.data.content || []).map(item => ({
...item,
year: item.postDate ? item.postDate.split('-')[0] : '',
date: item.postDate ? `${item.postDate.split('-')[1]}月${item.postDate.split('-')[2]}日` : '',
techDomainList: item.techDomainList || [],
icon: ''
}))
totalAll.value = res.data.totalElements || 0
}
} catch (error) {
console.error("获取实体清单更新历史失败:", error)
}
}
// 监听筛选条件变化
watch([selectedDomain, onlyChina], () => {
currentPageAll.value = 1
getSanctionUpdate()
})
const handlePageChangeAll = val => {
currentPageAll.value = val
getSanctionUpdate()
};
// 获取实体清单发布机构
const publishInfo = ref({})
const getPublishInfoFn = async () => {
const params = {
sanTypeId: 1 // 实体清单固定1
}
try {
const res = await getPublishInfo(params)
if (res && res.code === 200) {
publishInfo.value = res.data
// 获取发布机构动态
getPublishOrgInfoFn()
}
} catch (error) {
console.error("获取实体清单发布机构失败:", error)
}
}
// 获取发布机构动态
const publishOrgInfo = ref([])
const dynamicPage = ref(1)
const dynamicPageSize = ref(3)
const dynamicTotal = ref(0)
const getPublishOrgInfoFn = async (isLoadMore = false) => {
if (publishInfo.value && !publishInfo.value.id) return;
const params = {
orgId: publishInfo.value.id,
pageNum: dynamicPage.value,
pageSize: dynamicPageSize.value,
}
try {
const res = await getPublishOrgInfo(params)
if (res && res.code === 200) {
const newRows = (res.data.content || []).map(item => ({
...item,
publishDate: item.newsDate,
title: item.newsTitle
}))
if (isLoadMore) {
publishOrgInfo.value = [...publishOrgInfo.value, ...newRows]
} else {
publishOrgInfo.value = newRows
}
dynamicTotal.value = res.data.totalElements || 0
}
} catch (error) {
console.error("获取发布机构动态失败:", error)
}
}
const handleLoadMoreDynamic = () => {
dynamicPage.value++
getPublishOrgInfoFn(true)
}
// 获取实体清单基本信息
const entityInfo = ref({})
const getEntityInfoFn = async () => {
try {
const res = await getEntityInfo()
if (res && res.code === 200) {
entityInfo.value = res.data
}
} catch (error) {
console.error("获取实体清单基本信息失败:", error)
}
}
onMounted(() => {
// 获取实体清单基本信息
getEntityInfoFn()
// 获取实体清单发布机构
getPublishInfoFn()
// 获取实体清单更新历史
getSanctionUpdate()
})
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.introduction-page {
width: 1601px;
margin: 0 auto;
padding-top: 16px;
padding-bottom: 50px;
display: flex;
justify-content: space-between;
.left {
width: 1064px;
.left-top {
width: 100%;
height: auto;
padding-bottom: 20px;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background-color: #fff;
margin-bottom: 16px;
.left-top-main {
width: 100%;
padding: 8px 26px 0 26px;
.left-top-main-title {
padding: 16px 24px;
border-radius: 4px;
border: 1px solid rgba(231, 243, 255, 1);
background: linear-gradient(to bottom right, rgba(231, 243, 255, 1), rgba(231, 243, 255, 0));
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 30px;
color: rgb(5, 95, 194);
margin-bottom: 17px;
}
.left-top-main-content {
.content-item {
margin-bottom: 13px;
font-size: 16px;
font-family: "Microsoft YaHei";
display: flex;
.label {
width: 120px;
flex-shrink: 0;
font-weight: 700;
color: rgb(59, 65, 75);
white-space: nowrap;
}
.text {
color: rgb(59, 65, 75);
text-align: justify;
}
}
}
}
}
.left-bottom {
width: 100%;
min-height: 1000px;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background-color: #fff;
.title {
border-bottom: 1px solid rgb(234, 236, 238);
.filters {
margin-left: auto;
display: flex;
align-items: center;
margin-right: 20px;
}
.btn {
margin-left: 0;
}
}
.left-bottom-main {
padding: 16px 42px 0 25px;
.sanction-list {
width: 1169px;
padding: 0px 0 12px 0;
display: flex;
// justify-content: flex-start;
.time {
width: 100px;
height: 50px;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
margin-right: 16px;
font-family: "Microsoft YaHei";
color: rgb(5, 95, 194);
font-weight: 700;
.year {
font-size: 16px;
line-height: 24px;
}
.date {
font-size: 16px;
line-height: 24px;
}
}
img {
width: 30px;
height: 30px;
border-radius: 50%;
margin-top: 14px;
margin-right: 16px;
}
.main {
width: 855px;
padding-top: 14px;
position: relative;
.main-title {
width: 700px;
font-size: 20px;
font-weight: 700;
line-height: 26px;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
margin-bottom: 11px;
}
.main-desc {
font-size: 16px;
font-weight: 400;
line-height: 30px;
font-family: "Microsoft YaHei";
color: rgb(95, 101, 108);
margin-bottom: 9px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.tag-box {
display: flex;
.tag-item {
padding: 1px 8px;
margin-right: 8px;
border-radius: 4px;
font-size: 14px;
font-weight: 400;
line-height: 22px;
font-family: "Microsoft YaHei";
color: rgb(5, 95, 194);
background-color: rgba(231, 243, 255, 1);
}
}
.count-tag {
position: absolute;
padding: 2px 8px;
top: 0;
right: 0;
font-size: 16px;
font-weight: 400;
line-height: 24px;
font-family: "Microsoft YaHei";
color: rgb(206, 79, 81);
border-radius: 20px;
background-color: rgba(206, 79, 81, 0.1);
}
}
}
}
.left-footer {
width: 100%;
height: 73px;
border-top: 1px solid rgb(234, 236, 238);
padding: 0 31px;
display: flex;
justify-content: space-between;
align-items: center;
.total-count {
font-size: 16px;
font-weight: 400;
line-height: 24px;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
}
}
}
}
.right {
width: 520px;
height: 941px;
border-radius: 10px;
padding-bottom: 20px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background-color: #fff;
.right-main {
padding: 7px 24px 0px 23px;
.right-main-title {
width: 473px;
height: 110px;
border-radius: 4px;
background-color: rgb(247, 248, 249);
border: 1px solid rgb(234, 236, 238);
display: flex;
margin-bottom: 20px;
padding: 16px;
img {
width: 64px;
height: 64px;
margin-right: 14px;
}
.title-text {
font-size: 20px;
font-weight: 700;
line-height: 26px;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
margin-bottom: 6px;
}
.title-entext {
font-size: 16px;
font-weight: 400;
line-height: 24px;
font-family: "Microsoft YaHei";
color: rgb(95, 101, 108);
}
}
.right-main-key-person {
width: 100%;
padding-bottom: 20px;
border-bottom: 1px solid rgb(234, 236, 238);
margin-bottom: 18px;
.key-person-title {
display: flex;
align-items: center;
margin-bottom: 19px;
img {
width: 14px;
height: 14px;
margin-right: 12px;
}
span {
font-size: 18px;
font-weight: 700;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
}
}
.key-person-list {
display: flex;
flex-wrap: wrap;
.person-item {
width: 185px;
// height: 49px;
display: flex;
align-items: center;
margin-bottom: 16px;
&:nth-child(2n-1) {
margin-left: 28px;
margin-right: 39px;
}
img {
width: 48px;
height: 48px;
border-radius: 50%;
margin-right: 8px;
object-fit: cover;
flex-shrink: 0;
}
.person-info {
.name {
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
margin-bottom: 1px;
white-space: nowrap;
}
.title1 {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
color: rgb(95, 101, 108);
line-height: 1.2;
// white-space: nowrap;
}
}
}
}
}
.right-main-dynamic {
width: 100%;
.dynamic-title {
display: flex;
align-items: center;
margin-bottom: 19px;
img {
width: 14px;
height: 14px;
margin-right: 12px;
}
span {
font-size: 18px;
font-weight: 700;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
}
}
.dynamic-list {
max-height: 500px;
overflow-y: auto;
padding-right: 10px;
/* 滚动条样式 */
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
&:hover {
background: #a8a8a8;
}
}
.dynamic-item {
display: flex;
position: relative;
padding-bottom: 24px;
.dot-line {
width: 12px;
display: flex;
flex-direction: column;
align-items: center;
margin-right: 12px;
margin-top: 6px;
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
border: 2px solid rgb(5, 95, 194);
background-color: #fff;
z-index: 1;
}
.line {
width: 1px;
height: calc(100% - 2px);
background-color: rgb(234, 236, 238);
position: absolute;
top: 12px;
left: 5px;
}
}
.content-box {
flex: 1;
.date {
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
color: rgb(5, 95, 194);
margin-bottom: 8px;
}
.text {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
line-height: 30px;
text-align: justify;
}
}
}
}
.more-btn {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
color: rgb(5, 95, 194);
cursor: pointer;
margin-top: 8px;
span {
margin-right: 4px;
}
}
}
}
}
}
.title {
width: 100%;
height: 56px;
display: flex;
align-items: center;
padding: 14px 12px 16px 0;
.box {
width: 8px;
height: 20px;
background-color: rgb(5, 95, 194);
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
margin-right: 14px;
}
.text {
font-size: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(5, 95, 194);
}
.btn {
width: 60px;
height: 28px;
margin-left: auto;
img {
width: 28px;
height: 28px;
cursor: pointer;
}
img:first-child {
margin-right: 4px;
}
}
}
</style>
<style>
.common-prompt-popper.el-popper {
padding: 8px 16px !important;
border-radius: 10px !important;
background-color: rgb(59, 65, 75) !important;
font-size: 16px !important;
font-weight: 400 !important;
font-family: "Microsoft YaHei" !important;
line-height: 30px !important;
color: #fff !important;
border: none !important;
max-width: 500px !important;
}
.common-prompt-popper.el-popper .el-popper__arrow::before {
background-color: rgb(59, 65, 75) !important;
border-color: rgb(59, 65, 75) !important;
}
</style>
\ No newline at end of file
<template>
<el-dialog
v-model="visible"
width="1280px"
:show-close="false"
:close-on-click-modal="false"
class="rule-subsidiary-dialog"
destroy-on-close
top="5vh"
draggable
>
<template #header>
<div class="dialog-header">
<div class="left-title">
<img :src="defaultIcon" alt="" class="title-icon" />
<span class="company-name">{{ companyName }}</span>
<span class="suffix-text">“实体清单50%规则” 涉及实体列表</span>
</div>
<div class="right-actions">
<div class="right-count">
<span class="highlight">{{ totalCount }}</span>
</div>
<el-icon class="close-btn" @click="visible = false"><Close /></el-icon>
</div>
</div>
</template>
<div class="dialog-content">
<el-table
:data="tableData"
table-layout="fixed"
:row-class-name="tableRowClassName"
:header-cell-style="{ background: '#fff' }"
style="width: 100%"
>
<el-table-column label="实体名称" min-width="300">
<template #default="{ row }">
<div class="entity-name-cell">
<el-avatar class="avatar" :size="24" :src="row.avatar || defaultIcon" />
<div class="name" :title="row.name">{{ row.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="涉及领域" width="200">
<template #default="{ row }">
<div class="domain-cell">
<el-tag
v-for="tag in row.domains"
:key="tag"
class="domain-tag"
effect="plain"
:disable-transitions="true"
:style="getTagStyle(tag)"
>
{{ tag }}
</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="equityRatio" label="股权占比" width="120" align="center" />
<el-table-column prop="location" label="上市地点" width="120" align="center" />
<el-table-column prop="revenue" label="营收(亿元)" width="120" align="center" />
</el-table>
<div class="dialog-footer">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:total="totalCount"
layout="prev, pager, next"
prev-text="<"
next-text=">"
@current-change="handleCurrentChange"
/>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref, defineProps, defineEmits, computed, watch } from "vue";
import { Close } from "@element-plus/icons-vue";
import defaultIcon from "../../../../../assets/icons/default-avatar.png";
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
companyName: {
type: String,
default: ""
},
totalCount: {
type: Number,
default: 0
}
});
const emit = defineEmits(["update:modelValue"]);
const visible = computed({
get: () => props.modelValue,
set: val => emit("update:modelValue", val)
});
const currentPage = ref(1);
const pageSize = ref(10);
// Mock Data
const tableData = ref([
{
id: 1,
name: "讯飞智元信息科技有限公司",
domains: ["人工智能"],
equityRatio: "100%",
location: "深圳",
revenue: 325
},
{
id: 2,
name: "科大讯飞(北京)有限公司",
domains: ["通信网络", "集成电路"],
equityRatio: "100%",
location: "--",
revenue: 305
},
{
id: 3,
name: "深圳讯飞智慧科技有限公司",
domains: ["人工智能"],
equityRatio: "100%",
location: "深圳",
revenue: 325
},
{
id: 4,
name: "安徽讯飞寰语科技有限公司",
domains: ["通信网络", "集成电路"],
equityRatio: "100%",
location: "--",
revenue: 305
},
{
id: 5,
name: "天津讯飞信息科技有限公司",
domains: ["航空航天"],
equityRatio: "100%",
location: "--",
revenue: 289
},
{
id: 6,
name: "安徽听见科技有限公司",
domains: ["人工智能"],
equityRatio: "100%",
location: "深圳",
revenue: 270
},
{
id: 7,
name: "广州市讯飞樽鸿信息技术有限公司",
domains: ["航空航天"],
equityRatio: "69.8%",
location: "--",
revenue: 289
},
{
id: 8,
name: "苏州科大讯飞教育科技有限公司",
domains: ["人工智能"],
equityRatio: "66.3%",
location: "深圳",
revenue: 270
},
{
id: 9,
name: "深圳讯飞互动电子有限公司",
domains: ["人工智能"],
equityRatio: "57.1%",
location: "香港",
revenue: 255
},
{
id: 10,
name: "讯飞医疗科技股份有限公司",
domains: ["生物技术"],
equityRatio: "51%",
location: "深圳",
revenue: 270
}
]);
const handleCurrentChange = val => {
console.log("Page changed:", val);
};
const tableRowClassName = ({ rowIndex }) => {
return rowIndex % 2 === 0 ? "odd-row" : "";
};
const getTagStyle = tag => {
const colorPool = [
{ color: "#CD4246", background: "rgba(255, 242, 240, 1)", borderColor: "rgba(255, 163, 158, 1)" },
{ color: "#0E78F1", background: "rgba(230, 244, 255, 1)", borderColor: "rgba(145, 206, 255, 1)" },
{ color: "#722ED1", background: "rgba(249, 240, 255, 1)", borderColor: "rgba(211, 173, 247, 1)" },
{ color: "#13A8A8", background: "rgba(230, 255, 251, 1)", borderColor: "rgba(135, 232, 222, 1)" },
{ color: "#FA8C16", background: "rgba(255, 247, 230, 1)", borderColor: "rgba(255, 213, 145, 1)" },
{ color: "#52C41A", background: "rgba(246, 255, 237, 1)", borderColor: "rgba(183, 235, 143, 1)" }
];
let hash = 0;
for (let i = 0; i < tag.length; i++) {
hash = tag.charCodeAt(i) + ((hash << 5) - hash);
}
const index = Math.abs(hash) % colorPool.length;
return colorPool[index];
};
</script>
<style lang="scss" scoped>
.rule-subsidiary-dialog {
:deep(.el-dialog__header) {
margin-right: 0;
padding: 24px;
border-bottom: 1px solid #edeff2;
}
:deep(.el-dialog__body) {
padding: 0;
}
}
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
.left-title {
display: flex;
align-items: center;
.title-icon {
width: 24px;
height: 24px;
margin-right: 8px;
}
.company-name {
font-size: 18px;
font-weight: 700;
color: #3b414b;
margin-right: 8px;
font-family: "Microsoft YaHei";
}
.suffix-text {
font-size: 18px;
font-weight: 700;
color: #3b414b;
font-family: "Microsoft YaHei";
}
}
.right-actions {
display: flex;
align-items: center;
.right-count {
font-size: 14px;
color: #5f656c;
font-weight: 700;
font-family: "Microsoft YaHei";
margin-right: 20px;
.highlight {
color: #cd4246;
margin: 0 4px;
font-size: 16px;
}
}
.close-btn {
font-size: 20px;
color: #909399;
cursor: pointer;
transition: color 0.2s;
&:hover {
color: #409eff;
}
}
}
}
.dialog-content {
height: 722px; /* Adjusted to fit total 851px approx with header (65px) and footer (64px) */
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 0 24px;
:deep(.el-table) {
--el-table-header-bg-color: #fff;
--el-table-border-color: transparent;
--el-table-row-hover-bg-color: rgba(248, 249, 250, 1);
}
:deep(.el-table__inner-wrapper::before) {
background-color: transparent;
}
:deep(.el-table__header-wrapper th) {
height: 60px;
background-color: #fff;
font-size: 16px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
font-family: "Microsoft YaHei";
border-bottom: 1px solid rgba(230, 231, 232, 1);
}
:deep(.el-table__header-wrapper .cell) {
line-height: 22px;
}
:deep(.el-table__row) {
height: 64px;
}
:deep(.el-table__cell) {
border-bottom: 0;
font-size: 16px;
font-weight: 400;
color: rgb(95, 101, 108);
font-family: "Microsoft YaHei";
}
:deep(.odd-row td.el-table__cell) {
background-color: rgba(248, 249, 250, 1);
}
.entity-name-cell {
display: flex;
align-items: center;
.avatar {
flex: 0 0 24px;
margin-right: 9px;
}
.name {
font-size: 16px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.domain-cell {
display: flex;
align-items: center;
gap: 8px;
:deep(.el-tag) {
height: auto;
padding: 2px 8px;
border-radius: 4px;
font-size: 14px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 20px;
}
}
}
.dialog-footer {
height: 64px;
display: flex;
justify-content: center;
align-items: center;
border-top: 1px solid #edeff2;
:deep(.el-pagination) {
--el-pagination-button-bg-color: #fff;
--el-pagination-hover-color: #0e78f1;
--el-pagination-font-size: 14px;
.el-pager li {
border: 1px solid #dcdfe6;
border-radius: 4px;
margin: 0 4px;
font-weight: 400;
color: #5f656c;
min-width: 32px;
height: 32px;
line-height: 30px;
&.is-active {
background-color: #0e78f1;
color: #fff;
border-color: #0e78f1;
}
&:hover:not(.is-active) {
color: #0e78f1;
border-color: #0e78f1;
}
}
.btn-prev,
.btn-next {
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0;
margin: 0 4px;
min-width: 32px;
height: 32px;
line-height: 30px;
text-align: center;
&:hover {
color: #0e78f1;
border-color: #0e78f1;
}
&[disabled] {
border-color: #e4e7ed;
color: #c0c4cc;
}
}
}
}
</style>
\ No newline at end of file
<template>
<div class="list-page">
<div class="search-box">
<el-input v-model="searchKeyword" class="search-input" placeholder="搜索实体" :suffix-icon="Search" />
<div class="filters">
<el-checkbox v-model="onlyChina" label="只看中国实体" />
</div>
</div>
<div class="main">
<div class="left">
<div class="title">
<div class="box"></div>
<div class="text">科技领域</div>
</div>
<div class="checkbox-group">
<el-checkbox v-for="(item, index) in techFields" :key="index" v-model="item.checked" :label="item.label" @change="handleFilterChange(item, techFields, 'tech')" />
</div>
<div class="title">
<div class="box"></div>
<div class="text">实体类型</div>
</div>
<div class="checkbox-group">
<el-checkbox v-for="(item, index) in entityTypes" :key="index" v-model="item.checked" :label="item.label" @change="handleFilterChange(item, entityTypes, 'type')" />
</div>
<div class="title">
<div class="box"></div>
<div class="text">制裁时间</div>
</div>
<div class="checkbox-group">
<el-checkbox v-for="(item, index) in sanctionTimes" :key="index" v-model="item.checked" :label="item.label" @change="handleFilterChange(item, sanctionTimes, 'time')" />
<div v-if="sanctionTimes.find(i => i.value === 'custom' && i.checked)" class="custom-date-picker">
<el-date-picker
v-model="customDateRange"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</div>
</div>
</div>
<div class="right">
<div class="title">
<div class="left-wrapper">
<div class="box"></div>
<div class="text">实体清单</div>
</div>
<div class="right-wrapper">
<div class="stats">
<div class="dot"></div>
<div class="count-text"><span class="highlight">{{ruleCount.totalCount}}</span></div>
<div class="rule-text">(50%规则涉及<span class="highlight">{{ruleCount.ruleCount}}</span>家)</div>
</div>
<div class="btn">
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="right-table">
<el-table
:data="entityRows"
table-layout="fixed"
:row-class-name="tableRowClassName"
:header-cell-style="{ background: '#fff' }"
>
<el-table-column label="实体名称" min-width="200">
<template #default="{ row }">
<div class="entity-name-cell">
<el-image
v-if="row.img"
class="avatar"
:src="row.img"
alt=""
></el-image>
<div v-else class="avatar-undefined">
{{
(row.entityNameZh || row.entityName)?.match(
/[\u4e00-\u9fa5a-zA-Z0-9]/
)?.[0]
}}
</div>
<CommonPrompt :content="row.entityNameZh || row.entityName" style="flex: 1; overflow: hidden" />
</div>
</template>
</el-table-column>
<el-table-column label="涉及领域" min-width="150">
<template #default="{ row }">
<div class="domain-cell">
<el-tag
v-for="tag in row.techDomains"
:key="tag"
class="domain-tag"
effect="plain"
:disable-transitions="true"
:style="getTagStyle(tag)"
>
{{ tag }}
</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="startTime" label="制裁时间" width="140" show-overflow-tooltip align="center" />
<el-table-column label="50%规则子企业" min-width="280" show-overflow-tooltip align="right">
<template #default="{ row }">
<div class="rule-cell" v-if="row.ruleOrgCount > 0">
<div class="rule-text" :title="row.ruleOrgList && row.ruleOrgList.length > 0 ? row.ruleOrgList[0].orgName : ''">
{{ row.ruleOrgList && row.ruleOrgList.length > 0 ? row.ruleOrgList[0].orgName : '' }}...等
</div>
<el-link class="rule-link" type="primary" :underline="false" @click="handleRuleClick(row)">{{ row.ruleOrgCount }}家 ></el-link>
</div>
</template>
</el-table-column>
</el-table>
</div>
<div class="tight-footer">
<div class="total-text">共{{ total }}项</div>
<el-pagination
:current-page="currentPage"
v-model:page-size="pageSize"
:total="total"
layout="prev, pager, next"
prev-text="<"
next-text=">"
@current-change="handleCurrentChange"
/>
</div>
</div>
</div>
</div>
<RuleSubsidiaryDialog
v-model="ruleDialogVisible"
:company-name="currentRuleCompany"
:total-count="currentRuleCount"
/>
</template>
<script setup>
import { ref, computed, onMounted, watch } from "vue";
import { Search } from "@element-plus/icons-vue";
import defaultIcon from "../../../../../assets/icons/default-avatar.png";
import RuleSubsidiaryDialog from "./RuleSubsidiaryDialog.vue";
import { getExportControlList, get50PercentEntityCount } from "@/api/exportControlV2.0.js"
import CommonPrompt from '@/views/exportControl/commonPrompt/index.vue'
const searchKeyword = ref("");
const onlyChina = ref(false);
const sanctionTime = ref("");
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(0);
const entityRows = computed(() => mainList.value);
const handleCurrentChange = (val) => {
if (val === currentPage.value) return;
currentPage.value = val;
getExportControlListApi();
};
const getTagStyle = tag => {
// 预设颜色池
const colorPool = [
{ color: "#CD4246", background: "rgba(255, 242, 240, 1)", borderColor: "rgba(255, 163, 158, 1)" }, // 红色
{ color: "#0E78F1", background: "rgba(230, 244, 255, 1)", borderColor: "rgba(145, 206, 255, 1)" }, // 蓝色
{ color: "#722ED1", background: "rgba(249, 240, 255, 1)", borderColor: "rgba(211, 173, 247, 1)" }, // 紫色
{ color: "#13A8A8", background: "rgba(230, 255, 251, 1)", borderColor: "rgba(135, 232, 222, 1)" }, // 青色
{ color: "#FA8C16", background: "rgba(255, 247, 230, 1)", borderColor: "rgba(255, 213, 145, 1)" }, // 橙色
{ color: "#52C41A", background: "rgba(246, 255, 237, 1)", borderColor: "rgba(183, 235, 143, 1)" } // 绿色
];
// 使用简单的哈希算法将字符串映射到索引
let hash = 0;
for (let i = 0; i < tag.length; i++) {
hash = tag.charCodeAt(i) + ((hash << 5) - hash);
}
// 确保索引为正数并取模
const index = Math.abs(hash) % colorPool.length;
return colorPool[index];
};
const tableRowClassName = ({ rowIndex }) => {
return rowIndex % 2 === 0 ? "odd-row" : "";
};
// 左侧筛选数据
const techFields = ref([
{ label: "全部领域", value: "all", checked: true },
{ label: "人工智能", value: "1", checked: false },
{ label: "生物科技", value: "2", checked: false },
{ label: "新一代信息技术", value: "3", checked: false },
{ label: "量子科技", value: "4", checked: false },
{ label: "新能源", value: "5", checked: false },
{ label: "集成电路", value: "6", checked: false },
{ label: "海洋", value: "7", checked: false },
{ label: "先进制造", value: "8", checked: false },
{ label: "新材料", value: "9", checked: false },
{ label: "航空航天", value: "10", checked: false },
{ label: "深海", value: "11", checked: false },
{ label: "极地", value: "12", checked: false },
{ label: "太空", value: "13", checked: false },
{ label: "核", value: "14", checked: false }
]);
const entityTypes = ref([
{ label: "全部类型", value: "all", checked: true },
{ label: "科研院校", value: "2", checked: false },
{ label: "高校", value: "3", checked: false },
{ label: "企业", value: "4", checked: false }
]);
const sanctionTimes = ref([
{ label: "全部时间", value: "all", checked: true },
{ label: "2025年", value: "2025", checked: false },
{ label: "2024年", value: "2024", checked: false },
{ label: "2023年", value: "2023", checked: false },
{ label: "2022年", value: "2022", checked: false },
{ label: "2021年", value: "2021", checked: false },
{ label: "自定义", value: "custom", checked: false }
]);
const customDateRange = ref("");
const ruleDialogVisible = ref(false);
const currentRuleCompany = ref("");
const currentRuleCount = ref(0);
const handleRuleClick = (row) => {
currentRuleCompany.value = row.entityName;
currentRuleCount.value = row.ruleOrgCount;
ruleDialogVisible.value = true;
};
// 筛选逻辑处理
const handleFilterChange = (item, list, type) => {
// 如果点击的是"全部"
if (item.value === 'all') {
if (item.checked) {
// 选中全部,取消其他所有
list.forEach(i => {
if (i.value !== 'all') i.checked = false;
});
} else {
// 取消全部(通常不允许全部取消,至少得选一个,这里如果取消全部,就默认为全部选中)
item.checked = true;
}
} else {
// 点击的是具体项
if (item.checked) {
// 选中具体项,取消"全部"
const allItem = list.find(i => i.value === 'all');
if (allItem) allItem.checked = false;
// 特殊处理制裁时间的自定义和其他年份互斥
if (type === 'time') {
if (item.value === 'custom') {
list.forEach(i => {
if (i.value !== 'custom' && i.value !== 'all') i.checked = false;
});
} else {
const customItem = list.find(i => i.value === 'custom');
if (customItem) customItem.checked = false;
}
}
} else {
// 取消具体项,检查是否还有选中的
const anyChecked = list.some(i => i.checked);
if (!anyChecked) {
const allItem = list.find(i => i.value === 'all');
if (allItem) allItem.checked = true;
}
}
}
// 重置页码并查询
currentPage.value = 1;
getExportControlListApi();
};
// 获取实体清单列表
const mainList = ref([]);
const isFetching = ref(false);
const ruleCount = ref(0);
let abortController = null;
const getExportControlListApi = async () => {
// 取消上一轮未完成的请求
if (abortController) {
try { abortController.abort(); } catch {}
}
abortController = new AbortController();
isFetching.value = true;
// 处理科技领域筛选
let techDomains = [];
const allTech = techFields.value.find(item => item.value === 'all');
if (!allTech || !allTech.checked) {
techDomains = techFields.value
.filter(item => item.checked && item.value !== 'all')
.map(item => item.value);
}
// 处理实体类型筛选
let typeIds = [];
const allType = entityTypes.value.find(item => item.value === 'all');
if (!allType || !allType.checked) {
typeIds = entityTypes.value
.filter(item => item.checked && item.value !== 'all')
.map(item => Number(item.value));
}
// 处理制裁时间筛选
let years = [];
let startDate = undefined;
let endDate = undefined;
const allTime = sanctionTimes.value.find(item => item.value === 'all');
if (!allTime || !allTime.checked) {
years = sanctionTimes.value
.filter(item => item.checked && item.value !== 'all' && item.value !== 'custom')
.map(item => Number(item.value));
const customTime = sanctionTimes.value.find(item => item.value === 'custom');
if (customTime && customTime.checked && customDateRange.value && customDateRange.value.length === 2) {
const start = new Date(customDateRange.value[0]);
const end = new Date(customDateRange.value[1]);
startDate = `${start.getFullYear()}-${String(start.getMonth() + 1).padStart(2, '0')}-${String(start.getDate()).padStart(2, '0')}`;
endDate = `${end.getFullYear()}-${String(end.getMonth() + 1).padStart(2, '0')}-${String(end.getDate()).padStart(2, '0')}`;
}
}
const data = {
typeName: "实体清单",
isCn: onlyChina.value,
techDomains: techDomains.length > 0 ? techDomains : undefined,
entityTypes: typeIds.length > 0 ? typeIds : undefined,
years: years.length > 0 ? years : undefined,
startDate,
endDate,
keyword: searchKeyword.value || undefined,
pageNum: currentPage.value,
pageSize: pageSize.value
};
try {
const res = await getExportControlList(data, { signal: abortController.signal });
// 50%规则涉及实体数
const countRes = await get50PercentEntityCount(data);
if (countRes.code === 200) {
ruleCount.value = countRes.data;
}
if (res.code === 200) {
mainList.value = res.data.content;
total.value = res.data.totalElements;
}
} catch (error) {
if (!error || (error.code !== 'ERR_CANCELED' && error.name !== 'CanceledError' && error.name !== 'AbortError')) {
console.error(error);
}
} finally {
isFetching.value = false;
abortController = null;
}
}
watch(onlyChina, () => {
currentPage.value = 1;
getExportControlListApi();
});
const searchDebounceTimer = ref(null);
watch(searchKeyword, () => {
if (searchDebounceTimer.value) {
clearTimeout(searchDebounceTimer.value);
}
searchDebounceTimer.value = setTimeout(() => {
currentPage.value = 1;
getExportControlListApi();
}, 300);
});
onMounted(() => {
getExportControlListApi();
});
watch(customDateRange, () => {
if (sanctionTimes.value.find(item => item.value === 'custom' && item.checked)) {
currentPage.value = 1;
getExportControlListApi();
}
});
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.list-page {
width: 1601px;
padding-bottom: 50px;
margin: 0 auto;
padding-top: 16px;
.search-box {
margin-bottom: 16px;
display: flex;
justify-content: space-between;
align-items: center;
.search-input {
width: 388px;
height: 32px;
:deep(.el-input__wrapper) {
padding: 0 11px;
}
:deep(.el-input__inner) {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
}
}
.filters {
display: flex;
align-items: center;
.el-checkbox {
margin-right: 20px;
color: rgb(59, 65, 75);
}
.time-select {
width: 160px;
height: 32px;
}
}
}
.main {
width: 100%;
display: flex;
justify-content: space-between;
align-items: flex-start;
.left {
padding-bottom: 20px;
width: 388px;
height: auto;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background-color: #fff;
.checkbox-group {
display: flex;
flex-wrap: wrap;
padding: 0 0 0 24px;
.el-checkbox {
width: 50%;
margin-right: 0;
margin-bottom: 4px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
}
.custom-date-picker {
width: 100%;
margin-top: 8px;
padding-right: 24px;
box-sizing: border-box;
:deep(.el-date-editor) {
width: 100%;
height: 32px;
box-shadow: none;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 0 10px;
&:hover {
border-color: #c0c4cc;
}
&.is-active {
border-color: #409eff;
}
.el-range-input {
font-size: 14px;
font-family: "Microsoft YaHei";
color: rgb(95, 101, 108);
}
.el-range-separator {
color: rgb(95, 101, 108);
line-height: 30px;
}
.el-input__icon {
line-height: 32px;
color: rgb(95, 101, 108);
}
}
}
}
}
.right {
width: 1196px;
height: auto;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background-color: #fff;
.title {
width: 100%;
height: 56px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 12px 16px 0;
box-sizing: border-box;
.left-wrapper {
display: flex;
align-items: center;
.box {
width: 8px;
height: 20px;
background-color: rgb(5, 95, 194);
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
margin-right: 14px;
}
.text {
font-size: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(5, 95, 194);
}
}
.right-wrapper {
display: flex;
align-items: center;
.stats {
display: flex;
align-items: center;
margin-right: 24px;
.dot {
width: 8px;
height: 8px;
background-color: #cd4246;
border-radius: 50%;
margin-right: 8px;
}
.count-text {
font-size: 16px;
font-weight: 700;
color: #3b414b;
font-family: "Microsoft YaHei";
margin-right: 16px;
.highlight {
color: #cd4246;
margin: 0 4px;
}
}
.rule-text {
font-size: 14px;
color: #5f656c;
font-family: "Microsoft YaHei";
.highlight {
color: #cd4246;
}
}
}
.btn {
width: 60px;
height: 28px;
img {
width: 28px;
height: 28px;
cursor: pointer;
}
img:first-child {
margin-right: 4px;
}
}
}
}
.right-table {
padding: 5px 21px 0 21px;
:deep(.el-table) {
--el-table-header-bg-color: #fff;
--el-table-border-color: transparent;
--el-table-row-hover-bg-color: rgba(248, 249, 250, 1);
}
:deep(.el-table__inner-wrapper::before) {
background-color: transparent;
}
:deep(.el-table__header-wrapper th) {
height: 60px;
background-color: #fff;
font-size: 16px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
font-family: "Microsoft YaHei";
border-bottom: 1px solid rgba(230, 231, 232, 1);
border-top: 1px solid rgba(230, 231, 232, 1);
}
:deep(.el-table__header-wrapper .cell) {
line-height: 22px;
}
:deep(.el-table__header-wrapper th:first-child .cell) {
padding-left: 39px;
}
:deep(.el-table__row) {
height: 64px;
}
:deep(.el-table__cell) {
border-bottom: 0;
font-size: 16px;
font-weight: 400;
color: rgb(95, 101, 108);
font-family: "Microsoft YaHei";
}
:deep(.el-table__row .el-table__cell:first-child .cell) {
padding-left: 39px;
}
:deep(.odd-row td.el-table__cell) {
background-color: rgba(248, 249, 250, 1);
}
.entity-name-cell {
display: flex;
align-items: center;
.avatar {
width: 24px;
height: 24px;
margin-right: 8px;
border-radius: 4px;
}
.avatar-undefined {
width: 24px;
height: 24px;
margin-right: 8px;
border-radius: 4px;
background-color: #055fc2;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
font-weight: 500;
}
.name {
font-size: 16px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.domain-cell {
display: flex;
align-items: center;
gap: 8px;
:deep(.el-tag) {
height: auto;
padding: 2px 8px;
border-radius: 4px;
font-size: 14px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 20px;
border: 1px solid;
}
}
.rule-cell {
display: flex;
align-items: center;
gap: 12px;
.rule-text {
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.rule-link {
font-size: 16px;
font-weight: 400;
color: rgb(5, 95, 194);
cursor: pointer;
white-space: nowrap;
}
}
}
.tight-footer {
padding: 16px 24px;
display: flex;
justify-content: space-between;
align-items: center;
.total-text {
font-size: 14px;
font-weight: 400;
color: #5F656C;
font-family: "Microsoft YaHei";
}
:deep(.el-pagination) {
--el-pagination-button-bg-color: #fff;
--el-pagination-hover-color: #0E78F1;
--el-pagination-font-size: 14px;
.el-pager li {
border: 1px solid #DCDFE6;
border-radius: 4px;
margin: 0 4px;
font-weight: 400;
color: #5F656C;
min-width: 32px;
height: 32px;
line-height: 30px;
&.is-active {
background-color: #0E78F1;
color: #fff;
border-color: #0E78F1;
}
&:hover:not(.is-active) {
color: #0E78F1;
border-color: #0E78F1;
}
}
.btn-prev,
.btn-next {
border: 1px solid #DCDFE6;
border-radius: 4px;
padding: 0;
margin: 0 4px;
min-width: 32px;
height: 32px;
line-height: 30px;
text-align: center;
&:hover {
color: #0E78F1;
border-color: #0E78F1;
}
&[disabled] {
border-color: #E4E7ED;
color: #C0C4CC;
}
}
}
}
}
}
}
.title {
width: 100%;
height: 56px;
display: flex;
align-items: center;
padding: 14px 12px 16px 0;
.box {
width: 8px;
height: 16px;
background-color: rgb(5, 95, 194);
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
margin-right: 14px;
}
.text {
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(5, 95, 194);
}
}
</style>
<template>
<div class="sanctions-overview">
<div class="side-nav">
<div v-for="(item, index) in activeTab" :key="index" class="tab-item" :class="{'active': index === activeIndex}" @click="activeIndex = index">
{{item}}
<span v-if="index === activeIndex" class="arrow"></span>
</div>
</div>
<div class="content-box">
<introductionPage v-if="activeIndex === 0"></introductionPage>
<listPage v-if="activeIndex === 1"></listPage>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import introductionPage from "./components/introductionPage/index.vue"
import listPage from "./components/listPage/index.vue"
const activeTab = ref(["实体清单简介", "实体清单列表"])
const activeIndex = ref(0)
</script>
<style scoped lang="scss">
*{
margin: 0;
padding: 0;
}
.sanctions-overview{
width: 1601px;
margin: 0 auto;
position: relative;
// min-height: 800px;
.side-nav {
position: absolute;
top: 27px;
right: 100%;
margin-right: 12px;
display: flex;
flex-direction: column;
gap: 16px;
.tab-item {
cursor: pointer;
padding: 4px 20px;
border-radius: 22px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
white-space: nowrap;
display: flex;
align-items: center;
&.active {
background-color: rgb(5, 95, 194);
color: #fff;
.arrow {
display: inline-block;
width: 0;
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-left: 6px solid #fff;
margin-left: 8px;
}
}
}
}
.content-box {
width: 100%;
}
}
</style>
<template>
<div class="entity-list">
<div class="header">
<div class="header-title">
<img :src="headerTitle.img" alt="">
<div>
<div class="title">
{{ headerTitle.title }}
<span>{{ headerTitle.titleEn }}</span>
</div>
<div class="department">
{{ headerTitle.department }}
</div>
</div>
<div class="btn">
<img :src="icon01" alt="">切换
</div>
</div>
<div class="header-nav">
<div
class="nav-item"
v-for="(item, index) in headerNavList"
:key="index"
:class="{ active: activeIndex === index }"
@click="activeIndex = index"
>
<img :src="activeIndex === index ? item.imgActive : item.img" alt="">
<span>{{ item.title }}</span>
<div class="active-line" v-if="activeIndex === index"></div>
</div>
</div>
</div>
<div class="main">
<sanctions-overview v-if="activeIndex === 0"></sanctions-overview>
<data-statistics v-if="activeIndex === 1"></data-statistics>
<deep-mining v-if="activeIndex === 2"></deep-mining>
<impact-analysis v-if="activeIndex === 3"></impact-analysis>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import sanctionsOverview from "./components/sanctionsOverview/index.vue"
import dataStatistics from "./components/dataStatistics/index.vue"
import deepMining from "./components/deepMining/index.vue"
import impactAnalysis from "./components/impactAnalysis/index.vue"
import title from "./assets/title.png"
import icon01 from "./assets/icon01.png"
import icon1 from "../assets/icons/icon1.png";
import icon1Active from "../assets/icons/icon1_active.png";
import icon5 from "../assets/icons/icon5.png";
import icon5Active from "../assets/icons/icon5_active.png";
import icon2 from "../assets/icons/icon2.png";
import icon2Active from "../assets/icons/icon2_active.png";
import icon3 from "../assets/icons/icon3.png";
import icon3Active from "../assets/icons/icon3_active.png";
const headerTitle = ref({
img: title,
title: "实体清单",
titleEn: "Entity List",
department: "美国商务部工业与安全局"
})
const activeIndex = ref(0)
const headerNavList = ref([
{
img: icon1,
imgActive: icon1Active,
title: "制裁概况"
},
{
img: icon5,
imgActive: icon5Active,
title: "数据统计"
},
{
img: icon2,
imgActive: icon2Active,
title: "深度挖掘"
},
{
img: icon3,
imgActive: icon3Active,
title: "影响分析"
}
])
</script>
<style scoped lang="scss">
*{
margin: 0;
padding: 0;
}
.entity-list{
width: 100%;
height: 100%;
.header{
width: 100%;
height: 148px;
background-color: #fff;
padding-top: 16px;
.header-title{
width: 1601px;
height: 72px;
background-color: rgba(246, 250, 255, 1);
margin: 0 auto;
border-radius: 10px;
border: 2px solid rgba(174, 214, 255, 1);
display: flex;
align-items: center;
margin-bottom: 12px;
position: relative;
img {
width: 54px;
height: 54px;
margin-left: 15px;
margin-right: 11px;
}
.title {
font-size: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(59, 65, 75);
span {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
margin-left: 11px;
}
}
.department {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
}
.btn {
cursor: pointer;
display: flex;
align-items: center;
position: absolute;
right: 16px;
top: 25px;
font-size: 18px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(5, 95, 194);
img {
width: 20px;
height: 20px;
margin-right: 7px;
}
}
}
.header-nav {
width: 1601px;
margin: 0 auto;
height: 48px;
display: flex;
align-items: center;
.nav-item {
display: flex;
align-items: center;
height: 100%;
margin-right: 32px;
cursor: pointer;
position: relative;
font-size: 18px;
font-weight: 400;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
&:last-child {
margin-right: 0;
}
img {
width: 16px;
height: 16px;
margin-right: 4px;
}
&.active {
color: rgb(5, 95, 194);
font-weight: 700;
}
.active-line {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 3px;
background-color: #055fc2;
border-radius: 1.5px;
}
}
}
}
.main{
width: 100%;
height: calc(100% - 148px);
background-color: #F7F8F9;
}
}
</style>
<template>
<div class="data-statistics">
<div class="nav">
<div class="nav-item">
<div class="item-position"></div>
<div class="content">
<div class="info">
<div class="title">中国实体数量</div>
</div>
<div class="number">
<span class="num">24</span>
<span class="unit"></span>
</div>
</div>
</div>
<div class="nav-item">
<div class="item-position"></div>
<div class="content">
<div class="info">
<div class="title">中国实体数量</div>
<div class="subtitle">50%规则涉及</div>
</div>
<div class="number">
<span class="num">102</span>
<span class="unit"></span>
</div>
</div>
</div>
<div class="nav-item">
<div class="item-position"></div>
<div class="content">
<div class="info">
<div class="title">涉及领域</div>
</div>
<div class="number">
<span class="num">3</span>
<span class="unit"></span>
</div>
</div>
</div>
</div>
<div class="main">
<div class="main-item">
<div class="title-com">
<div class="box"></div>
<div class="text">制裁实体领域分布情况</div>
<div class="right-group">
<el-select v-model="domainTime" class="time-select" placeholder="请选择">
<el-option v-for="item in timeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<div class="btn">
<img src="../../assets/数据库按钮.png" alt="" />
<img src="../../assets/下载按钮.png" alt="" />
<img src="../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="echarts" ref="domainChartRef"></div>
<div class="bottom">
<div class="ai">
<div class="left">
<img :src="ai" alt="" class="icon1" />
<div class="text">美国对中国的制裁集中在半导体、人工智能等领域。</div>
</div>
<div class="right">
<img :src="right" alt="" class="icon2" />
</div>
</div>
</div>
</div>
<div class="main-item">
<div class="title-com">
<div class="box"></div>
<div class="text">制裁实体类型分布情况</div>
<div class="right-group">
<el-select v-model="typeTime" class="time-select" placeholder="请选择">
<el-option v-for="item in timeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<div class="btn">
<img src="../../assets/数据库按钮.png" alt="" />
<img src="../../assets/下载按钮.png" alt="" />
<img src="../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="echarts" ref="typeChartRef"></div>
<div class="bottom">
<div class="ai">
<div class="left">
<img :src="ai" alt="" class="icon1" />
<div class="text">我国被制裁实体以企业、科研院所和高校为主。</div>
</div>
<div class="right">
<img :src="right" alt="" class="icon2" />
</div>
</div>
</div>
</div>
<div class="main-item">
<div class="title-com">
<div class="box"></div>
<div class="text">制裁实体国家分布</div>
<div class="right-group">
<div class="btn">
<img src="../../assets/数据库按钮.png" alt="" />
<img src="../../assets/下载按钮.png" alt="" />
<img src="../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="country-list">
<div class="list-item" v-for="(item, index) in countryDistribution" :key="index">
<img :src="flag" alt="" class="flag">
<div class="country-name">{{ item.name }}</div>
<div class="progress-bar-container">
<div
class="progress-bar"
:style="{
width: item.width,
background: item.gradient
}"
></div>
</div>
<div class="count" :class="{ 'highlight': index === 0 }">{{ item.count }}</div>
</div>
</div>
<div class="bottom">
<div class="ai">
<div class="left">
<img :src="ai" alt="" class="icon1" />
<div class="text">美国对中国的制裁近年来呈现显著增长趋势。</div>
</div>
<div class="right">
<img :src="right" alt="" class="icon2" />
</div>
</div>
</div>
</div>
<div class="main-item">
<div class="title-com">
<div class="box"></div>
<div class="text">制裁实体地域分布情况</div>
<div class="right-group">
<el-select v-model="regionTime" class="time-select" placeholder="请选择">
<el-option v-for="item in timeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<div class="btn">
<img src="../../assets/数据库按钮.png" alt="" />
<img src="../../assets/下载按钮.png" alt="" />
<img src="../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="map-wrapper">
<div class="map-chart" ref="mapChartRef"></div>
<div class="rank-list">
<div class="rank-item" v-for="(item, index) in rankData" :key="index">
<div class="rank-index" :class="'rank-' + (index + 1)">{{ index + 1 }}</div>
<div class="rank-name">{{ item.name }}</div>
<div class="rank-bar-bg">
<div
class="rank-bar-fill"
:style="{ width: (item.value / 50) * 100 + '%', background: getBarColor(index) }"
></div>
</div>
<div class="rank-value">{{ item.value }}</div>
</div>
</div>
</div>
<div class="bottom">
<div class="ai">
<div class="left">
<img :src="ai" alt="" class="icon1" />
<div class="text">我国被制裁实体多分布于沿海经济活跃省份。</div>
</div>
<div class="right">
<img :src="right" alt="" class="icon2" />
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import * as echarts from "echarts";
import chinaJson from "../../../utils/China.json";
import ai from "./assets/ai.png";
import right from "./assets/right.png";
import flag from "../../assets/default-icon2.png"
const regionTime = ref("all");
const domainTime = ref("all");
const typeTime = ref("all");
const timeOptions = [
{ label: "全部时间", value: "all" },
{ label: "2024年", value: "2024" },
{ label: "2023年", value: "2023" }
];
const countryDistribution = [
{ name: "中国", count: 24, width: "80%", gradient: "linear-gradient(90deg, rgba(205, 66, 70, 0) 0%, rgba(205, 66, 70, 1) 100%)" },
{ name: "沙特阿拉伯", count: 2, width: "60%", gradient: "linear-gradient(90deg, rgba(255, 172, 77, 0) 0%, rgba(255, 172, 77, 1) 100%)" },
{ name: "伊朗", count: 2, width: "60%", gradient: "linear-gradient(90deg, rgba(255, 172, 77, 0) 0%, rgba(255, 172, 77, 1) 100%)" },
{ name: "俄罗斯", count: 2, width: "55%", gradient: "linear-gradient(90deg, rgba(255, 172, 77, 0) 0%, rgba(255, 172, 77, 1) 100%)" },
{ name: "中国香港", count: 1, width: "40%", gradient: "linear-gradient(90deg, rgba(5, 95, 194, 0) 0%, rgba(5, 95, 194, 1) 100%)" }
];
const mapChartRef = ref(null);
const domainChartRef = ref(null);
const typeChartRef = ref(null);
const rankData = [
{ name: "广东省", value: 42 },
{ name: "上海市", value: 35 },
{ name: "浙江省", value: 28 },
{ name: "江苏省", value: 19 },
{ name: "山东省", value: 15 },
{ name: "福建省", value: 14 },
{ name: "中国香港", value: 13 }
];
const getBarColor = index => {
if (index === 0) return "linear-gradient(90deg, rgba(255, 77, 79, 0) 0%, rgba(255, 77, 79, 1) 100%)";
if (index === 1 || index === 2) return "linear-gradient(90deg, rgba(255, 172, 77, 0) 0%, rgba(255, 172, 77, 1) 100%)";
return "linear-gradient(90deg, rgba(5, 95, 194, 0) 0%, rgba(5, 95, 194, 1) 100%)";
};
const geoCoordMap = {
广东省: [113.280637, 23.125178],
上海市: [121.472644, 31.231706],
浙江省: [120.153576, 30.287459],
江苏省: [118.767413, 32.041544],
山东省: [117.000923, 36.675807],
福建省: [119.306239, 26.075302],
中国香港: [114.173355, 22.320048],
北京市: [116.405285, 39.904989],
天津市: [117.190182, 39.125596],
重庆市: [106.504962, 29.533155],
河北省: [114.502461, 38.045474],
山西省: [112.549248, 37.857014],
辽宁省: [123.429096, 41.796767],
吉林省: [125.3245, 43.886841],
黑龙江省: [126.642464, 45.756967],
安徽省: [117.283042, 31.86119],
江西省: [115.892151, 28.676493],
河南省: [113.665412, 34.757975],
湖北省: [114.298572, 30.584355],
湖南省: [112.982279, 28.19409],
海南省: [110.33119, 20.031971],
四川省: [104.065735, 30.659462],
贵州省: [106.713478, 26.578343],
云南省: [102.712251, 25.040609],
陕西省: [108.948024, 34.263161],
甘肃省: [103.823557, 36.058039],
青海省: [101.778916, 36.623178],
内蒙古自治区: [111.670801, 40.818311],
广西壮族自治区: [108.320004, 22.82402],
西藏自治区: [91.132212, 29.660361],
宁夏回族自治区: [106.278179, 38.46637],
新疆维吾尔自治区: [87.617733, 43.792818],
台湾省: [121.509062, 25.044332],
澳门特别行政区: [113.54909, 22.198951]
};
const convertData = data => {
const res = [];
for (let i = 0; i < data.length; i++) {
const geoCoord = geoCoordMap[data[i].name];
if (geoCoord) {
res.push({
name: data[i].name,
value: geoCoord.concat(data[i].value)
});
}
}
return res;
};
const initMapChart = () => {
if (!mapChartRef.value) return;
const chart = echarts.init(mapChartRef.value);
echarts.registerMap("china", chinaJson);
const option = {
tooltip: {
show: false
},
geo: {
map: "china",
roam: true,
center: [105, 36],
zoom: 1.6,
top: "10%",
bottom: "10%",
label: {
normal: {
show: false
}
},
itemStyle: {
areaColor: "#eff6ff",
borderColor: "#8cbaff",
borderWidth: 1
},
emphasis: {
itemStyle: {
areaColor: "#dbeafe"
}
}
},
series: [
{
type: "scatter",
coordinateSystem: "geo",
data: convertData(rankData),
symbolSize: 8,
itemStyle: {
color: "#ff4d4f",
shadowBlur: 10,
shadowColor: "rgba(255, 77, 79, 0.5)"
},
tooltip: {
show: true,
formatter: params => {
return `${params.name} ${params.value[2]}家`;
}
}
}
]
};
chart.setOption(option);
window.addEventListener("resize", () => {
chart.resize();
});
};
const initDomainChart = () => {
if (!domainChartRef.value) return;
const chart = echarts.init(domainChartRef.value);
const data = [
{ value: 215, name: "集成电路" },
{ value: 198, name: "人工智能" },
{ value: 117, name: "通信网络" },
{ value: 98, name: "量子科技" },
{ value: 91, name: "先进制造" },
{ value: 80, name: "新材料" },
{ value: 62, name: "航空航天" }
];
const option = {
tooltip: {
trigger: "item",
formatter: "{b}: {c} ({d}%)"
},
color: [
"#60acfc", // 集成电路 - 蓝色
"#feb64d", // 人工智能 - 橙色
"#5bc49f", // 通信网络 - 青色
"#959cf3", // 量子科技 - 淡蓝紫
"#ff7c7c", // 先进制造 - 红色
"#b689ea", // 新材料 - 紫色
"#32d3eb" // 航空航天 - 浅蓝
],
series: [
{
name: "制裁实体领域分布",
type: "pie",
radius: [73.5, 89.5],
center: ["50%", "50%"],
startAngle: 90,
data: data,
label: {
show: true,
alignTo: "edge",
minMargin: 5,
edgeDistance: 10,
formatter: params => {
return "{name|" + params.name + "}\n{value|" + params.value + "家 " + params.percent + "%}";
},
rich: {
name: {
fontSize: 18,
fontWeight: 700,
color: "rgb(59, 65, 75)",
padding: [0, 0, 5, 0],
fontFamily: "Microsoft YaHei",
lineHeight: 26
},
value: {
fontSize: 16,
fontWeight: 400,
color: "rgb(95, 101, 108)",
fontFamily: "Microsoft YaHei",
lineHeight: 24,
padding: [5, 0, 0, 0]
}
}
},
labelLine: {
show: true,
length: 15,
length2: 0,
maxSurfaceAngle: 80,
lineStyle: {
width: 1.1
}
},
labelLayout: function (params) {
const isLeft = params.labelRect.x < chart.getWidth() / 2;
const points = params.labelLinePoints;
// Update the end point.
points[2][0] = isLeft ? params.labelRect.x : params.labelRect.x + params.labelRect.width;
return {
labelLinePoints: points
};
},
itemStyle: {
borderWidth: 0
}
}
]
};
chart.setOption(option);
window.addEventListener("resize", () => {
chart.resize();
});
};
const initTypeChart = () => {
if (!typeChartRef.value) return;
const chart = echarts.init(typeChartRef.value);
const data = [
{ value: 50, name: "企业" },
{ value: 32, name: "高校" },
{ value: 32, name: "科研院所" }
];
const option = {
tooltip: {
trigger: "item",
formatter: "{b}: {c} ({d}%)"
},
color: [
"#3B82F6", // 企业 - 蓝色
"#feb64d", // 高校 - 橙色
"#ff9f9f" // 科研院所 - 粉红
],
series: [
{
name: "制裁实体类型分布",
type: "pie",
radius: [73.5, 89.5],
center: ["50%", "50%"],
startAngle: -90,
data: data,
label: {
show: true,
alignTo: "edge",
minMargin: 5,
edgeDistance: 10,
formatter: params => {
return "{name|" + params.name + "}\n{value|" + params.value + "家 " + params.percent + "%}";
},
rich: {
name: {
fontSize: 18,
fontWeight: 700,
color: "rgb(59, 65, 75)",
padding: [0, 0, 5, 0],
fontFamily: "Microsoft YaHei",
lineHeight: 26
},
value: {
fontSize: 16,
fontWeight: 400,
color: "rgb(95, 101, 108)",
fontFamily: "Microsoft YaHei",
lineHeight: 24,
padding: [5, 0, 0, 0]
}
}
},
labelLine: {
show: true,
length: 15,
length2: 0,
maxSurfaceAngle: 80,
lineStyle: {
width: 1
}
},
labelLayout: function (params) {
const isLeft = params.labelRect.x < chart.getWidth() / 2;
const points = params.labelLinePoints;
// Update the end point.
points[2][0] = isLeft ? params.labelRect.x : params.labelRect.x + params.labelRect.width;
return {
labelLinePoints: points
};
},
itemStyle: {
borderWidth: 0
}
}
]
};
chart.setOption(option);
window.addEventListener("resize", () => {
chart.resize();
});
};
onMounted(() => {
initMapChart();
initDomainChart();
initTypeChart();
});
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.data-statistics {
width: 1601px;
margin: 0 auto;
padding-top: 16px;
padding-bottom: 50px;
.nav {
width: 100%;
display: flex;
justify-content: space-between;
margin-bottom: 16px;
.nav-item {
width: 521px;
height: 80px;
border-radius: 10px;
background-color: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
position: relative;
.item-position {
position: absolute;
top: 14px;
left: 0;
width: 8px;
height: 52px;
background-color: rgb(5, 95, 194);
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.content {
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 30px;
padding-right: 40px;
box-sizing: border-box;
.info {
display: flex;
flex-direction: column;
justify-content: center;
.title {
font-size: 20px;
font-weight: 700;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
line-height: 26px;
}
.subtitle {
font-size: 16px;
font-weight: 400;
color: rgb(95, 101, 108);
font-family: "Microsoft YaHei";
margin-top: 5px;
line-height: 24px;
}
}
.number {
display: flex;
align-items: baseline;
.num {
font-size: 32px;
font-weight: 700;
color: #cd4246;
font-family: "Microsoft YaHei";
margin-right: 2px;
}
.unit {
font-size: 32px;
font-weight: 700;
color: #cd4246;
font-family: "Microsoft YaHei";
}
}
}
}
}
.main {
width: 100%;
height: 828px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-content: space-between;
.main-item {
width: 792px;
height: 406px;
background-color: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
border-radius: 10px;
.country-list {
width: 100%;
height: 280px;
padding: 0 20px;
margin-bottom: 20px;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;
.list-item {
display: flex;
align-items: center;
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
.flag {
width: 24px;
height: 24px;
border-radius: 50%;
margin-right: 12px;
object-fit: cover;
}
.country-name {
width: 80px;
font-size: 16px;
font-weight: 400;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
margin-right: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.progress-bar-container {
flex: 1;
height: 8px;
margin-right: 16px;
.progress-bar {
height: 100%;
border-radius: 4px;
}
}
.count {
font-size: 16px;
font-weight: 400;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
min-width: 40px;
text-align: right;
&.highlight {
color: #cd4246;
font-weight: 700;
font-size: 20px;
}
}
}
}
.echarts {
width: 100%;
height: 280px;
padding: 0 20px 0 20px;
margin-bottom: 20px;
}
.bottom {
width: 100%;
height: 40px;
padding: 0 19px 0 17px;
box-sizing: border-box;
.ai {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
background-color: rgba(246, 250, 255, 1);
border-radius: 4px;
border: 1px solid rgba(231, 243, 255, 1);
padding: 6px 12px;
.left {
display: flex;
align-items: center;
.icon1 {
width: 19px;
height: 20px;
margin-right: 13px;
}
.text {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(5, 95, 194);
}
}
.right {
display: flex;
align-items: center;
.icon2 {
width: 24px;
height: 24px;
}
}
}
}
}
}
}
.title-com {
width: 100%;
height: 56px;
display: flex;
align-items: center;
padding: 14px 12px 16px 0;
.box {
width: 8px;
height: 20px;
background-color: rgb(5, 95, 194);
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
margin-right: 14px;
}
.text {
font-size: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(5, 95, 194);
}
.right-group {
margin-left: auto;
display: flex;
align-items: center;
.toggle-btns {
display: flex;
gap: 8px;
margin-right: 20px;
.t-btn {
height: 28px;
padding: 1px 8px;
border: 1px solid transparent;
border-radius: 4px;
font-size: 16px;
font-weight: 400;
line-height: 24px;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
cursor: pointer;
box-sizing: border-box;
border: 1px solid rgb(230, 231, 232);
&.active {
color: rgb(5, 95, 194);
background-color: rgba(246, 250, 255, 1);
border-color: rgb(5, 95, 194);
}
}
}
.btn {
margin-left: 0;
}
.time-select {
width: 120px;
height: 28px;
margin-right: 14px;
:deep(.el-input__wrapper) {
height: 28px;
min-height: 28px;
padding: 0 8px;
box-shadow: 0 0 0 1px #dcdfe6 inset;
}
:deep(.el-input__inner) {
height: 28px;
line-height: 28px;
font-size: 14px;
}
}
}
.btn {
width: 92px;
height: 28px;
margin-left: auto;
img {
width: 28px;
height: 28px;
cursor: pointer;
}
img:first-child {
margin-right: 4px;
}
}
}
.map-wrapper {
width: 100%;
height: 280px;
display: flex;
padding: 0 20px 0 20px;
margin-bottom: 20px;
box-sizing: border-box;
.map-chart {
flex: 1;
height: 100%;
}
.rank-list {
flex: 1;
height: 100%;
overflow-y: auto;
padding-left: 20px;
box-sizing: border-box;
.rank-item {
height: 36px;
display: flex;
align-items: center;
margin-bottom: 4px;
font-size: 14px;
color: #333;
cursor: pointer;
&:hover {
background-color: rgba(0, 0, 0, 0.02);
}
.rank-index {
width: 24px;
height: 24px;
line-height: 24px;
text-align: center;
border-radius: 50%;
background-color: #e6f4ff;
color: #1677ff;
margin-right: 16px;
font-size: 12px;
flex-shrink: 0;
&.rank-1,
&.rank-2,
&.rank-3 {
background-color: #e6f4ff;
color: #1677ff;
}
}
.rank-name {
width: 70px;
margin-right: 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex-shrink: 0;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(59, 65, 75);
}
.rank-bar-bg {
flex: 1;
height: 6px;
background-color: transparent;
border-radius: 3px;
margin-right: 10px;
position: relative;
.rank-bar-fill {
height: 100%;
border-radius: 3px;
}
}
.rank-value {
min-width: 40px;
text-align: right;
color: #666;
flex-shrink: 0;
}
}
}
}
</style>
<template>
<div class="deep-mining">
<div class="side-nav">
<div
v-for="(item, index) in activeTab"
:key="index"
class="tab-item"
:class="{ active: index === activeIndex }"
@click="activeIndex = index"
>
{{ item }}
<span v-if="index === activeIndex" class="arrow"></span>
</div>
</div>
<div class="main" v-if="activeIndex === 0">
<div class="left">
<div class="title-com">
<div class="box"></div>
<div class="text">本次制裁实体清单列表</div>
<div class="right-group">
<div class="btn">
<img src="../../assets/数据库按钮.png" alt="" />
<img src="../../assets/下载按钮.png" alt="" />
<img src="../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="left-main">
<div class="filter-bar">
<el-select v-model="searchDomain" placeholder="全部领域" class="domain-select">
<el-option label="全部领域" value="" />
<el-option label="集成电路" value="集成电路" />
<el-option label="人工智能" value="人工智能" />
</el-select>
<el-input v-model="searchText" placeholder="搜索实体" class="search-input">
<template #suffix>
<el-icon class="el-input__icon"><Search /></el-icon>
</template>
</el-input>
</div>
<div class="entity-tree custom-scrollbar">
<div class="tree-group" v-for="group in entityList" :key="group.id">
<div class="group-header" @click="toggleGroup(group)">
<el-icon class="arrow-icon" :class="{ expanded: group.expanded }">
<CaretRight />
</el-icon>
<span class="group-name">{{ group.name }}</span>
<span class="group-count">{{ group.count }}家</span>
</div>
<div class="group-children custom-scrollbar" v-show="group.expanded">
<div
class="entity-item"
v-for="item in group.children"
:key="item.id"
:class="{ active: activeEntityId === item.id }"
@click="selectEntity(item)"
>
<div class="item-icon">
<img :src="defaultTitle" alt="" class="item-img">
</div>
<span class="item-name">{{ item.name }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="right">
<div class="title-com custom-right-header">
<div class="toggle-group">
<div
class="toggle-btn"
:class="{ active: rightActiveTab === 'supplyChain' }"
@click="rightActiveTab = 'supplyChain'"
>
<img :src="rightActiveTab === 'supplyChain' ? icon01Active : icon01" alt="">
<span>供应链</span>
</div>
<div
class="toggle-btn"
:class="{ active: rightActiveTab === 'equity' }"
@click="rightActiveTab = 'equity'"
>
<img :src="rightActiveTab === 'equity' ? icon02Active : icon02" alt="">
<span>股权</span>
</div>
</div>
<div class="right-group">
<div class="rule-checkbox" v-if="rightActiveTab === 'equity'">
<el-checkbox v-model="is50PercentRule" size="large">50%规则涉及实体</el-checkbox>
</div>
<el-select v-model="filterType" placeholder="全部类型" class="header-select">
<el-option label="全部类型" value="" />
</el-select>
<el-select v-model="filterDomain" placeholder="全部领域" class="header-select last-select">
<el-option label="全部领域" value="" />
</el-select>
<div class="btn">
<img src="../../assets/数据库按钮.png" alt="" />
<img src="../../assets/下载按钮.png" alt="" />
<img src="../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="right-echarts">
<div class="chart-wrapper">
<div class="chart-controls">
<div class="control-btn"><img :src="echartsIcon01" /></div>
<div class="control-btn"><img :src="echartsIcon02" /></div>
<div class="control-btn"><img :src="echartsIcon03" /></div>
</div>
<div class="chart-legend">
<div class="legend-item"><span class="dot blue"></span>已被制裁实体</div>
<div class="legend-item"><span class="dot grey"></span>未被制裁实体</div>
</div>
<div ref="chartRef" class="chart-container"></div>
<div class="node-popup" v-if="selectedNode">
<div class="popup-header">
<img :src="defaultTitle" class="popup-icon" />
<span class="popup-title">{{ selectedNode.name.replace(/\n/g, '') }}</span>
<el-icon class="close-icon" @click="selectedNode = null"><Close /></el-icon>
</div>
<div class="popup-body">
<div class="tag-row">
<span class="red-dot"></span>
<span class="red-text">2025年7月15日 《实体清单》</span>
</div>
<div class="desc">
因获取和试图获取美国原产物品以支持中国军事和国防相关空间领域活动以及中国量子技术能力而被列入。
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick, watch, onUnmounted } from "vue";
import * as echarts from "echarts";
import { ArrowLeft, ArrowRight, Search, CaretRight, Close, CircleCheckFilled } from "@element-plus/icons-vue";
import defaultTitle from "../../assets/default-icon2.png";
import icon01 from "./assets/icon01.png"
import icon02 from "./assets/icon02.png"
import icon01Active from "./assets/icon01-active.png"
import icon02Active from "./assets/icon02-active.png"
import echartsIcon01 from "./assets/echartsIcon01.png"
import echartsIcon02 from "./assets/echartsIcon02.png"
import echartsIcon03 from "./assets/echartsIcon03.png"
import company from "./assets/company.png"
import companyActive from "./assets/company-active.png"
const activeTab = ref(["实体穿透分析", "重点实体识别"]);
const activeIndex = ref(0);
const rightActiveTab = ref("supplyChain");
const searchDomain = ref("");
const searchText = ref("");
const filterType = ref("");
const filterDomain = ref("");
const activeEntityId = ref("");
const currentEntityName = ref("");
const is50PercentRule = ref(true);
const entityList = ref([
{
id: "group1",
name: "企业",
count: 13,
expanded: true,
children: [
{ id: "1", name: "北京复旦微电子技术有限公司" },
{ id: "2", name: "上海复旦微电子股份有限公司" },
{ id: "3", name: "深圳复旦微电子有限公司" },
{ id: "4", name: "上海复控华龙微系统技术有限公司" },
{ id: "5", name: "上海富伟迅捷数字技术有限公司" },
{ id: "6", name: "中芯国际集成电路制造有限公司" },
{ id: "7", name: "上海复旦微电子(香港)有限公司" },
{ id: "8", name: "华科供应链(香港)有限公司" },
{ id: "9", name: "华科物流(香港)有限公司" },
{ id: "10", name: "长沙网迅电子科技有限公司" },
{ id: "11", name: "香港 DEMX 有限公司" },
{ id: "12", name: "北京天一辉远生物技术有限公司" },
{ id: "13", name: "上海索辰信息技术有限公司" }
]
},
{
id: "group2",
name: "科研机构",
count: 2,
expanded: false,
children: [
{ id: "14", name: "中国科学院微电子研究所" },
{ id: "15", name: "国家超级计算中心" }
]
}
]);
const toggleGroup = (group) => {
group.expanded = !group.expanded;
};
const selectEntity = (item) => {
activeEntityId.value = item.id;
currentEntityName.value = item.name;
initChart();
};
const formatEntityName = (name) => {
if (!name) return '';
if (name.length > 8) {
const mid = Math.ceil(name.length / 2);
return name.slice(0, mid) + '\n' + name.slice(mid);
}
return name;
};
const chartRef = ref(null);
const chartInstance = ref(null);
const selectedNode = ref(null);
const initChart = () => {
if (!chartRef.value) return;
if (chartInstance.value) {
chartInstance.value.dispose();
}
chartInstance.value = echarts.init(chartRef.value);
let option = {};
if (rightActiveTab.value === 'supplyChain') {
option = getSupplyChainOption();
} else {
option = getEquityOption();
}
chartInstance.value.setOption(option);
chartInstance.value.on('click', (params) => {
if (params.dataType === 'node') {
selectedNode.value = params.data;
} else {
selectedNode.value = null;
}
});
chartInstance.value.getZr().on('click', (params) => {
if (!params.target) {
selectedNode.value = null;
}
});
};
const getSupplyChainOption = () => {
const centerName = formatEntityName(currentEntityName.value);
const nodes = [
{ id: '0', name: centerName, category: 0, symbol: 'image://' + companyActive, x: 550, y: 400, symbolSize: 50, label: { fontSize: 16, fontWeight: 'bold', color: '#055FC2' } },
{ id: '1', name: '上海华虹(集团)\n有限公司', category: 0, symbol: 'image://' + companyActive, x: 100, y: 300 },
{ id: '2', name: '南京芯全信息科技\n有限公司', category: 1, symbol: 'image://' + company, x: 250, y: 300 },
{ id: '3', name: '上海伟测半导体科\n技股份有限公司', category: 0, symbol: 'image://' + companyActive, x: 400, y: 250 },
{ id: '4', name: '上海宏测半导体科\n技有限公司', category: 0, symbol: 'image://' + companyActive, x: 400, y: 100 },
{ id: '5', name: '德耐尔节能科技\n(上海)股份有限\n公司', category: 0, symbol: 'image://' + companyActive, x: 250, y: 100 },
{ id: '6', name: '台湾积体电路制造\n股份有限公司', category: 1, symbol: 'image://' + company, x: 550, y: 250 },
{ id: '7', name: '江苏长电科技股份\n有限公司', category: 0, symbol: 'image://' + companyActive, x: 700, y: 250 },
{ id: '8', name: '杭州士兰微电子股\n份有限公司', category: 0, symbol: 'image://' + companyActive, x: 850, y: 300 },
// Customers
{ id: '9', name: '上海复微迅捷数字\n技术有限公司', category: 0, symbol: 'image://' + companyActive, x: 200, y: 600 },
{ id: '10', name: '上海索辰信息技术\n有限公司', category: 0, symbol: 'image://' + companyActive, x: 400, y: 600 },
{ id: '11', name: '上海复旦微电子\n(香港)有限公司', category: 0, symbol: 'image://' + companyActive, x: 600, y: 600 },
{ id: '12', name: '上海华岭集成电路\n技术股份有限公司', category: 1, symbol: 'image://' + company, x: 800, y: 600 },
{ id: '13', name: '上海复旦通讯股份\n有限公司', category: 1, symbol: 'image://' + company, x: 950, y: 600 },
];
const nodeCategoryMap = nodes.reduce((acc, node) => {
acc[node.id] = node.category;
return acc;
}, {});
const links = [
{ source: '1', target: '0', value: '供应商' },
{ source: '2', target: '0', value: '供应商' },
{ source: '3', target: '0', value: '供应商' },
{ source: '4', target: '3', value: '供应商' },
{ source: '5', target: '3', value: '供应商' },
{ source: '6', target: '0', value: '供应商' },
{ source: '7', target: '0', value: '供应商' },
{ source: '8', target: '0', value: '供应商' },
{ source: '0', target: '9', value: '客户' },
{ source: '0', target: '10', value: '客户' },
{ source: '0', target: '11', value: '客户' },
{ source: '0', target: '12', value: '客户' },
{ source: '0', target: '13', value: '客户' },
];
return {
tooltip: { show: false },
series: [
{
type: 'graph',
layout: 'none',
symbolSize: 36,
roam: true,
label: {
show: true,
position: 'bottom',
formatter: '{b}',
fontSize: 12,
lineHeight: 16
},
edgeSymbol: ['none', 'arrow'],
edgeSymbolSize: [4, 8],
edgeLabel: {
position: 'middle',
offset: [0, 13],
fontSize: 12,
fontWeight: 400,
fontFamily: 'Microsoft YaHei',
lineHeight: 16,
show: true,
formatter: '{c}',
color: 'rgba(170, 173, 177, 1)',
backgroundColor: 'rgba(234, 236, 238, 1)',
padding: [4, 8],
borderRadius: 20
},
data: nodes.map(node => ({
...node,
label: {
color: node.category === 0 ? '#055FC2' : '#5F656C',
...node.label
}
})),
links: links.map(link => {
const isSanctioned = nodeCategoryMap[link.source] === 0 && nodeCategoryMap[link.target] === 0;
return {
...link,
lineStyle: {
color: isSanctioned ? 'rgba(100, 180, 255, 1)' : 'rgb(180, 181, 182)',
width: 1,
curveness: 0
},
label: isSanctioned ? {
show: true,
formatter: '{c}',
backgroundColor: 'rgba(231, 243, 255, 1)',
color: 'rgba(50, 150, 250, 1)',
borderRadius: 20,
padding: [4, 8],
fontSize: 12,
fontWeight: 400,
fontFamily: 'Microsoft YaHei',
lineHeight: 16
} : undefined
};
}),
}
]
};
};
const getEquityOption = () => {
const centerName = formatEntityName(currentEntityName.value);
const nodes = [
{ id: '0', name: centerName, category: 0, symbol: 'image://' + companyActive, x: 550, y: 350, symbolSize: 50, label: { fontSize: 16, fontWeight: 'bold', color: '#055FC2' } },
// Shareholders (Top)
{ id: '1', name: '上海复芯凡高集成\n电路技术有限公司', category: 0, symbol: 'image://' + companyActive, x: 150, y: 50 },
{ id: '2', name: '中信证券股份有限\n公司-嘉实上证科创\n板芯片交易型开...', category: 1, symbol: 'image://' + company, x: 350, y: 50 },
{ id: '3', name: '上海复旦复控科技\n产业控股有限公司', category: 0, symbol: 'image://' + companyActive, x: 550, y: 50 },
{ id: '4', name: '香港中央结算(代\n理人)有限公司', category: 1, symbol: 'image://' + company, x: 750, y: 50 },
{ id: '5', name: '中国农业银行股份\n有限公司-南方军工\n改革灵活配置混...', category: 1, symbol: 'image://' + company, x: 950, y: 50 },
// Investments (Bottom)
{ id: '6', name: '上海复龙鸿芯微系\n统技术有限公司', category: 0, symbol: 'image://' + companyActive, x: 50, y: 650 },
{ id: '7', name: '上海复微迅捷数字\n技术有限公司', category: 0, symbol: 'image://' + companyActive, x: 200, y: 650 },
{ id: '8', name: '上海索辰信息技术\n有限公司', category: 0, symbol: 'image://' + companyActive, x: 350, y: 650 },
{ id: '9', name: '上海复旦微电子\n(香港)有限公司', category: 0, symbol: 'image://' + companyActive, x: 500, y: 650 },
{ id: '10', name: '北京复旦微电子技\n术有限公司', category: 0, symbol: 'image://' + companyActive, x: 650, y: 650 },
{ id: '11', name: '深圳复旦微电子有\n限公司', category: 0, symbol: 'image://' + companyActive, x: 800, y: 650 },
{ id: '12', name: '上海华岭集成电路\n技术股份有限公司', category: 1, symbol: 'image://' + company, x: 950, y: 650 },
{ id: '13', name: '上海复旦通讯股份\n有限公司', category: 1, symbol: 'image://' + company, x: 1100, y: 650 },
];
const links = [
{ source: '1', target: '0', value: '持股51%', isSanctioned: true },
{ source: '2', target: '0', value: '持股12%', isSanctioned: false },
{ source: '3', target: '0', value: '持股19%', isSanctioned: true },
{ source: '4', target: '0', value: '持股12%', isSanctioned: false },
{ source: '5', target: '0', value: '持股12%', isSanctioned: false },
{ source: '0', target: '6', value: '持股85%', isSanctioned: true },
{ source: '0', target: '7', value: '持股60%', isSanctioned: true },
{ source: '0', target: '8', value: '持股54%', isSanctioned: true },
{ source: '0', target: '9', value: '持股51%', isSanctioned: true },
{ source: '0', target: '10', value: '持股51%', isSanctioned: true },
{ source: '0', target: '11', value: '持股51%', isSanctioned: true },
{ source: '0', target: '12', value: '持股15%', isSanctioned: false },
{ source: '0', target: '13', value: '持股12%', isSanctioned: false },
];
return {
tooltip: { show: false },
series: [
{
type: 'graph',
layout: 'none',
symbolSize: 36,
roam: true,
label: {
show: true,
position: 'bottom',
formatter: '{b}',
fontSize: 12,
lineHeight: 16
},
edgeSymbol: ['none', 'arrow'],
edgeSymbolSize: [4, 8],
edgeLabel: {
position: 'middle',
offset: [0, 13],
fontSize: 12,
fontWeight: 400,
fontFamily: 'Microsoft YaHei',
lineHeight: 16,
show: true,
formatter: '{c}',
color: 'rgba(170, 173, 177, 1)',
backgroundColor: 'rgba(234, 236, 238, 1)',
padding: [4, 8],
borderRadius: 20
},
data: nodes.map(node => ({
...node,
label: {
color: node.category === 0 ? '#055FC2' : '#5F656C',
...node.label
}
})),
links: links.map(link => {
return {
...link,
lineStyle: {
color: link.isSanctioned ? 'rgba(100, 180, 255, 1)' : 'rgb(180, 181, 182)',
width: 1,
curveness: 0
},
label: link.isSanctioned ? {
show: true,
formatter: '{c}',
backgroundColor: 'rgba(231, 243, 255, 1)',
color: 'rgba(50, 150, 250, 1)',
borderRadius: 20,
padding: [4, 8],
fontSize: 12,
fontWeight: 400,
fontFamily: 'Microsoft YaHei',
lineHeight: 16
} : undefined
};
}),
}
]
};
};
watch(rightActiveTab, (val) => {
nextTick(() => {
initChart();
});
}, { immediate: true });
onMounted(() => {
// 默认选中第一个实体
if (entityList.value.length > 0 && entityList.value[0].children && entityList.value[0].children.length > 0) {
const firstEntity = entityList.value[0].children[0];
activeEntityId.value = firstEntity.id;
currentEntityName.value = firstEntity.name;
}
nextTick(() => {
initChart();
});
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
if (chartInstance.value) {
chartInstance.value.dispose();
}
window.removeEventListener('resize', handleResize);
});
const handleResize = () => {
if (chartInstance.value) {
chartInstance.value.resize();
}
};
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.deep-mining {
width: 1601px;
margin: 0 auto;
position: relative;
// min-height: 800px;
.side-nav {
position: absolute;
top: 27px;
right: 100%;
margin-right: 12px;
display: flex;
flex-direction: column;
gap: 16px;
.tab-item {
cursor: pointer;
padding: 4px 20px;
border-radius: 22px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
white-space: nowrap;
display: flex;
align-items: center;
&.active {
background-color: rgb(5, 95, 194);
color: #fff;
.arrow {
display: inline-block;
width: 0;
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-left: 6px solid #fff;
margin-left: 8px;
}
}
}
}
.main {
width: 100%;
padding-top: 16px;
padding-bottom: 50px;
display: flex;
justify-content: space-between;
.left {
width: 480px;
height: 828px;
background-color: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
border-radius: 10px;
.left-main {
padding: 0 17px 14px 17px;
height: calc(100% - 56px);
display: flex;
flex-direction: column;
box-sizing: border-box;
.filter-bar {
display: flex;
gap: 12px;
margin-bottom: 16px;
.domain-select {
width: 140px;
}
.search-input {
flex: 1;
}
}
.entity-tree {
flex: 1;
overflow-y: auto;
padding-right: 4px;
.tree-group {
// margin-bottom: 16px;
border: 1px solid rgb(234, 236, 238);
border-radius: 4px;
overflow: hidden;
.group-header {
height: 48px;
padding: 0 12px;
display: flex;
align-items: center;
cursor: pointer;
user-select: none;
background-color: #fff;
border-bottom: 1px solid rgb(234, 236, 238);
&:hover {
background-color: #F2F3F5;
}
.arrow-icon {
margin-right: 8px;
font-size: 14px;
color: #86909C;
transition: transform 0.3s;
&.expanded {
transform: rotate(90deg);
}
}
.group-name {
flex: 1;
font-size: 14px;
font-weight: 700;
color: #1D2129;
font-family: "Microsoft YaHei";
}
.group-count {
font-size: 14px;
color: #4E5969;
}
}
.group-children {
.entity-item {
display: flex;
align-items: center;
height: 48px;
padding: 0 12px 0 29px;
cursor: pointer;
margin-bottom: 0;
transition: all 0.2s;
box-sizing: border-box;
&:hover {
background-color: #F7F8FA;
}
&.active {
background-color: rgba(5, 95, 194, 0.05);
border-top: 1px solid rgba(174, 214, 255, 1);
border-bottom: 1px solid rgba(174, 214, 255, 1);
.item-name {
color: rgb(5, 95, 194);
font-weight: 700;
}
}
.item-icon {
margin-right: 10px;
display: flex;
align-items: center;
flex-shrink: 0;
.item-img {
width: 24px;
height: 24px;
object-fit: contain;
}
}
.item-name {
font-size: 16px;
color: rgb(59, 65, 75);
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
word-break: break-all;
}
}
}
}
}
}
}
.right {
width: 1105px;
height: 828px;
background-color: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
border-radius: 10px;
.right-echarts {
width: 100%;
height: calc(100% - 56px);
position: relative;
.chart-wrapper {
width: 100%;
height: 100%;
position: relative;
}
.chart-container {
width: 100%;
height: 100%;
}
.chart-controls {
position: absolute;
top: 0px;
left: 16px;
display: flex;
align-items: center;
justify-content: space-between;
z-index: 10;
width: 96px;
height: 32px;
padding: 8px 10px;
background: #fff;
border-radius: 4px;
box-shadow: 0 0px 8px rgba(0, 0, 0, 0.1);
.control-btn {
width: 14px;
height: 14px;
cursor: pointer;
img {
width: 14px;
height: 14px;
}
}
}
.chart-legend {
position: absolute;
top: 4px;
right: 23px;
display: flex;
gap: 20px;
z-index: 10;
.legend-item {
display: flex;
align-items: center;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: #5F656C;
&:first-child {
color: #055FC2;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
&.blue {
background: #055FC2;
}
&.grey {
background: rgb(95, 101, 108);
}
}
}
}
.node-popup {
position: absolute;
right: 20px;
top: 200px;
width: 320px;
background: #fff;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
padding: 16px;
z-index: 20;
.popup-header {
display: flex;
align-items: center;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid rgb(234, 236, 238);
position: relative;
.popup-icon {
width: 24px;
height: 24px;
margin-right: 8px;
}
.popup-title {
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
padding-right: 20px;
}
.close-icon {
cursor: pointer;
font-size: 16px;
color: #86909C;
position: absolute;
right: 0;
top: 0;
}
}
.popup-body {
.tag-row {
display: flex;
align-items: center;
margin-bottom: 8px;
.red-dot {
width: 8px;
height: 8px;
background: rgb(206, 79, 81);
border-radius: 50%;
margin-right: 23px;
}
.red-text {
color: rgb(206, 79, 81);
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
}
}
.desc {
padding-left: 31px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
color: rgb(95, 101, 108);
line-height: 24px;
text-align: justify;
}
}
}
}
}
}
}
.title-com {
width: 100%;
height: 56px;
display: flex;
align-items: center;
padding: 14px 12px 16px 0;
.box {
width: 8px;
height: 20px;
background-color: rgb(5, 95, 194);
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
margin-right: 14px;
}
.text {
font-size: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(5, 95, 194);
}
.right-group {
margin-left: auto;
display: flex;
align-items: center;
.rule-checkbox {
margin-right: 20px;
:deep(.el-checkbox__label) {
font-size: 16px;
color: #5F656C;
font-family: "Microsoft YaHei";
}
:deep(.el-checkbox__inner) {
border-color: #C9CDD4;
}
:deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
background-color: #055FC2;
border-color: #055FC2;
}
:deep(.el-checkbox__input.is-checked + .el-checkbox__label) {
color: #5F656C;
}
}
.toggle-btns {
display: flex;
gap: 8px;
margin-right: 20px;
.t-btn {
height: 28px;
padding: 1px 8px;
border: 1px solid transparent;
border-radius: 4px;
font-size: 16px;
font-weight: 400;
line-height: 24px;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
cursor: pointer;
box-sizing: border-box;
border: 1px solid rgb(230, 231, 232);
&.active {
color: rgb(5, 95, 194);
background-color: rgba(246, 250, 255, 1);
border-color: rgb(5, 95, 194);
}
}
}
.btn {
margin-left: 0;
}
.time-select {
width: 120px;
height: 28px;
margin-right: 14px;
:deep(.el-input__wrapper) {
height: 28px;
min-height: 28px;
padding: 0 8px;
box-shadow: 0 0 0 1px #dcdfe6 inset;
}
:deep(.el-input__inner) {
height: 28px;
line-height: 28px;
font-size: 14px;
}
}
}
.btn {
width: 92px;
height: 28px;
margin-left: auto;
img {
width: 28px;
height: 28px;
cursor: pointer;
}
img:first-child {
margin-right: 4px;
}
}
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #DCDFE6;
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: transparent;
}
.title-com.custom-right-header {
.box, .text {
display: none;
}
.toggle-group {
display: flex;
background: #fff;
border: 1px solid #055FC2;
border-radius: 20px;
height: 32px;
overflow: hidden;
margin-left: 16px;
.toggle-btn {
height: 100%;
padding: 4px 16px;
display: flex;
align-items: center;
cursor: pointer;
transition: all 0.3s;
font-size: 18px;
color: rgb(5, 95, 194);
background: #fff;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
img {
width: 14px;
height: 14px;
margin-right: 8px;
}
&.active {
background: rgb(5, 95, 194);
color: #fff;
}
}
}
.right-group {
.header-select {
width: 120px;
height: 32px;
margin-right: 12px;
&.last-select {
margin-right: 19px;
}
:deep(.el-input__wrapper) {
height: 32px;
min-height: 32px;
box-shadow: 0 0 0 1px #dcdfe6 inset;
padding: 0 11px;
}
:deep(.el-input__inner) {
height: 32px;
line-height: 32px;
}
}
}
}
</style>
<template>
<div class="industrial-impact">
<div class="main">
<div class="left">
<div class="title-com">
<div class="box"></div>
<div class="text">制裁企业列表</div>
<div class="right-group">
<div class="btn">
<img src="../../../../assets/数据库按钮.png" alt="" />
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="left-main">
<div class="top-bar">
<el-select v-model="selectedDomain" class="domain-select" placeholder="Select">
<el-option label="全部领域" value="全部领域" />
</el-select>
<el-input v-model="searchKeyword" class="search-input" placeholder="搜索实体" :suffix-icon="Search" />
</div>
<div class="company-list-container">
<div class="list-header">企业名称</div>
<div class="company-list">
<div
class="company-item"
:class="{ active: selectedCompanyId === item.id }"
v-for="item in companyList"
:key="item.id"
@click="selectedCompanyId = item.id"
>
<div class="icon-wrapper">
<img :src="defaultTitle" alt="" />
</div>
<div class="company-name">{{ item.name }}</div>
</div>
</div>
</div>
</div>
</div>
<div class="right">
<div class="right-item">
<div class="title-com">
<div class="box"></div>
<div class="text">企业规模</div>
<div class="right-group">
<div class="toggle-btns">
<div
class="t-btn"
:class="{ active: activeScale === item }"
v-for="item in scaleOptions"
:key="item"
@click="activeScale = item"
>
{{ item }}
</div>
</div>
<div class="btn">
<img src="../../../../assets/数据库按钮.png" alt="" />
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="right-main">
<div class="echarts" ref="chartRef"></div>
<div class="bottom">
<img :src="ai" class="ai-icon" alt="" />
<span class="text">列入实体清单后企业营收初期下降,后基本趋于稳定。</span>
<img :src="right" class="right-icon" alt="" />
</div>
</div>
</div>
<div class="right-item">
<div class="title-com">
<div class="box"></div>
<div class="text">企业市值变化</div>
<div class="right-group">
<div class="btn">
<img src="../../../../assets/数据库按钮.png" alt="" />
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="right-main">
<div class="echarts" ref="marketChartRef"></div>
<div class="bottom">
<img :src="ai" class="ai-icon" alt="" />
<span class="text">列入实体清单后企业营收初期下降,后基本趋于稳定。</span>
<img :src="right" class="right-icon" alt="" />
</div>
</div>
</div>
<div class="right-item">
<div class="title-com">
<div class="box"></div>
<div class="text">企业研发投入</div>
<div class="right-group">
<div class="toggle-btns">
<div
class="t-btn"
:class="{ active: activeRD === item }"
v-for="item in rdOptions"
:key="item"
@click="activeRD = item"
>
{{ item }}
</div>
</div>
<div class="btn">
<img src="../../../../assets/数据库按钮.png" alt="" />
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="right-main">
<div class="echarts" ref="rdChartRef"></div>
<div class="bottom">
<img :src="ai" class="ai-icon" alt="" />
<span class="text">列入实体清单后企业研发资金投入逐渐提高。</span>
<img :src="right" class="right-icon" alt="" />
</div>
</div>
</div>
<div class="right-item">
<div class="title-com">
<div class="box"></div>
<div class="text">企业市场占比</div>
<div class="right-group">
<div class="toggle-btns">
<div
class="t-btn"
:class="{ active: activeMarketShare === item }"
v-for="item in marketShareOptions"
:key="item"
@click="activeMarketShare = item"
>
{{ item }}
</div>
</div>
<div class="btn">
<img src="../../../../assets/数据库按钮.png" alt="" />
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="right-main">
<div class="echarts" ref="shareChartRef"></div>
<div class="bottom">
<img :src="ai" class="ai-icon" alt="" />
<span class="text">列入实体清单后企业营收初期下降,后基本趋于稳定。</span>
<img :src="right" class="right-icon" alt="" />
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick, computed } from "vue";
import * as echarts from "echarts";
import { Search } from "@element-plus/icons-vue";
import defaultTitle from "../../../../assets/default-icon2.png";
import ai from "../../assets/ai.png";
import right from "../../assets/right.png";
const activeScale = ref("营收");
const scaleOptions = ["营收", "净利润", "人员"];
const activeRD = ref("研发经费");
const rdOptions = ["研发经费"];
const activeMarketShare = ref("总体市场份额");
const marketShareOptions = ["总体市场份额"];
const selectedDomain = ref("全部领域");
const searchKeyword = ref("");
const selectedCompanyId = ref(1);
const companyList = ref([
{ id: 1, name: "比亚迪股份有限公司" },
{ id: 2, name: "宁德时代新能源科技股份有限公司" },
{ id: 3, name: "隆基绿能科技股份有限公司" },
{ id: 4, name: "晶科能源控股有限公司" },
{ id: 5, name: "厦门海辰储能科技股份有限公司" },
{ id: 6, name: "国轩高科股份有限公司" },
{ id: 7, name: "远景科技集团" },
{ id: 8, name: "惠州亿纬锂能股份有限公司" },
{ id: 9, name: "天合光能股份有限公司" },
{ id: 10, name: "晶澳太阳能科技股份有限公司" }
]);
const chartData = ref({
dates: [
"2023\nQ3",
"2023\nQ4",
"2024\nQ1",
"2024\nQ2",
"2024\nQ3",
"2024\nQ4",
"2025\nQ1",
"2025\nQ2",
"2025\nQ3",
"2025\nQ4"
],
values: [620, 650, 600, 480, 480, 420, 480, 520, 650, 650],
sanctionDate: "2024\nQ1",
endValue: 645
});
const marketChartData = ref({
dates: [
"2023\nQ3",
"2023\nQ4",
"2024\nQ1",
"2024\nQ2",
"2024\nQ3",
"2024\nQ4",
"2025\nQ1",
"2025\nQ2",
"2025\nQ3",
"2025\nQ4"
],
values: [620, 650, 600, 480, 480, 420, 480, 520, 650, 650],
sanctionDate: "2024\nQ1",
endValue: 645
});
const rdChartData = ref({
dates: [
"2023\nQ3",
"2023\nQ4",
"2024\nQ1",
"2024\nQ2",
"2024\nQ3",
"2024\nQ4",
"2025\nQ1",
"2025\nQ2",
"2025\nQ3",
"2025\nQ4"
],
values: [62, 65, 60, 55, 62, 68, 83, 92, 89, 92],
sanctionDate: "2024\nQ1",
endValue: 92,
maxY: 100
});
const shareChartData = ref({
dates: [
"2023\nQ3",
"2023\nQ4",
"2024\nQ1",
"2024\nQ2",
"2024\nQ3",
"2024\nQ4",
"2025\nQ1",
"2025\nQ2",
"2025\nQ3",
"2025\nQ4"
],
values: [65, 70, 72, 70, 58, 65, 68, 72, 72, 68],
sanctionDate: "2024\nQ1",
type: "bar",
unit: "%",
maxY: 100
});
const chartRef = ref(null);
const marketChartRef = ref(null);
const rdChartRef = ref(null);
const shareChartRef = ref(null);
const initChart = (domRef, data) => {
if (!domRef) return;
const myChart = echarts.init(domRef);
const lastDate = data.dates[data.dates.length - 1];
const lastSeriesValue = data.values[data.values.length - 1];
const maxY = data.maxY || 1000;
const chartType = data.type || "line";
const unit = data.unit || " 亿元";
const option = {
tooltip: {
trigger: "axis",
valueFormatter: value => value + unit
},
grid: {
top: "15%",
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true
},
xAxis: {
type: "category",
boundaryGap: chartType === "bar",
data: data.dates,
axisLine: {
lineStyle: {
color: "#E6EBF5"
}
},
axisLabel: {
color: "#606266",
fontSize: 12,
lineHeight: 18
},
axisTick: {
show: false
}
},
yAxis: {
type: "value",
min: 0,
max: maxY,
interval: maxY / 5,
name: chartType === "bar" ? "百分比" : "",
nameTextStyle: {
align: "right",
padding: [0, 8, 0, 0],
color: "#606266"
},
axisLabel: {
color: "#606266",
fontSize: 12
},
splitLine: {
lineStyle: {
type: "dashed",
color: "rgba(231, 243, 255, 1)"
}
}
},
series: [
{
data: data.values,
type: chartType,
symbol: "none",
smooth: false,
barWidth: 16,
itemStyle:
chartType === "bar"
? {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#055FC2" },
{ offset: 1, color: "rgba(5, 95, 194, 0)" }
]),
borderRadius: [8, 8, 0, 0]
}
: undefined,
lineStyle:
chartType === "line"
? {
color: "#055FC2",
width: 2
}
: undefined,
areaStyle:
chartType === "line"
? {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: "rgba(5, 95, 194, 0.2)"
},
{
offset: 1,
color: "rgba(5, 95, 194, 0)"
}
])
}
: undefined,
markLine: {
symbol: "none",
data: [
{
xAxis: data.sanctionDate,
label: {
formatter: "列入实体清单",
position: "end",
rotate: 0,
color: "#F56C6C",
backgroundColor: "rgba(255, 238, 238, 1)",
borderRadius: 4,
padding: [4, 8],
offset: [0, 0]
},
lineStyle: {
color: "#F56C6C",
type: "dotted",
width: 1
}
},
chartType === "line"
? [
{
coord: [lastDate, lastSeriesValue],
symbol: "none"
},
{
coord: [lastDate, maxY],
symbol: "none",
lineStyle: {
color: "#055FC2",
type: "dotted",
width: 1
}
}
]
: null
].filter(Boolean)
},
markPoint:
chartType === "line"
? {
symbol: "circle",
symbolSize: 6,
itemStyle: {
color: "#055FC2"
},
label: {
show: true,
position: "left",
formatter: "{c}" + unit.trim(),
color: "#055FC2",
fontSize: 16,
fontWeight: "bold",
offset: [-5, 0]
},
data: [
{
coord: [lastDate, maxY],
value: data.endValue || lastSeriesValue
}
]
}
: undefined
}
]
};
myChart.setOption(option);
window.addEventListener("resize", () => {
myChart.resize();
});
};
onMounted(() => {
nextTick(() => {
initChart(chartRef.value, chartData.value);
initChart(marketChartRef.value, marketChartData.value);
initChart(rdChartRef.value, rdChartData.value);
initChart(shareChartRef.value, shareChartData.value);
});
});
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.industrial-impact {
width: 100%;
padding-top: 16px;
padding-bottom: 50px;
.main {
width: 100%;
display: flex;
justify-content: space-between;
.left {
width: 480px;
height: 848px;
background-color: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
border-radius: 10px;
.left-main {
padding: 0 16px;
.top-bar {
display: flex;
gap: 12px;
margin-bottom: 8px;
.domain-select {
width: 150px;
:deep(.el-input__wrapper) {
height: 32px;
border-radius: 4px;
}
}
.search-input {
width: 288px;
:deep(.el-input__wrapper) {
height: 32px;
border-radius: 4px;
}
}
}
.company-list-container {
.list-header {
width: 446px;
height: 48px;
padding: 12px 16px;
font-size: 16px;
font-weight: 700;
color: rgb(59, 65, 75);
border-top: 1px solid rgb(234, 236, 238);
border-bottom: 1px solid rgb(234, 236, 238);
}
.company-list {
height: 700px;
overflow-y: auto;
.company-item {
display: flex;
align-items: center;
height: 48px;
padding: 0 16px;
cursor: pointer;
border-bottom: 1px solid #f0f2f5;
box-sizing: border-box;
&:hover {
background-color: #f5f7fa;
}
&.active {
background-color: rgba(246, 250, 255, 1);
border: 1px solid rgba(174, 214, 255, 1);
.company-name {
color: rgb(5, 95, 194);
font-weight: 700;
}
.icon-wrapper {
background-color: transparent;
}
}
.icon-wrapper {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
border-radius: 50%;
img {
width: 100%;
height: 100%;
}
}
.company-name {
font-size: 16px;
color: rgb(59, 65, 75);
font-weight: 400;
line-height: 24px;
font-family: "Microsoft YaHei";
}
}
}
}
}
}
.right {
width: 1104px;
height: 848px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-content: space-between;
.right-item {
width: 544px;
height: 416px;
background-color: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
border-radius: 10px;
}
.right-main {
width: 100%;
height: calc(100% - 56px);
padding: 0px 14px 15px 14px;
.echarts {
width: 516px;
height: 300px;
margin-bottom: 5px;
}
.bottom {
width: 516px;
// height: 40px;
border-radius: 4px;
background-color: rgba(246, 250, 255, 1);
border: 1px solid rgba(231, 243, 255, 1);
display: flex;
align-items: center;
padding: 6px 12px;
box-sizing: border-box;
.ai-icon {
width: 20px;
height: 20px;
margin-right: 12px;
}
.text {
flex: 1;
font-size: 16px;
font-family: "Microsoft YaHei";
font-weight: 400;
color: rgb(5, 95, 194);
line-height: 24px;
}
.right-icon {
width: 24px;
height: 24px;
cursor: pointer;
}
}
}
}
}
}
.title-com {
width: 100%;
height: 56px;
display: flex;
align-items: center;
padding: 14px 12px 16px 0;
.box {
width: 8px;
height: 20px;
background-color: rgb(5, 95, 194);
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
margin-right: 14px;
}
.text {
font-size: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(5, 95, 194);
}
.right-group {
margin-left: auto;
display: flex;
align-items: center;
.rule-checkbox {
margin-right: 20px;
:deep(.el-checkbox__label) {
font-size: 16px;
color: #5f656c;
font-family: "Microsoft YaHei";
}
:deep(.el-checkbox__inner) {
border-color: #c9cdd4;
}
:deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
background-color: #055fc2;
border-color: #055fc2;
}
:deep(.el-checkbox__input.is-checked + .el-checkbox__label) {
color: #5f656c;
}
}
.toggle-btns {
display: flex;
gap: 8px;
margin-right: 20px;
.t-btn {
padding: 2px 8px;
border: 1px solid rgb(230, 231, 232);
border-radius: 4px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
cursor: pointer;
box-sizing: border-box;
&.active {
color: rgb(5, 95, 194);
background-color: rgba(231, 243, 255, 1);
border-color: rgb(5, 95, 194);
}
}
}
.btn {
margin-left: 0;
}
.time-select {
width: 120px;
height: 28px;
margin-right: 14px;
:deep(.el-input__wrapper) {
height: 28px;
min-height: 28px;
padding: 0 8px;
box-shadow: 0 0 0 1px #dcdfe6 inset;
}
:deep(.el-input__inner) {
height: 28px;
line-height: 28px;
font-size: 14px;
}
}
}
.btn {
width: 92px;
height: 28px;
margin-left: auto;
img {
width: 28px;
height: 28px;
cursor: pointer;
}
img:first-child {
margin-right: 4px;
}
}
}
</style>
<template>
<div class="industrial-impact">
<div class="main">
<div class="left">
<div class="title-com">
<div class="box"></div>
<div class="text">制裁科研机构列表</div>
<div class="right-group">
<el-select
v-model="activeDependency"
class="dependency-select"
placeholder="Select"
>
<el-option
v-for="item in dependencyOptions"
:key="item"
:label="item"
:value="item"
/>
</el-select>
<div class="btn-com">
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="left-main">
<div class="company-list-container">
<div class="list-header">科研机构名称</div>
<div class="company-list">
<div
class="company-item"
:class="{ active: selectedCompanyId === item.id }"
v-for="item in companyList"
:key="item.id"
@click="selectedCompanyId = item.id"
>
<div class="icon-wrapper">
<img :src="defaultTitle" alt="" />
</div>
<div class="company-name">{{ item.name }}</div>
</div>
</div>
</div>
</div>
</div>
<div class="right">
<div class="right-item">
<div class="title-com">
<div class="box"></div>
<div class="text">科研仪器对美依赖情况</div>
<div class="right-group">
<div class="btn">
<img src="../../../../assets/数据库按钮.png" alt="" />
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="right-main">
<div class="echarts" ref="chartRef"></div>
<div class="bottom">
<img :src="ai" class="ai-icon" alt="" />
<span class="text">受制裁实体中,电子测量仪器对美依赖程度最高,此外,电子策略仪器、物理性能测试仪器等也有较高的对美依赖度。</span>
<img :src="right" class="right-icon" alt="" />
</div>
</div>
</div>
<div class="right-item">
<div class="title-com">
<div class="box"></div>
<div class="text">科研仪器进口国分布</div>
<div class="right-group">
<el-select
v-model="activeInstrument"
class="instrument-select"
placeholder="Select"
>
<el-option
v-for="item in instrumentOptions"
:key="item"
:label="item"
:value="item"
/>
</el-select>
<div class="btn">
<img src="../../../../assets/数据库按钮.png" alt="" />
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="right-main">
<div class="echarts" ref="marketChartRef"></div>
<div class="bottom">
<img :src="ai" class="ai-icon" alt="" />
<span class="text">电子测量仪器来源国主要为美国,但是德国、日本等国也占有较大比重,后续可考虑将上述国家作为仪器进口来源替代国。</span>
<img :src="right" class="right-icon" alt="" />
</div>
</div>
</div>
<div class="right-item">
<div class="title-com">
<div class="box"></div>
<div class="text">新增国际合作项目</div>
<div class="right-group">
<el-select
v-model="activeProjectDomain"
class="project-domain-select"
placeholder="Select"
>
<el-option
v-for="item in projectDomainOptions"
:key="item"
:label="item"
:value="item"
/>
</el-select>
<div class="btn">
<img src="../../../../assets/数据库按钮.png" alt="" />
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="right-main">
<div class="echarts" ref="rdChartRef"></div>
<div class="bottom">
<img :src="ai" class="ai-icon" alt="" />
<span class="text">列入实体清单后新增国际项目数量明显减少。</span>
<img :src="right" class="right-icon" alt="" />
</div>
</div>
</div>
<div class="right-item">
<div class="title-com">
<div class="box"></div>
<div class="text">新增国际合著论文</div>
<div class="right-group">
<el-select
v-model="activePaperDomain"
class="paper-domain-select"
placeholder="Select"
>
<el-option
v-for="item in paperDomainOptions"
:key="item"
:label="item"
:value="item"
/>
</el-select>
<div class="btn">
<img src="../../../../assets/数据库按钮.png" alt="" />
<img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" />
</div>
</div>
</div>
<div class="right-main">
<div class="echarts" ref="shareChartRef"></div>
<div class="bottom">
<img :src="ai" class="ai-icon" alt="" />
<span class="text">总量下降,特别是在人工智能、高端通信等敏感技术领域。</span>
<img :src="right" class="right-icon" alt="" />
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from "vue";
import * as echarts from "echarts";
import defaultTitle from "../../../../assets/default-icon2.png";
import ai from "../../assets/ai.png";
import right from "../../assets/right.png";
const chartRef = ref(null);
const marketChartRef = ref(null);
const rdChartRef = ref(null);
const shareChartRef = ref(null);
const initChart = () => {
if (!chartRef.value) return;
const myChart = echarts.init(chartRef.value);
const data = [
{ name: "电子测量仪器", value: 109 },
{ name: "物理性能测试仪器", value: 95 },
{ name: "激光器", value: 79 },
{ name: "分析仪器", value: 25 },
{ name: "计量仪器", value: 21 },
{ name: "计算机及其配套设备", value: 10 },
{ name: "地球探测仪器", value: 10 }
];
const option = {
grid: {
top: "5%",
left: "0%",
right: "5%",
bottom: "0%",
containLabel: true
},
xAxis: {
show: false,
type: "value"
},
yAxis: [
{
type: "category",
data: data.map((item) => item.name),
axisLine: { show: false },
axisTick: { show: false },
axisLabel: {
color: "rgb(59, 65, 75)",
fontSize: 16,
fontWeight: 400,
fontFamily: "Microsoft YaHei",
margin: 20,
interval: 0
},
inverse: true
},
{
type: "category",
data: data.map((item) => item.value),
axisLine: { show: false },
axisTick: { show: false },
axisLabel: {
margin: 20,
interval: 0,
fontSize: 16,
fontFamily: "Microsoft YaHei",
color: (value, index) => {
return index < 3
? "rgba(209, 90, 90, 1)"
: "rgba(59, 139, 245, 1)";
}
},
inverse: true
}
],
series: [
{
type: "bar",
data: data.map((item, index) => {
return {
value: item.value,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{
offset: 0,
color:
index < 3
? "rgba(209, 90, 90, 0.05)"
: "rgba(59, 139, 245, 0.05)"
},
{
offset: 1,
color:
index < 3
? "rgba(209, 90, 90, 1)"
: "rgba(59, 139, 245, 1)"
}
]),
borderRadius: [0, 10, 10, 0]
},
label: {
show: false
}
};
}),
barWidth: 10,
showBackground: false
}
]
};
myChart.setOption(option);
window.addEventListener("resize", () => {
myChart.resize();
});
};
const initMarketChart = () => {
if (!marketChartRef.value) return;
const myChart = echarts.init(marketChartRef.value);
const data = [
{ name: "美国", value: 27 },
{ name: "日本", value: 22 },
{ name: "德国", value: 18 },
{ name: "英国", value: 15 },
{ name: "韩国", value: 12 },
{ name: "荷兰", value: 8 },
{ name: "其他", value: 7 }
];
const colors = [
"#66b1ff",
"#ffba63",
"#7ce3d3",
"#8a9dff",
"#ff7c7c",
"#b386f2",
"#4080ff"
];
const option = {
color: colors,
tooltip: {
trigger: "item",
formatter: "{b}: {c}%"
},
legend: {
orient: "vertical",
right: "15%",
top: "middle",
itemGap: 20,
icon: "circle",
formatter: function (name) {
const item = data.find((i) => i.name === name);
return `{name|${name}} {value|${item.value}%}`;
},
textStyle: {
rich: {
name: {
fontSize: 16,
color: "#333",
width: 60,
padding: [0, 0, 0, 5],
fontFamily: "Microsoft YaHei"
},
value: {
fontSize: 16,
color: "#333",
fontFamily: "Microsoft YaHei"
}
}
}
},
series: [
{
name: "科研仪器进口国分布",
type: "pie",
radius: ["80px", "98px"],
center: ["30%", "50%"],
avoidLabelOverlap: false,
label: {
show: false,
position: "center"
},
emphasis: {
label: {
show: false
}
},
labelLine: {
show: false
},
data: data
}
]
};
myChart.setOption(option);
window.addEventListener("resize", () => {
myChart.resize();
});
};
const initRdChart = () => {
if (!rdChartRef.value) return;
const myChart = echarts.init(rdChartRef.value);
const data = [6, 7, 6, 6, 6, 7, 7, 6, 5, 4];
const years = [
"2016",
"2017",
"2018",
"2019",
"2020",
"2021",
"2022",
"2023",
"2024",
"2025"
];
const option = {
tooltip: {
trigger: "axis"
},
grid: {
top: "15%",
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true
},
xAxis: {
type: "category",
boundaryGap: false,
data: years,
axisLine: {
lineStyle: {
color: "#E6EBF5"
}
},
axisLabel: {
color: "#606266",
fontSize: 12,
lineHeight: 18,
fontFamily: "Microsoft YaHei"
},
axisTick: {
show: false
}
},
yAxis: {
type: "value",
min: 0,
max: 10,
interval: 2,
axisLabel: {
color: "#606266",
fontSize: 12,
fontFamily: "Microsoft YaHei"
},
splitLine: {
lineStyle: {
type: "dashed",
color: "rgba(231, 243, 255, 1)"
}
}
},
series: [
{
data: data,
type: "line",
symbol: "none",
smooth: false,
lineStyle: {
color: "#055FC2",
width: 2
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: "rgba(5, 95, 194, 0.2)"
},
{
offset: 1,
color: "rgba(5, 95, 194, 0)"
}
])
},
markLine: {
symbol: "none",
data: [
{
xAxis: "2023",
label: {
formatter: "列入实体清单",
position: "end",
rotate: 0,
color: "#F56C6C",
backgroundColor: "rgba(255, 238, 238, 1)",
borderRadius: 4,
padding: [4, 8]
},
lineStyle: {
color: "#F56C6C",
type: "dotted",
width: 1
}
}
]
}
}
]
};
myChart.setOption(option);
window.addEventListener("resize", () => {
myChart.resize();
});
};
const initShareChart = () => {
if (!shareChartRef.value) return;
const myChart = echarts.init(shareChartRef.value);
const data = [61, 65, 59, 46, 46, 41, 46, 51, 21, 24];
const years = [
"2016",
"2017",
"2018",
"2019",
"2020",
"2021",
"2022",
"2023",
"2024",
"2025"
];
const option = {
tooltip: {
trigger: "axis"
},
grid: {
top: "15%",
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true
},
xAxis: {
type: "category",
boundaryGap: false,
data: years,
axisLine: {
lineStyle: {
color: "#E6EBF5"
}
},
axisLabel: {
color: "#606266",
fontSize: 12,
lineHeight: 18,
fontFamily: "Microsoft YaHei"
},
axisTick: {
show: false
}
},
yAxis: {
type: "value",
min: 0,
max: 100,
interval: 20,
axisLabel: {
color: "#606266",
fontSize: 12,
fontFamily: "Microsoft YaHei"
},
splitLine: {
lineStyle: {
type: "dashed",
color: "rgba(231, 243, 255, 1)"
}
}
},
series: [
{
data: data,
type: "line",
symbol: "none",
smooth: false,
lineStyle: {
color: "#055FC2",
width: 2
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: "rgba(5, 95, 194, 0.2)"
},
{
offset: 1,
color: "rgba(5, 95, 194, 0)"
}
])
},
markLine: {
symbol: "none",
data: [
{
xAxis: "2023",
label: {
formatter: "列入实体清单",
position: "end",
rotate: 0,
color: "#F56C6C",
backgroundColor: "rgba(255, 238, 238, 1)",
borderRadius: 4,
padding: [4, 8]
},
lineStyle: {
color: "#F56C6C",
type: "dotted",
width: 1
}
}
]
}
}
]
};
myChart.setOption(option);
window.addEventListener("resize", () => {
myChart.resize();
});
};
onMounted(() => {
initChart();
initMarketChart();
initRdChart();
initShareChart();
});
const selectedCompanyId = ref(1);
const activeDependency = ref("对外依赖");
const dependencyOptions = ["对外依赖"];
const activeInstrument = ref("电子测量仪器");
const instrumentOptions = ["电子测量仪器"];
const activeProjectDomain = ref("全部领域");
const projectDomainOptions = ["全部领域"];
const activePaperDomain = ref("全部领域");
const paperDomainOptions = ["全部领域"];
const companyList = ref([
{ id: 1, name: "空天信息创新研究院" },
{ id: 2, name: "中国科学院国家授时中心" }
]);
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.industrial-impact {
width: 100%;
padding-top: 16px;
padding-bottom: 50px;
.main {
width: 100%;
display: flex;
justify-content: space-between;
.left {
width: 480px;
height: 860px;
background-color: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
border-radius: 10px;
.left-main {
padding: 0 16px;
.top-bar {
display: flex;
gap: 12px;
margin-bottom: 8px;
.domain-select {
width: 150px;
:deep(.el-input__wrapper) {
height: 32px;
border-radius: 4px;
}
}
.search-input {
width: 288px;
:deep(.el-input__wrapper) {
height: 32px;
border-radius: 4px;
}
}
}
.company-list-container {
.list-header {
width: 446px;
height: 48px;
padding: 12px 16px;
font-size: 16px;
font-weight: 700;
color: rgb(59, 65, 75);
border-top: 1px solid rgb(234, 236, 238);
border-bottom: 1px solid rgb(234, 236, 238);
}
.company-list {
height: 700px;
overflow-y: auto;
.company-item {
display: flex;
align-items: center;
height: 48px;
padding: 0 16px;
cursor: pointer;
border-bottom: 1px solid #f0f2f5;
box-sizing: border-box;
&:hover {
background-color: #f5f7fa;
}
&.active {
background-color: rgba(246, 250, 255, 1);
border: 1px solid rgba(174, 214, 255, 1);
.company-name {
color: rgb(5, 95, 194);
font-weight: 700;
}
.icon-wrapper {
background-color: transparent;
}
}
.icon-wrapper {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
border-radius: 50%;
img {
width: 100%;
height: 100%;
}
}
.company-name {
font-size: 16px;
color: rgb(59, 65, 75);
font-weight: 400;
line-height: 24px;
font-family: "Microsoft YaHei";
}
}
}
}
}
}
.right {
width: 1104px;
height: 860px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-content: space-between;
.right-item {
width: 544px;
height: 422px;
background-color: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
border-radius: 10px;
}
.right-main {
width: 100%;
height: calc(100% - 56px);
padding: 0px 14px 15px 14px;
.echarts {
width: 516px;
height: 300px;
margin-bottom: 5px;
}
.bottom {
width: 516px;
// height: 40px;
border-radius: 4px;
background-color: rgba(246, 250, 255, 1);
border: 1px solid rgba(231, 243, 255, 1);
display: flex;
align-items: center;
padding: 6px 12px;
box-sizing: border-box;
.ai-icon {
width: 20px;
height: 20px;
margin-right: 12px;
}
.text {
flex: 1;
font-size: 16px;
font-family: "Microsoft YaHei";
font-weight: 400;
color: rgb(5, 95, 194);
line-height: 24px;
}
.right-icon {
width: 24px;
height: 24px;
cursor: pointer;
}
}
}
}
}
}
.title-com {
width: 100%;
height: 56px;
display: flex;
align-items: center;
padding: 14px 12px 16px 0;
.box {
width: 8px;
height: 20px;
background-color: rgb(5, 95, 194);
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
margin-right: 14px;
}
.text {
font-size: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(5, 95, 194);
}
.right-group {
margin-left: auto;
display: flex;
align-items: center;
.rule-checkbox {
margin-right: 20px;
:deep(.el-checkbox__label) {
font-size: 16px;
color: #5f656c;
font-family: "Microsoft YaHei";
}
:deep(.el-checkbox__inner) {
border-color: #c9cdd4;
}
:deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
background-color: #055fc2;
border-color: #055fc2;
}
:deep(.el-checkbox__input.is-checked + .el-checkbox__label) {
color: #5f656c;
}
}
.toggle-btns {
display: flex;
gap: 8px;
margin-right: 20px;
.t-btn {
padding: 2px 8px;
border: 1px solid rgb(230, 231, 232);
border-radius: 4px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
cursor: pointer;
box-sizing: border-box;
&.active {
color: rgb(5, 95, 194);
background-color: rgba(231, 243, 255, 1);
border-color: rgb(5, 95, 194);
}
}
}
.btn {
margin-left: 0;
}
.dependency-select {
width: 121px;
height: 28px;
margin-right: 14px;
:deep(.el-input__wrapper) {
height: 28px;
min-height: 28px;
padding: 0 8px;
box-shadow: 0 0 0 1px #dcdfe6 inset;
}
:deep(.el-input__inner) {
height: 28px;
line-height: 28px;
font-size: 14px;
}
}
.instrument-select {
width: 180px;
height: 28px;
margin-right: 14px;
:deep(.el-input__wrapper) {
height: 28px;
min-height: 28px;
padding: 0 8px;
box-shadow: 0 0 0 1px #dcdfe6 inset;
}
:deep(.el-input__inner) {
height: 28px;
line-height: 28px;
font-size: 14px;
}
}
.project-domain-select {
width: 150px;
height: 28px;
margin-right: 14px;
:deep(.el-input__wrapper) {
height: 28px;
min-height: 28px;
padding: 0 8px;
box-shadow: 0 0 0 1px #dcdfe6 inset;
}
:deep(.el-input__inner) {
height: 28px;
line-height: 28px;
font-size: 14px;
}
}
.paper-domain-select {
width: 150px;
height: 28px;
margin-right: 14px;
:deep(.el-input__wrapper) {
height: 28px;
min-height: 28px;
padding: 0 8px;
box-shadow: 0 0 0 1px #dcdfe6 inset;
}
:deep(.el-input__inner) {
height: 28px;
line-height: 28px;
font-size: 14px;
}
}
}
.btn {
width: 92px;
height: 28px;
margin-left: auto;
img {
width: 28px;
height: 28px;
cursor: pointer;
}
img:first-child {
margin-right: 4px;
}
}
.btn-com {
width: 60px;
height: 28px;
margin-left: auto;
img {
width: 28px;
height: 28px;
cursor: pointer;
}
}
}
</style>
<template>
<div class="impact-analysis">
<div class="side-nav">
<div
v-for="(item, index) in activeTab"
:key="index"
class="tab-item"
:class="{ active: index === activeIndex }"
@click="activeIndex = index"
>
{{ item }}
<span v-if="index === activeIndex" class="arrow"></span>
</div>
</div>
<industrial-impact v-if="activeIndex === 0"></industrial-impact>
<research-impact v-if="activeIndex === 1"></research-impact>
</div>
</template>
<script setup>
import { ref } from "vue";
import industrialImpact from "./components/industrialImpact/index.vue";
import researchImpact from "./components/researchImpact/index.vue";
const activeTab = ref(["对华产业影响", "对华科研影响"]);
const activeIndex = ref(0);
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.impact-analysis {
width: 1601px;
margin: 0 auto;
position: relative;
.side-nav {
position: absolute;
top: 27px;
right: 100%;
margin-right: 12px;
display: flex;
flex-direction: column;
gap: 16px;
.tab-item {
cursor: pointer;
padding: 4px 20px;
border-radius: 22px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
white-space: nowrap;
display: flex;
align-items: center;
&.active {
background-color: rgb(5, 95, 194);
color: #fff;
.arrow {
display: inline-block;
width: 0;
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
border-left: 6px solid #fff;
margin-left: 8px;
}
}
}
}
}
</style>
<template>
<div class="sanctions-overview">
<div class="main">
<div class="left">
<div class="left-top">
<div class="title-com">
<div class="box"></div>
<div class="text">基本信息</div>
<div class="btn">
<img src="../../assets/下载按钮.png" alt="" />
<img src="../../assets/收藏按钮.png" alt="" />
</div>
</div>
<div class="left-top-title">
<div class="info-row">
<div class="label">发布机构:</div>
<div class="value link">
<img :src="title" alt="" class="icon">
<span>商务部工业与安全局 ></span>
</div>
</div>
<div class="info-row">
<div class="label">发布时间:</div>
<div class="value">2025年9月16日</div>
</div>
<div class="info-row">
<div class="label">生效时间:</div>
<div class="value">2025年9月12日</div>
</div>
<div class="info-row">
<div class="label">发布文件:</div>
<div class="value">2025-17893 (90 FR 44496)</div>
</div>
<div class="info-row">
<div class="label">案卷号:</div>
<div class="value">No. 250912-0152</div>
</div>
<div class="info-row">
<div class="label">发布人:</div>
<div class="value link">
<img :src="defaultTitle" alt="" class="icon avatar">
<span>朱莉哑·A·赫尔松斯基 (战略贸易副助理部长) ></span>
</div>
</div>
<div class="info-row">
<div class="label">制裁领域:</div>
<div class="value tags">
<span class="tag">量子科技</span>
<span class="tag">人工智能</span>
<span class="tag">集成电路</span>
</div>
</div>
</div>
<div class="left-top-content">
<div class="content-title">制裁实体分布:</div>
<div class="distribution-list">
<div
class="list-item"
v-for="(item, index) in entityDistribution"
:key="index"
>
<img :src="flag" alt="" class="flag">
<div class="country-name">{{ item.name }}</div>
<div class="progress-bar-container">
<div
class="progress-bar"
:style="{
width: item.width,
background: item.gradient
}"
></div>
</div>
<div class="count" :class="{ 'highlight': index === 0 }">{{ item.count }}</div>
</div>
</div>
</div>
</div>
<div class="left-bottom">
<div class="title-com">
<div class="box"></div>
<div class="text">制裁背景</div>
<div class="btn">
<img src="../../assets/下载按钮.png" alt="" />
<img src="../../assets/收藏按钮.png" alt="" />
</div>
</div>
<div class="left-bottom-content">
<div class="timeline-list">
<div
class="timeline-item"
v-for="(item, index) in timelineData"
:key="index"
>
<div class="date-row">
<div class="dot"></div>
<div class="date">{{ item.date }}</div>
</div>
<div class="content">{{ item.content }}</div>
</div>
</div>
<div class="view-more">
查看更多 <el-icon class="icon-more"><DArrowRight /></el-icon>
</div>
</div>
</div>
</div>
<div class="right">
<div class="title-com">
<div class="box"></div>
<div class="text">制裁清单</div>
<div class="btn">
<img src="../../assets/下载按钮.png" alt="" />
<img src="../../assets/收藏按钮.png" alt="" />
</div>
</div>
<div class="right-title">
<div class="filter-row">
<div class="filter-left">
<el-select v-model="filterEntity" placeholder="受制裁实体" style="width: 184px">
<el-option label="受制裁实体" value="1" />
</el-select>
</div>
<div class="filter-right">
<el-checkbox v-model="onlyChina" label="只看中国实体" />
<el-select v-model="filterField" placeholder="全部领域" style="width: 150px; margin: 0 12px 0 16px">
<el-option label="全部领域" value="1" />
</el-select>
<el-input
v-model="searchKeyword"
placeholder="搜索实体"
style="width: 150px"
:suffix-icon="Search"
/>
</div>
</div>
<div class="stats-row">
<div class="tabs">
<div class="tab-btn" :class="{ active: activeTab === 'add' }" @click="activeTab = 'add'">新增实体</div>
<div class="tab-btn" :class="{ active: activeTab === 'remove' }" @click="activeTab = 'remove'">移除实体</div>
</div>
<div class="stats-info">
<div class="stat-item">
<span class="dot red"></span>
<span class="text">新增 <span class="num red">24</span> 家 (50%规则涉及<span class="num red">51</span>家)</span>
</div>
<div class="stat-item">
<span class="dot green"></span>
<span class="text">移除 <span class="num green">4</span> 家 (50%规则涉及<span class="num green">6</span>家)</span>
</div>
</div>
</div>
</div>
<div class="right-content">
<div class="sanction-group-list">
<div class="sanction-group" v-for="(group, index) in sanctionList" :key="index">
<el-table
:data="group.entities"
style="width: 100%"
>
<el-table-column label="实体名称" min-width="280">
<template #default="scope">
<div class="name-cell">
<div class="dot"></div>
<img :src="defaultTitle" class="company-icon" />
<span class="company-name">{{ scope.row.name }}</span>
</div>
</template>
</el-table-column>
<el-table-column label="涉及领域" width="120" align="center">
<template #default="scope">
<span class="tag" :style="getTagStyle(scope.row.field)">{{ scope.row.field }}</span>
</template>
</el-table-column>
<el-table-column prop="location" label="上市地点" width="100" align="center" />
<el-table-column prop="date" label="制裁时间" width="120" align="center" />
<el-table-column prop="revenue" label="营收(亿元)" width="120" align="center" />
<el-table-column label="50%规则子企业" width="180" align="center">
<template #default="scope">
<span v-if="scope.row.subsidiaryCount" class="subsidiary-link">
{{ scope.row.subsidiaryText }} <span class="blue-text">{{ scope.row.subsidiaryCount }}家 ></span>
</span>
<span v-else>--</span>
</template>
</el-table-column>
</el-table>
<div class="reason-box">
{{ group.reason }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { DArrowRight, Search } from "@element-plus/icons-vue";
import title from "../../assets/title.png"
import defaultTitle from "../../assets/default-icon1.png"
import flag from "../../assets/default-icon2.png"
const filterEntity = ref('')
const onlyChina = ref(true)
const filterField = ref('')
const searchKeyword = ref('')
const activeTab = ref('add')
const tagColors = [
{ bg: 'rgb(242, 235, 255)', border: 'rgb(211, 190, 255)', text: 'rgb(114, 46, 209)' }, // Purple
{ bg: 'rgb(225, 250, 248)', border: 'rgb(178, 242, 238)', text: 'rgb(16, 178, 166)' }, // Cyan
{ bg: 'rgb(231, 243, 255)', border: 'rgb(186, 224, 255)', text: 'rgb(5, 95, 194)' }, // Blue
{ bg: 'rgb(255, 247, 230)', border: 'rgb(255, 213, 145)', text: 'rgb(250, 140, 22)' }, // Orange
{ bg: 'rgb(255, 241, 240)', border: 'rgb(255, 204, 199)', text: 'rgb(245, 34, 45)' }, // Red
{ bg: 'rgb(246, 255, 237)', border: 'rgb(183, 235, 143)', text: 'rgb(82, 196, 26)' }, // Green
];
const getTagStyle = (tagName) => {
if (!tagName) return {};
let hash = 0;
for (let i = 0; i < tagName.length; i++) {
hash = tagName.charCodeAt(i) + ((hash << 5) - hash);
}
const index = Math.abs(hash) % tagColors.length;
const color = tagColors[index];
return {
backgroundColor: color.bg,
borderColor: color.border,
color: color.text
};
}
const sanctionList = ref([
{
reason: "该实体因获取和试图获取美国原产物品以支持中国军事和国防相关空间领域活动以及中国量子技术能力而被列入;鉴于量子技术的军事应用,这些行动对美国国家安全造成严重影响。",
entities: [
{
name: "中国科学院国家授时中心",
field: "量子科技",
fieldClass: "purple",
location: "--",
date: "2025年9月",
revenue: "325",
subsidiaryCount: 0
}
]
},
{
reason: "这些实体被列入清单是因为其获取和试图获取美国原产物品以支持中国军事现代化,参与中国先进计算和集成电路制造及分销领域,以及直接供应中国军事、政府和安全机构。上海复旦微电子股份有限公司和中芯国际集成电路制造有限公司将获得脚注 4 的指定。这些实体参与高性能计算(HPC)芯片的生产,包括人工智能和其他双重用途应用。此外,上海复旦微电子股份有限公司已向俄罗斯军事最终用户提供技术。",
entities: [
{
name: "北京复旦微电子技术有限公司",
field: "集成电路",
fieldClass: "cyan",
location: "深圳",
date: "2025年9月",
revenue: "325",
subsidiaryCount: 0
},
{
name: "上海复旦微电子股份有限公司",
field: "集成电路",
fieldClass: "cyan",
location: "--",
date: "2025年9月",
revenue: "325",
subsidiaryText: "北京复旦微电子技术有...等",
subsidiaryCount: 4
},
{
name: "深圳复旦微电子有限公司",
field: "集成电路",
fieldClass: "cyan",
location: "--",
date: "2025年9月",
revenue: "325",
subsidiaryCount: 0
},
{
name: "上海复控华龙微系统技术有限公司",
field: "集成电路",
fieldClass: "cyan",
location: "--",
date: "2025年9月",
revenue: "325",
subsidiaryCount: 0
},
{
name: "上海富伟迅捷数字技术有限公司",
field: "集成电路",
fieldClass: "cyan",
location: "--",
date: "2025年9月",
revenue: "325",
subsidiaryCount: 0
},
{
name: "中芯国际集成电路制造有限公司",
field: "集成电路",
fieldClass: "cyan",
location: "上海",
date: "2025年9月",
revenue: "325",
subsidiaryText: "中芯协成投资(背景)...等",
subsidiaryCount: 5
},
{
name: "上海复旦微电子(香港)有限公司",
field: "集成电路",
fieldClass: "cyan",
location: "--",
date: "2025年9月",
revenue: "325",
subsidiaryCount: 0
}
]
},
{
reason: "这些实体因参与支持受制裁实体(包括伊朗军方)的两用物品转售而被添加。",
entities: [
{
name: "华科供应链(香港)有限公司",
field: "集成电路",
fieldClass: "cyan",
location: "香港",
date: "2025年9月",
revenue: "325",
subsidiaryText: "讯飞智元信息科技有限公司...等",
subsidiaryCount: 2
},
{
name: "华科物流(香港)有限公司",
field: "集成电路",
fieldClass: "cyan",
location: "--",
date: "2025年9月",
revenue: "325",
subsidiaryCount: 0
},
{
name: "深圳新利康供应链管理有限公司",
field: "集成电路",
fieldClass: "cyan",
location: "--",
date: "2025年9月",
revenue: "325",
subsidiaryCount: 0
}
]
},
{
reason: "该实体被添加是因为其与支持中国高空气球计划的公司有关联。",
entities: [
{
name: "中国科学院空天信息研究院",
field: "航空航天",
fieldClass: "blue",
location: "--",
date: "2025年9月",
revenue: "325",
subsidiaryCount: 0
}
]
},
{
reason: "此次列入的原因是这两家公司采购了原产于美国的物项,并将其转售给实体清单上的实体。具体而言,这两家公司在未获得美国商务部工业安全局(BIS)的必要许可或授权的情况下,为实体清单上的两家实体——中芯国际北方集成电路制造(北京)有限公司和中芯国际(北京)股份有限公司——采购了原产于美国的半导体制造设备。",
entities: [
{
name: "GMC 半导体技术(无锡)有限公司",
field: "集成电路",
fieldClass: "cyan",
location: "深圳",
date: "2025年9月",
revenue: "325",
subsidiaryText: "GMC集成电路技术有...等",
subsidiaryCount: 1
},
{
name: "济村半导体技术(上海)有限公司",
field: "集成电路",
fieldClass: "cyan",
location: "--",
date: "2025年9月",
revenue: "325",
subsidiaryCount: 0
}
]
},
{
reason: "这些实体构成不可接受的风险,可能将原产于美国的物项用于或转移给中国人民解放军军事医学科学院。",
entities: [
{
name: "北京天一辉远生物技术有限公司",
field: "生物科技",
fieldClass: "cyan",
location: "深圳",
date: "2025年9月",
revenue: "325",
subsidiaryText: "北京天一辉远制药生产...等",
subsidiaryCount: 1
},
{
name: "北京擎科生物技术有限公司",
field: "生物科技",
fieldClass: "cyan",
location: "--",
date: "2025年9月",
revenue: "325",
subsidiaryCount: 0
},
{
name: "上海生工生物工程股份有限公司",
field: "生物科技",
fieldClass: "cyan",
location: "深圳",
date: "2025年9月",
revenue: "325",
subsidiaryText: "讯飞智元信息科技有限公司...等",
subsidiaryCount: 3
}
]
},
{
reason: "它们为中国军工联合体的关键客户(包括美国商务部工业安全局(BIS)实体清单上的客户)开发计算机辅助工程软件。",
entities: [
{
name: "上海索辰信息技术有限公司",
field: "集成电路",
fieldClass: "cyan",
location: "深圳",
date: "2025年9月",
revenue: "325",
subsidiaryText: "讯飞智元信息科技有限公司...等",
subsidiaryCount: 15
},
{
name: "香港德迈克斯有限公司",
field: "集成电路",
fieldClass: "cyan",
location: "--",
date: "2025年9月",
revenue: "325",
subsidiaryText: "讯飞智元信息科技有限公司...等",
subsidiaryCount: 15
}
]
},
{
reason: "因为它们存在规避出口管制和将产品转移至被列入实体清单实体的风险。",
entities: [
{
name: "长沙网发电子科技有限公司",
field: "集成电路",
fieldClass: "cyan",
location: "深圳",
date: "2025年9月",
revenue: "325",
subsidiaryText: "讯飞智元信息科技有限公司...等",
subsidiaryCount: 15
},
{
name: "常州网发微电子有限公司",
field: "集成电路",
fieldClass: "cyan",
location: "--",
date: "2025年9月",
revenue: "325",
subsidiaryText: "讯飞智元信息科技有限公司...等",
subsidiaryCount: 15
},
{
name: "成都网发微电子有限公司",
field: "集成电路",
fieldClass: "cyan",
location: "深圳",
date: "2025年9月",
revenue: "325",
subsidiaryText: "讯飞智元信息科技有限公司...等",
subsidiaryCount: 15
},
{
name: "深圳网发微电子有限公司",
field: "集成电路",
fieldClass: "cyan",
location: "--",
date: "2025年9月",
revenue: "325",
subsidiaryText: "讯飞智元信息科技有限公司...等",
subsidiaryCount: 15
}
]
}
])
const timelineData = ref([
{
date: "2025-07-31",
content: "美商务部发布指南,警告全球企业使用华为昇腾芯片可能违反美国出口管制。意在限制中国AI产业发展,阻碍其获得先进算力。"
},
{
date: "2025-07-30",
content: "美商务部持续对多种中国产品发起“双反”(反倾销、反补贴)调查并作出裁决,涉及产品从工业原料到日常用品,且裁定的税率普遍较高。"
},
{
date: "2025-07-30",
content: "美商务部进一步收紧对华先进半导体出口管制,将更多中国实体列入“实体清单”。限制14纳米及以下先进芯片、DRAM等对华出口"
}
])
const entityDistribution = ref([
{
name: "中国",
count: 24,
width: "100%",
gradient: "linear-gradient(270deg, rgba(206,79,81,1) 0%, rgba(255,255,255,0) 100%)"
},
{
name: "沙特阿拉伯",
count: 2,
width: "60%",
gradient: "linear-gradient(270deg, rgba(255,170,0,1) 0%, rgba(255,255,255,0) 100%)"
},
{
name: "俄罗斯",
count: 2,
width: "60%",
gradient: "linear-gradient(270deg, rgba(255,170,0,1) 0%, rgba(255,255,255,0) 100%)"
},
{
name: "中国香港",
count: 1,
width: "50%",
gradient: "linear-gradient(270deg, rgba(5,95,194,1) 0%, rgba(255,255,255,0) 100%)"
}
])
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.sanctions-overview {
width: 1601px;
margin: 0 auto;
padding-top: 16px;
padding-bottom: 50px;
.main {
width: 100%;
height: 100%;
display: flex;
justify-content: space-between;
.left {
width: 520px;
height: 1119px;
.left-top {
width: 100%;
height: 582px;
padding-bottom: 20px;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background-color: #fff;
margin-bottom: 16px;
.left-top-title {
padding: 22px 0 22px 27px;
width: 100%;
height: 286px;
border-bottom: 1px solid rgb(234, 236, 238);
display: flex;
flex-direction: column;
// justify-content: space-between;
gap: 12px;
.info-row {
display: flex;
align-items: center;
font-size: 16px;
font-family: "Microsoft YaHei";
line-height: 24px;
.label {
min-width: 92px;
color: rgb(59, 65, 75);
font-weight: 700;
font-size: 16px;
font-family: "Microsoft YaHei";
line-height: 24px;
text-align: left;
}
.value {
display: flex;
align-items: center;
color: rgb(59, 65, 75);
font-weight: 400;
font-size: 16px;
font-family: "Microsoft YaHei";
line-height: 24px;
&.link {
color: rgb(5, 95, 194);
cursor: pointer;
}
.icon {
width: 20px;
height: 20px;
margin-right: 8px;
object-fit: contain;
&.avatar {
border-radius: 50%;
width: 24px;
height: 24px;
}
}
&.tags {
gap: 8px;
.tag {
padding: 1px 8px;
background: rgba(246, 250, 255, 1);
border-radius: 4px;
border: 1px solid rgba(231, 243, 255, 1);
color: rgb(5, 95, 194);
font-size: 14px;
line-height: 20px;
}
}
}
}
}
.left-top-content {
width: 100%;
height: 234px;
padding: 19px 29px 22px 27px;
.content-title {
font-size: 16px;
font-weight: 700;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
line-height: 24px;
margin-bottom: 12px;
text-align: left;
}
.distribution-list {
display: flex;
flex-direction: column;
gap: 16px;
.list-item {
display: flex;
align-items: center;
height: 24px;
.flag {
width: 24px;
height: 24px;
margin-right: 16px;
border-radius: 50%;
object-fit: cover;
}
.country-name {
width: 96px;
font-size: 16px;
font-weight: 400;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
line-height: 24px;
margin-right: 12px;
text-align: left;
}
.progress-bar-container {
flex: 1;
height: 8px;
// background: #f0f2f5;
// border-radius: 4px;
// overflow: hidden;
margin-right: 24px;
display: flex;
align-items: center;
.progress-bar {
height: 100%;
border-radius: 4px;
}
}
.count {
// width: 40px;
text-align: right;
font-size: 16px;
font-weight: 400;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
line-height: 24px;
&.highlight {
color: rgb(206, 79, 81);
font-weight: 700;
}
}
}
}
}
}
.left-bottom {
width: 100%;
height: 521px;
padding-bottom: 20px;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background-color: #fff;
.left-bottom-content {
padding: 26px 30px 0 25px;
.timeline-list {
display: flex;
flex-direction: column;
gap: 24px;
margin-bottom: 24px;
}
.timeline-item {
display: flex;
flex-direction: column;
// margin-left: 12px;
position: relative;
&::before {
content: "";
position: absolute;
left: 6px;
top: 12px;
height: calc(100% + 24px);
width: 1px;
background-color: rgb(234, 236, 238);
transform: translateX(-50%);
}
&:last-child::before {
display: none;
}
}
.date-row {
display: flex;
align-items: center;
margin-bottom: 3px;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #fff;
border: 3px solid rgb(5, 95, 194);
box-sizing: border-box;
margin-right: 13px;
position: relative;
z-index: 1;
}
.date {
font-size: 16px;
font-weight: 700;
color: rgb(5, 95, 194);
font-family: "Microsoft YaHei";
line-height: 24px;
// margin-bottom: 3px;
}
.content {
font-size: 16px;
font-weight: 400;
color: rgb(59, 65, 75);
line-height: 24px;
margin-left: 25px;
text-align: left;
font-family: "Microsoft YaHei";
}
.view-more {
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 400;
color: rgb(5, 95, 194);
cursor: pointer;
margin-top: 24px;
font-family: "Microsoft YaHei";
.icon-more {
transform: rotate(90deg);
margin-left: 8px;
}
}
}
}
}
.right {
width: 1064px;
height: auto;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background-color: #fff;
.right-title {
width: 100%;
// height: 107px;
padding: 4px 20px 20px 20px;
box-sizing: border-box;
.filter-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
:deep(.el-input__inner) {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
&::placeholder {
color: rgb(95, 101, 108);
}
}
:deep(.el-checkbox__label) {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
}
.filter-right {
display: flex;
align-items: center;
}
}
.stats-row {
display: flex;
justify-content: space-between;
align-items: center;
.tabs {
display: flex;
gap: 8px;
.tab-btn {
padding: 4px 8px;
border: 1px solid rgb(230, 231, 232);
border-radius: 4px;
cursor: pointer;
font-size: 18px;
color: rgb(59, 65, 75);
font-weight: 400;
line-height: 24px;
font-family: "Microsoft YaHei";
&.active {
color: rgb(5, 95, 194);
border-color: rgb(5, 95, 194);
font-weight: 700;
}
}
}
.stats-info {
display: flex;
gap: 24px;
align-items: center;
.stat-item {
display: flex;
align-items: center;
font-size: 18px;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
line-height: 24px;
margin-right: 34px;
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 8px;
&.red {
background-color: rgb(206, 79, 81);
}
&.green {
background-color: rgb(33, 129, 57);
}
}
.text {
font-size: 18px;
font-weight: 700;
line-height: 24px;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
.num {
font-weight: 700;
&.red {
color: rgb(206, 79, 81);
}
&.green {
color: rgb(33, 129, 57);
}
}
}
}
}
}
}
.right-content {
padding: 0 20px 17px 20px;
.sanction-group-list {
display: flex;
flex-direction: column;
gap: 16px;
.sanction-group {
border: 1px solid rgb(234, 236, 238);
border-radius: 8px;
overflow: hidden;
:deep(.el-table__header-wrapper) {
th {
background-color: rgb(59, 65, 75) !important;
height: 48px;
padding: 0;
color: #fff;
font-weight: 700;
font-size: 16px;
font-family: "Microsoft YaHei";
border-bottom: none;
}
th:first-child {
border-top-left-radius: 8px;
}
th:last-child {
border-top-right-radius: 8px;
}
}
:deep(.el-table__body-wrapper) {
tr:nth-child(odd) td {
background-color: rgba(248, 249, 250, 1);
}
tr:nth-child(even) td {
background-color: #fff;
}
td {
height: 48px;
padding: 0;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
color: rgb(95, 101, 108);
line-height: 24px;
}
}
.name-cell {
display: flex;
align-items: center;
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: rgb(206, 79, 81);
margin-right: 16px;
flex-shrink: 0;
}
.company-icon {
width: 24px;
height: 24px;
margin-right: 12px;
border-radius: 50%;
flex-shrink: 0;
}
.company-name {
font-size: 16px;
font-weight: 700;
line-height: 24px;
font-family: "Microsoft YaHei";
color: rgba(59, 65, 75, 1);
}
}
.tag {
padding: 1px 8px;
border-radius: 4px;
font-size: 14px;
display: inline-block;
border: 1px solid transparent;
}
.subsidiary-link {
cursor: pointer;
font-size: 14px;
color: rgb(59, 65, 75);
.blue-text {
color: rgb(5, 95, 194);
}
}
.reason-box {
padding: 12px 19px 14px 39px;
background-color: #fff;
border-top: 1px solid rgb(234, 236, 238);
font-size: 16px;
color: rgb(59, 65, 75);
line-height: 28px;
font-family: "Microsoft YaHei";
text-align: left;
}
}
}
}
}
}
}
.title-com {
width: 100%;
height: 56px;
display: flex;
align-items: center;
padding: 14px 12px 16px 0;
.box {
width: 8px;
height: 20px;
background-color: rgb(5, 95, 194);
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
margin-right: 14px;
}
.text {
font-size: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(5, 95, 194);
}
.btn {
width: 60px;
height: 28px;
margin-left: auto;
img {
width: 28px;
height: 28px;
cursor: pointer;
}
img:first-child {
margin-right: 4px;
}
}
}
</style>
<template>
<div class="entity-list">
<div class="header">
<div class="header-title">
<img :src="headerTitle.img" alt="">
<div>
<div class="title">
{{ headerTitle.title }}
<span>{{ headerTitle.titleEn }}</span>
</div>
<div class="department">
{{ headerTitle.department }}
</div>
</div>
<div class="btn">
<img :src="icon01" alt="">切换
</div>
</div>
<div class="header-nav">
<div
class="nav-item"
v-for="(item, index) in headerNavList"
:key="index"
:class="{ active: activeIndex === index }"
@click="activeIndex = index"
>
<img :src="activeIndex === index ? item.imgActive : item.img" alt="">
<span>{{ item.title }}</span>
<div class="active-line" v-if="activeIndex === index"></div>
</div>
<div class="original-text-btn">
<img :src="icon1" alt="">
<span>实体清单原文</span>
</div>
</div>
</div>
<div class="main">
<sanctions-overview v-if="activeIndex === 0"></sanctions-overview>
<data-statistics v-if="activeIndex === 1"></data-statistics>
<deep-mining v-if="activeIndex === 2"></deep-mining>
<impact-analysis v-if="activeIndex === 3"></impact-analysis>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import sanctionsOverview from "./components/sanctionsOverview/index.vue"
import dataStatistics from "./components/dataStatistics/index.vue"
import deepMining from "./components/deepMining/index.vue"
import impactAnalysis from "./components/impactAnalysis/index.vue"
import title from "./assets/title.png"
import icon01 from "./assets/icon01.png"
import icon1 from "../assets/icons/icon1.png";
import icon1Active from "../assets/icons/icon1_active.png";
import icon5 from "../assets/icons/icon5.png";
import icon5Active from "../assets/icons/icon5_active.png";
import icon2 from "../assets/icons/icon2.png";
import icon2Active from "../assets/icons/icon2_active.png";
import icon3 from "../assets/icons/icon3.png";
import icon3Active from "../assets/icons/icon3_active.png";
const headerTitle = ref({
img: title,
title: "2025年9月12日《对实体清单的添加和修订 》",
titleEn: "",
department: "2025-17893(90 FR 44496)"
})
const activeIndex = ref(0)
const headerNavList = ref([
{
img: icon1,
imgActive: icon1Active,
title: "制裁概况"
},
{
img: icon5,
imgActive: icon5Active,
title: "数据统计"
},
{
img: icon2,
imgActive: icon2Active,
title: "深度挖掘"
},
{
img: icon3,
imgActive: icon3Active,
title: "影响分析"
}
])
</script>
<style scoped lang="scss">
*{
margin: 0;
padding: 0;
}
.entity-list{
width: 100%;
height: 100%;
.header{
width: 100%;
height: 148px;
background-color: #fff;
padding-top: 16px;
.header-title{
width: 1601px;
height: 72px;
background-color: rgba(246, 250, 255, 1);
margin: 0 auto;
border-radius: 10px;
border: 2px solid rgba(174, 214, 255, 1);
display: flex;
align-items: center;
margin-bottom: 12px;
position: relative;
img {
width: 54px;
height: 54px;
margin-left: 15px;
margin-right: 11px;
}
.title {
font-size: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(59, 65, 75);
span {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
margin-left: 11px;
}
}
.department {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
}
.btn {
cursor: pointer;
display: flex;
align-items: center;
position: absolute;
right: 16px;
top: 25px;
font-size: 18px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(5, 95, 194);
img {
width: 20px;
height: 20px;
margin-right: 7px;
}
}
}
.header-nav {
width: 1601px;
margin: 0 auto;
height: 48px;
display: flex;
align-items: center;
.nav-item {
display: flex;
align-items: center;
height: 100%;
margin-right: 32px;
cursor: pointer;
position: relative;
font-size: 18px;
font-weight: 400;
font-family: "Microsoft YaHei";
color: rgb(59, 65, 75);
&:last-child {
margin-right: 0;
}
img {
width: 16px;
height: 16px;
margin-right: 4px;
}
&.active {
color: rgb(5, 95, 194);
font-weight: 700;
}
.active-line {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 3px;
background-color: #055fc2;
border-radius: 1.5px;
}
}
.original-text-btn {
margin-left: auto;
width: 152px;
height: 36px;
background: #FFFFFF;
border-radius: 4px;
border: 1px solid rgba(230, 231, 232, 1);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
img {
width: 16px;
height: 16px;
margin-right: 8px;
}
span {
font-size: 16px;
font-weight: 400;
color: rgb(95, 101, 108);
font-family: "Microsoft YaHei";
line-height: 24px;
}
}
}
}
.main{
width: 100%;
height: calc(100% - 148px);
background-color: #F7F8F9;
}
}
</style>
<template>
<div class="symmetry-progress-container">
<div class="progress-left">
<div class="label">{{ leftVal }}%</div>
<div class="bar-wrapper">
<div class="bar left" :style="{ width: leftVal + '%' }"></div>
</div>
</div>
<div class="separator"></div>
<div class="progress-right">
<div class="bar-wrapper">
<div class="bar right" :style="{ width: rightVal + '%' }"></div>
</div>
<div class="label">{{ rightVal }}%</div>
</div>
</div>
</template>
<script setup>
defineProps({
leftVal: {
type: Number,
default: 0
},
rightVal: {
type: Number,
default: 0
}
});
</script>
<style scoped>
.symmetry-progress-container {
display: flex;
align-items: center;
width: 100%;
height: 40px;
}
.progress-left, .progress-right {
flex: 1;
display: flex;
align-items: center;
gap: 10px;
}
.progress-left {
justify-content: flex-end;
}
.bar-wrapper {
flex: 1;
height: 12px;
background-color: #f0f0f0;
border-radius: 6px;
overflow: hidden;
max-width: 200px;
}
.bar {
height: 100%;
}
.bar.left {
background-color: #409eff;
float: right; /* Align to right side of the container */
}
.bar.right {
background-color: #67c23a;
}
.separator {
width: 2px;
height: 20px;
background-color: #ccc;
margin: 0 15px;
}
.label {
font-size: 14px;
font-weight: bold;
color: #333;
min-width: 35px;
}
</style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论