提交 500577d5 authored 作者: 李智林's avatar 李智林

出口管制2.0更新

上级 a94961db
...@@ -842,23 +842,21 @@ export function getScientificImpactEntityList(startTime) { ...@@ -842,23 +842,21 @@ export function getScientificImpactEntityList(startTime) {
/** /**
* 概览页--获取出口管制制裁措施 * 概览页--获取出口管制制裁措施
* @param {Object} data * @param {Object} data
* @param {string} [data.typeName="实体清单"] - 类型名称 * @param {string} [data.typeName="实体清单"] - 类型名称(实体清单、商业管制清单、军事最终用户清单)
* @param {boolean} [data.isCn=true] - 是否只看中国实体 * @param {boolean} [data.isCn=true] - 是否只看涉华
* @param {string[]} [data.techDomains] - 科技领域ID列表 * @param {Array<string>} [data.techDomainIds] - 科技领域ID
* @param {number[]} [data.years] - 制裁年份列表 * @param {Array<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) { export function getExportControlList(data) {
return request200( return request200(
request({ request({
method: "POST", method: "POST",
url: "/api/sanctionList/pageQuery", url: "/api/entitiesDataCount/getSanctionProcess",
data data
}) })
); );
} }
\ No newline at end of file
//
\ No newline at end of file
...@@ -224,4 +224,345 @@ export function getDeepMiningIndustryEntity(params) { ...@@ -224,4 +224,345 @@ export function getDeepMiningIndustryEntity(params) {
url: `/api/chain/getChainEntityStat`, url: `/api/chain/getChainEntityStat`,
params, params,
}) })
}
// 单次制裁-制裁概况-基本信息
/**
* @param {Object} params
* @param {string} params.sanRecordId - 制裁记录ID
* @header token
*/
export function getSingleSanctionOverview(params) {
return request({
method: 'GET',
url: `/api/sanctionList/record/getDetail`,
params,
})
}
// 单次制裁-制裁概况-制裁实体国家分布
/**
* @param {Object} params
* @param {string} params.sanRecordId - 制裁记录ID
* @header token
*/
export function getSingleSanctionEntityCountry(params) {
return request({
method: 'GET',
url: `/api/sanctionList/statistics/el/countryRegion`,
params,
})
}
// 单次制裁-制裁概况-制裁背景
/**
* @param {Object} data
* @param {string} data.sanRecordId - 制裁记录ID
* @param {number} [data.pageNum=1] - 页码
* @param {number} [data.pageSize=3] - 每页数量
* @header token
*/
export function getSingleSanctionBackground(data) {
return request({
method: 'POST',
url: `/api/sanctionList/record/background`,
data,
})
}
// 单次制裁-制裁概况-制裁清单
/**
* @param {Object} data
* @param {Integer} data.sanRecordId - 制裁记录ID
* @param {Boolean} [data.isOnlyCn=true] - 是否仅查询中国数据
* @param {String} [data.domainId] - 科技领域ID
* @param {String} [data.searchText] - 搜索文本
* @header token
*/
export function getSingleSanctionOverviewList(data) {
return request({
method: 'POST',
url: `/api/sanctionList/record/sanList`,
data,
})
}
// 单次制裁-数据统计-总量统计
/**
* @param {Object} params
* @param {string} params.sanRecordId - 制裁记录ID
* @header token
*/
export function getSingleSanctionTotalCount(params) {
return request({
method: 'GET',
url: `/api/sanctionList/statistics/el/total`,
params,
})
}
// 单次制裁-数据统计-制裁实体领域分布情况
/**
* @param {Object} params
* @param {string} params.sanRecordId - 制裁记录ID
* @param {string} [params.startDate] - 开始时间
* @param {string} [params.endDate] - 结束时间
* @header token
*/
export function getSingleSanctionDomainCount(params) {
return request({
method: 'GET',
url: `/api/sanctionList/statistics/el/domain`,
params,
})
}
// 单次制裁-数据统计-制裁实体类型分布情况
/**
* @param {Object} params
* @param {string} params.sanRecordId - 制裁记录ID
* @param {string} [params.startDate] - 开始时间
* @param {string} [params.endDate] - 结束时间
* @header token
*/
export function getSingleSanctionEntityTypeCount(params) {
return request({
method: 'GET',
url: `/api/sanctionList/statistics/el/entityType`,
params,
})
}
// 单次制裁-数据统计-制裁实体国家分布
/**
* @param {Object} params
* @param {string} params.sanRecordId - 制裁记录ID
* @header token
*/
export function getSingleSanctionEntityCountryCount(params) {
return request({
method: 'GET',
url: `/api/sanctionList/statistics/el/countryRegion`,
params,
})
}
// 单次制裁-数据统计-制裁实体地域分布情况
/**
* @param {Object} params
* @param {string} params.sanRecordId - 制裁记录ID
* @param {string} [params.startDate] - 开始时间
* @param {string} [params.endDate] - 结束时间
* @header token
*/
export function getSingleSanctionEntityRegionCount(params) {
return request({
method: 'GET',
url: `/api/sanctionList/statistics/el/region`,
params,
})
}
// 单次制裁-深度挖掘-本次制裁实体清单列表
/**
* @param {Object} params
* @param {Integer} data.sanRecordId - 制裁记录ID
* @param {Boolean} [data.isOnlyCn=true] - 是否仅查询中国数据
* @param {String} [data.domainId] - 科技领域ID
* @param {String} [data.searchText] - 搜索文本
* @header token
*/
export function getSingleSanctionEntityList(data) {
return request({
method: 'POST',
url: `/api/sanctionList/record/sanListByType`,
data,
})
}
// 单次制裁-深度挖掘-制裁实体供应链信息
/**
* @param {Object} params
* @param {Integer} params.orgId - 组织ID
* @header token
*/
export function getSingleSanctionEntitySupplyChain(params) {
return request({
method: 'GET',
url: `/api/sanctionList/record/supplyChain`,
params,
})
}
// 单次制裁-深度挖掘-制裁实体股权信息
/**
* @param {Object} params
* @param {Integer} params.orgId - 组织ID
* @param {Boolean} params.rule - 是否遵循规则
* @header token
*/
export function getSingleSanctionEntityEquity(params) {
return request({
method: 'GET',
url: `/api/sanctionList/record/shareholding`,
params,
})
}
// 单次制裁-影响分析-企业规模-营收
/**
* @param {Object} params
* @param {Integer} params.id - 机构ID
* @header token
*/
export function getSingleSanctionEntityRevenue(params) {
return request({
method: 'GET',
url: `/api/organization/scale/revenue`,
params,
})
}
// 单次制裁-影响分析-企业规模-净利润
/**
* @param {Object} params
* @param {Integer} params.id - 机构ID
* @header token
*/
export function getSingleSanctionEntityNetProfit(params) {
return request({
method: 'GET',
url: `/api/organization/scale/netProfit`,
params,
})
}
// 单次制裁-影响分析-企业规模-人员
/**
* @param {Object} params
* @param {Integer} params.id - 机构ID
* @header token
*/
export function getSingleSanctionEntityPersonnel(params) {
return request({
method: 'GET',
url: `/api/organization/scale/personnel`,
params,
})
}
// 单次制裁-影响分析-企业市值变化
/**
* @param {Object} params
* @param {Integer} params.id - 机构ID
* @header token
*/
export function getSingleSanctionEntityMarketValue(params) {
return request({
method: 'GET',
url: `/api/organization/marketValue`,
params,
})
}
// 单次制裁-影响分析-企业研发投入
/**
* @param {Object} params
* @param {Integer} params.id - 机构ID
* @header token
*/
export function getSingleSanctionEntityRDInvestment(params) {
return request({
method: 'GET',
url: `/api/organization/rdInvestment`,
params,
})
}
// 单次制裁-影响分析-企业市场占比
/**
* @param {Object} params
* @param {Integer} params.id - 机构ID
* @header token
*/
export function getSingleSanctionEntityMarketShare(params) {
return request({
method: 'GET',
url: `/api/organization/marketShare`,
params,
})
}
// 单次制裁-影响分析-科研仪器对美依赖情况
/**
* @param {Object} params
* @param {Integer} params.id - 机构ID
* @header token
*/
export function getSingleSanctionEntityRDInstrumentDependency(params) {
return request({
method: 'GET',
url: `/api/organization/instrument/getDependencyUS`,
params,
})
}
// 单次制裁-影响分析-科研仪器进口国分布
/**
* @param {Object} params
* @param {Integer} params.id - 机构ID
* @header token
*/
export function getSingleSanctionEntityRDInstrumentImportCountry(params) {
return request({
method: 'GET',
url: `/api/organization/instrument/getOriginCount`,
params,
})
}
// 单次制裁-影响分析-新增国际合作项目
/**
* @param {Object} params
* @param {Integer} params.id - 机构ID
* @param {String} params.domainId - 科技领域ID
* @header token
*/
export function getSingleSanctionEntityInternationalCooperation(params) {
return request({
method: 'GET',
url: `/api/organization/project/icCount`,
params,
})
}
// 单次制裁-影响分析-新增国际合作论文
/**
* @param {Object} params
* @param {Integer} params.id - 机构ID
* @param {String} params.domainId - 科技领域ID
* @header token
*/
export function getSingleSanctionEntityInternationalPaper(params) {
return request({
method: 'GET',
url: `/api/organization/paper/icCount`,
params,
})
} }
\ No newline at end of file
...@@ -741,26 +741,8 @@ import listIcon from "./assets/images/icon-list.png"; ...@@ -741,26 +741,8 @@ 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 comTitle from "./assets/images/panel1_1.png";
import newsImg from "@/assets/images/news-img.png";
import newsImg1 from "@/assets/images/newsImg1.png";
import getMultiLineChart from "./utils/multiLineChart"; import getMultiLineChart from "./utils/multiLineChart";
import bill1 from "./assets/images/bill1.png";
import bill2 from "./assets/images/bill2.png";
import bill3 from "./assets/images/bill3.png";
import bill4 from "./assets/images/bill4.png";
import bill5 from "./assets/images/bill5.png";
import bill6 from "./assets/images/bill6.png";
import bill7 from "./assets/images/bill7.png";
import bill8 from "./assets/images/bill8.png";
import bill9 from "./assets/images/bill9.png";
import bill10 from "./assets/images/bill10.png";
import bill11 from "./assets/images/bill11.png";
import bill12 from "./assets/images/bill12.png";
import icon01 from "./assets/images/jianzhu.png"; import icon01 from "./assets/images/jianzhu.png";
import { import {
getEntitiesDataCount, getEntitiesDataCount,
getEntitiesDataInfo, getEntitiesDataInfo,
...@@ -795,7 +777,7 @@ const techOptions = [ ...@@ -795,7 +777,7 @@ const techOptions = [
{ label: "太空", value: 13 }, { label: "太空", value: 13 },
{ label: "核", value: 14 } { label: "核", value: 14 }
]; ];
const timeOptions = ["全部时间", "2025年", "2024年", "2023年", "2022年", "2021年"]; const timeOptions = ["全部时间", "2025年", "2024年", "2023年", "2022年", "2021年", "2020年", "2019年", "2018年", "2017年", "2016年", "2015年", "2014年", "2013年", "2012年", "2011年", "2010年", "2009年", "2008年", "2007年", "2006年", "2005年", "2004年", "2003年", "2002年", "2001年"];
const checkedTech = ref([0]); const checkedTech = ref([0]);
const checkedTime = ref(["全部时间"]); const checkedTime = ref(["全部时间"]);
...@@ -1246,24 +1228,30 @@ const fetchSanctionList = async () => { ...@@ -1246,24 +1228,30 @@ const fetchSanctionList = async () => {
const params = { const params = {
pageNum: currentPageAll.value, pageNum: currentPageAll.value,
pageSize: pageSizeAll.value, pageSize: pageSizeAll.value,
techDomains: techDomains, techDomainIds: techDomains,
years: years, years: years,
isCn: false, isCn: false,
typeName: "" typeName: "实体清单"
}; };
const res = await getExportControlList(params); const res = await getExportControlList(params);
if (res && res.content) { if (res && res.content) {
sanctionList.value = res.content.map(item => { sanctionList.value = res.content.map(item => {
const tags = Array.isArray(item.techDomains) ? item.techDomains : item.techDomain ? [item.techDomain] : []; const tags = Array.isArray(item.techDomains) ? item.techDomains : item.techDomain ? [item.techDomain] : item.techDomainList || [];
const fullTime = item.startTime ? formatAnyDateToChinese(item.startTime) : item.publishDate || item.date; const fullTime = item.startTime ? formatAnyDateToChinese(item.startTime) : item.postDate || item.publishDate || item.date;
let year = ""; let year = "";
let dateStr = fullTime; let dateStr = fullTime;
if (typeof fullTime === "string" && fullTime.includes("年")) { if (typeof fullTime === "string") {
const parts = fullTime.split("年"); if (fullTime.includes("年")) {
year = parts[0]; const parts = fullTime.split("年");
dateStr = parts[1].replace(/\s+/g, ""); year = parts[0];
dateStr = parts[1].replace(/\s+/g, "");
} else if (fullTime.includes("-")) {
const parts = fullTime.split("-");
year = parts[0];
dateStr = parts.slice(1).join("-");
}
} }
return { return {
...@@ -1274,7 +1262,7 @@ const fetchSanctionList = async () => { ...@@ -1274,7 +1262,7 @@ const fetchSanctionList = async () => {
title: item.entityNameZh || item.entityName || item.title || item.name, title: item.entityNameZh || item.entityName || item.title || item.name,
desc: item.sanReason || item.description || item.summary || item.content, desc: item.sanReason || item.description || item.summary || item.content,
tags: tags, tags: tags,
countTag: item.ruleOrgCount ? `${item.ruleOrgCount}家关联实体` : item.countTag || "" countTag: item.cnEntityCount ? `${item.cnEntityCount}家中国实体` : item.ruleOrgCount ? `${item.ruleOrgCount}家关联实体` : item.countTag || ""
}; };
}); });
totalAll.value = res.totalElements; totalAll.value = res.totalElements;
...@@ -1456,12 +1444,14 @@ const fetchSocialMediaInfo = async () => { ...@@ -1456,12 +1444,14 @@ const fetchSocialMediaInfo = async () => {
try { try {
const data = await getSocialMediaInfo(); const data = await getSocialMediaInfo();
if (data && Array.isArray(data)) { if (data && Array.isArray(data)) {
// console.log(data);
socialMediaList.value = data.map(item => ({ socialMediaList.value = data.map(item => ({
avatar: item.personImage, avatar: item.personImage,
name: item.personName, name: item.personName,
time: formatTime(item.time), time: formatTime(item.time),
source: item.orgName, source: item.orgName,
content: item.remarks content: item.remarks,
personId: item.personId
})); }));
} }
} catch (err) { } catch (err) {
...@@ -1493,7 +1483,8 @@ const handlePerClick = item => { ...@@ -1493,7 +1483,8 @@ const handlePerClick = item => {
const route = router.resolve({ const route = router.resolve({
path: "/characterPage", path: "/characterPage",
query: { query: {
type: item.type || [1, 2, 3][Math.floor(Math.random() * 3)] type: item.type || [1, 2, 3][Math.floor(Math.random() * 3)],
personId: item.personId
} }
}); });
window.open(route.href, "_blank"); window.open(route.href, "_blank");
...@@ -1661,7 +1652,7 @@ const handleClose = () => { ...@@ -1661,7 +1652,7 @@ const handleClose = () => {
dialogVisible.value = false; dialogVisible.value = false;
}; };
const handleOrgClick = item => { const handleOrgClick = item => {
console.log(item, item.name); // console.log(item, item.name);
currentOrgList.value = item.ruleOrgList; currentOrgList.value = item.ruleOrgList;
dialogVisible.value = true; dialogVisible.value = true;
}; };
...@@ -1672,7 +1663,7 @@ const handleMediaClose = () => { ...@@ -1672,7 +1663,7 @@ const handleMediaClose = () => {
mediaVisible.value = false; mediaVisible.value = false;
}; };
const handleMediaClick = item => { const handleMediaClick = item => {
console.log(item, item.name); // console.log(item, item.name);
currentMedia.value = item.content; currentMedia.value = item.content;
mediaVisible.value = true; mediaVisible.value = true;
}; };
...@@ -3070,6 +3061,7 @@ const handleMediaClick = item => { ...@@ -3070,6 +3061,7 @@ const handleMediaClick = item => {
padding-top: 14px; padding-top: 14px;
position: relative; position: relative;
.main-title { .main-title {
width: 800px;
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
line-height: 26px; line-height: 26px;
......
...@@ -17,27 +17,19 @@ ...@@ -17,27 +17,19 @@
<div class="left-top-main-content"> <div class="left-top-main-content">
<div class="content-item"> <div class="content-item">
<span class="label">常见列入原因:</span> <span class="label">常见列入原因:</span>
<span class="text" <span class="text">{{ entityInfo.commonReason }}</span>
>{{ entityInfo.commonReason }}</span
>
</div> </div>
<div class="content-item"> <div class="content-item">
<span class="label">核心限制措施:</span> <span class="label">核心限制措施:</span>
<span class="text" <span class="text">{{ entityInfo.restrictiveMeasure }}</span>
>{{ entityInfo.restrictiveMeasure }}</span
>
</div> </div>
<div class="content-item"> <div class="content-item">
<span class="label">负责机构:</span> <span class="label">负责机构:</span>
<span class="text" <span class="text">{{ entityInfo.responsibleOrganization }}</span>
>{{ entityInfo.responsibleOrganization }}</span
>
</div> </div>
<div class="content-item"> <div class="content-item">
<span class="label">移除机制:</span> <span class="label">移除机制:</span>
<span class="text" <span class="text">{{ entityInfo.removalMechanism }}</span>
>{{ entityInfo.removalMechanism }}</span
>
</div> </div>
</div> </div>
</div> </div>
...@@ -46,17 +38,16 @@ ...@@ -46,17 +38,16 @@
<div class="title"> <div class="title">
<div class="box"></div> <div class="box"></div>
<div class="text">实体清单更新历史</div> <div class="text">实体清单更新历史</div>
<div class="filters"> <div class="filters">
<el-select v-model="selectedDomain" placeholder="Select" style="width: 150px; height: 32px; margin-right: 16px"> <el-select
<el-option v-model="selectedDomain"
v-for="item in domainOptions" placeholder="Select"
:key="item.value" style="width: 150px; height: 32px; margin-right: 16px"
:label="item.label" >
:value="item.value" <el-option v-for="item in domainOptions" :key="item.value" :label="item.label" :value="item.value" />
/> </el-select>
</el-select> <el-checkbox v-model="onlyChina">只看涉华动态</el-checkbox>
<el-checkbox v-model="onlyChina">只看涉华动态</el-checkbox> </div>
</div>
<div class="btn"> <div class="btn">
<img src="../../../../assets/下载按钮.png" alt="" /> <img src="../../../../assets/下载按钮.png" alt="" />
<img src="../../../../assets/收藏按钮.png" alt="" /> <img src="../../../../assets/收藏按钮.png" alt="" />
...@@ -70,7 +61,7 @@ ...@@ -70,7 +61,7 @@
</div> </div>
<img :src="item.icon || title" alt="" /> <img :src="item.icon || title" alt="" />
<div class="main"> <div class="main">
<div class="main-title">{{ item.name }}</div> <div class="main-title" @click="handleClick(item)">{{ item.name }}</div>
<el-tooltip <el-tooltip
effect="dark" effect="dark"
:content="item.summary" :content="item.summary"
...@@ -84,7 +75,9 @@ ...@@ -84,7 +75,9 @@
<div v-for="tag in item.techDomainList" :key="tag" class="tag-item">{{ tag }}</div> <div v-for="tag in item.techDomainList" :key="tag" class="tag-item">{{ tag }}</div>
</div> </div>
<div :class="{'count-tag': item.cnEntityCount}">{{ item.cnEntityCount ? `${item.cnEntityCount}家中国实体` : '' }}</div> <div :class="{ 'count-tag': item.cnEntityCount }">
{{ item.cnEntityCount ? `${item.cnEntityCount}家中国实体` : "" }}
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -111,84 +104,114 @@ ...@@ -111,84 +104,114 @@
</div> </div>
</div> </div>
<div class="right-main"> <div class="right-main">
<div class="right-main-title"> <div class="right-main-title">
<img :src="publishInfo.imageUrl" alt=""> <img :src="publishInfo.imageUrl" alt="" />
<div> <div>
<div class="title-text">{{publishInfo.orgNameZh}} ></div> <div class="title-text">{{ publishInfo.orgNameZh }} ></div>
<div class="title-entext">{{publishInfo.orgName}}</div> <div class="title-entext">{{ publishInfo.orgName }}</div>
</div> </div>
</div> </div>
<!-- 关键人物 --> <!-- 关键人物 -->
<div class="right-main-key-person"> <div class="right-main-key-person">
<div class="key-person-title"> <div class="key-person-title">
<img :src="icon01" alt=""> <img :src="icon01" alt="" />
<span>关键人物</span> <span>关键人物</span>
</div> </div>
<div class="key-person-list"> <div class="key-person-list">
<div class="person-item" v-for="(item, index) in publishInfo.personList" :key="index"> <div
<img :src="item.imageUrl" alt=""> class="person-item"
<div class="person-info"> v-for="(item, index) in publishInfo.personList"
<div class="name">{{ item.name }}</div> :key="index"
<div class="title1">{{ item.position }}</div> @click="handlePerClick(item)"
</div> >
</div> <img :src="item.imageUrl" alt="" />
</div> <div class="person-info">
</div> <div class="name">{{ item.name }}</div>
<!-- 机构动态 --> <div class="title1">{{ item.position }}</div>
<div class="right-main-dynamic"> </div>
<div class="dynamic-title"> </div>
<img :src="icon02" alt=""> </div>
<span>机构动态</span> </div>
</div> <!-- 机构动态 -->
<div class="dynamic-list"> <div class="right-main-dynamic">
<div class="dynamic-item" v-for="(item, index) in publishOrgInfo" :key="item.newsId"> <div class="dynamic-title">
<div class="dot-line"> <img :src="icon02" alt="" />
<div class="dot"></div> <span>机构动态</span>
<div class="line" v-if="index !== publishOrgInfo.length - 1"></div> </div>
</div> <div class="dynamic-list">
<div class="content-box"> <div class="dynamic-item" v-for="(item, index) in publishOrgInfo" :key="item.newsId">
<div class="date">{{ item.publishDate }}</div> <div class="dot-line">
<div class="text">{{ item.newsContent }}</div> <div class="dot"></div>
</div> <div class="line" v-if="index !== publishOrgInfo.length - 1"></div>
</div> </div>
</div> <div class="content-box">
<div class="more-btn" v-if="publishOrgInfo.length < dynamicTotal" @click="handleLoadMoreDynamic"> <div class="date">{{ item.publishDate }}</div>
<span>查看更多</span> <div class="text">{{ item.newsContent }}</div>
<el-icon><ArrowDown /></el-icon> </div>
</div> </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>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted, watch } from "vue"; import { ref, onMounted, watch } from "vue";
import title from "../../../../assets/title.png" import router from "@/router";
import defaultIcon from "../../../../../assets/icons/default-avatar.png" import title from "../../../../assets/title.png";
import icon01 from "../../assets/icon01.png" import defaultIcon from "../../../../../assets/icons/default-avatar.png";
import icon02 from "../../assets/icon02.png" import icon01 from "../../assets/icon01.png";
import { ArrowDown } from '@element-plus/icons-vue' import icon02 from "../../assets/icon02.png";
import { getEntityInfo, getPublishInfo, getPublishOrgInfo, getEntityUpdateInfo } from "@/api/exportControlV2.0.js" import { ArrowDown } from "@element-plus/icons-vue";
import { getEntityInfo, getPublishInfo, getPublishOrgInfo, getEntityUpdateInfo } from "@/api/exportControlV2.0.js";
// 跳转到详情页
const handleClick = item => {
const route = router.resolve({
path: "/exportControl/singleSanction",
query: {
id: item.id
}
});
window.open(route.href, "_blank");
};
// 处理点击关键人物的方法
const handlePerClick = item => {
console.log("点击了关键人物:", item);
const route = router.resolve({
path: "/characterPage",
query: {
type: item.type,
personId: item.id
}
});
window.open(route.href, "_blank");
};
const selectedDomain = ref(0); const selectedDomain = ref(0);
const onlyChina = ref(false); const onlyChina = ref(false);
const domainOptions = [ const domainOptions = [
{ label: '全部领域', value: 0 }, { label: "全部领域", value: 0 },
{ label: '人工智能', value: 1 }, { label: "人工智能", value: 1 },
{ label: '生物科技', value: 2 }, { label: "生物科技", value: 2 },
{ label: '新一代信息技术', value: 3 }, { label: "新一代信息技术", value: 3 },
{ label: '量子科技', value: 4 }, { label: "量子科技", value: 4 },
{ label: '新能源', value: 5 }, { label: "新能源", value: 5 },
{ label: '集成电路', value: 6 }, { label: "集成电路", value: 6 },
{ label: '海洋', value: 7 }, { label: "海洋", value: 7 },
{ label: '先进制造', value: 8 }, { label: "先进制造", value: 8 },
{ label: '新材料', value: 9 }, { label: "新材料", value: 9 },
{ label: '航空航天', value: 10 }, { label: "航空航天", value: 10 },
{ label: '深海', value: 11 }, { label: "深海", value: 11 },
{ label: '极地', value: 12 }, { label: "极地", value: 12 },
{ label: '太空', value: 13 }, { label: "太空", value: 13 },
{ label: '核', value: 14 } { label: "核", value: 14 }
]; ];
// const keyPersonList = [ // const keyPersonList = [
...@@ -227,122 +250,119 @@ const getSanctionUpdate = async () => { ...@@ -227,122 +250,119 @@ const getSanctionUpdate = async () => {
typeName: "实体清单", typeName: "实体清单",
pageNum: currentPageAll.value, pageNum: currentPageAll.value,
pageSize: pageSizeAll.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);
} }
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], () => { watch([selectedDomain, onlyChina], () => {
currentPageAll.value = 1 currentPageAll.value = 1;
getSanctionUpdate() getSanctionUpdate();
}) });
const handlePageChangeAll = val => { const handlePageChangeAll = val => {
currentPageAll.value = val currentPageAll.value = val;
getSanctionUpdate() getSanctionUpdate();
}; };
// 获取实体清单发布机构 // 获取实体清单发布机构
const publishInfo = ref({}) const publishInfo = ref({});
const getPublishInfoFn = async () => { const getPublishInfoFn = async () => {
const params = { const params = {
sanTypeId: 1 // 实体清单固定1 sanTypeId: 1 // 实体清单固定1
} };
try { try {
const res = await getPublishInfo(params) const res = await getPublishInfo(params);
if (res && res.code === 200) { if (res && res.code === 200) {
publishInfo.value = res.data publishInfo.value = res.data;
// 获取发布机构动态 // 获取发布机构动态
getPublishOrgInfoFn() getPublishOrgInfoFn();
} }
} catch (error) { } catch (error) {
console.error("获取实体清单发布机构失败:", error) console.error("获取实体清单发布机构失败:", error);
} }
} };
// 获取发布机构动态 // 获取发布机构动态
const publishOrgInfo = ref([]) const publishOrgInfo = ref([]);
const dynamicPage = ref(1) const dynamicPage = ref(1);
const dynamicPageSize = ref(3) const dynamicPageSize = ref(3);
const dynamicTotal = ref(0) const dynamicTotal = ref(0);
const getPublishOrgInfoFn = async (isLoadMore = false) => { const getPublishOrgInfoFn = async (isLoadMore = false) => {
if (publishInfo.value && !publishInfo.value.id) return; if (publishInfo.value && !publishInfo.value.id) return;
const params = { const params = {
orgId: publishInfo.value.id, orgId: publishInfo.value.id,
pageNum: dynamicPage.value, pageNum: dynamicPage.value,
pageSize: dynamicPageSize.value, pageSize: dynamicPageSize.value
} };
try { try {
const res = await getPublishOrgInfo(params) const res = await getPublishOrgInfo(params);
if (res && res.code === 200) { if (res && res.code === 200) {
const newRows = (res.data.content || []).map(item => ({ const newRows = (res.data.content || []).map(item => ({
...item, ...item,
publishDate: item.newsDate, publishDate: item.newsDate,
title: item.newsTitle title: item.newsTitle
})) }));
if (isLoadMore) { if (isLoadMore) {
publishOrgInfo.value = [...publishOrgInfo.value, ...newRows] publishOrgInfo.value = [...publishOrgInfo.value, ...newRows];
} else { } else {
publishOrgInfo.value = newRows publishOrgInfo.value = newRows;
} }
dynamicTotal.value = res.data.totalElements || 0 dynamicTotal.value = res.data.totalElements || 0;
} }
} catch (error) { } catch (error) {
console.error("获取发布机构动态失败:", error) console.error("获取发布机构动态失败:", error);
} }
} };
const handleLoadMoreDynamic = () => { const handleLoadMoreDynamic = () => {
dynamicPage.value++ dynamicPage.value++;
getPublishOrgInfoFn(true) getPublishOrgInfoFn(true);
} };
// 获取实体清单基本信息 // 获取实体清单基本信息
const entityInfo = ref({}) const entityInfo = ref({});
const getEntityInfoFn = async () => { const getEntityInfoFn = async () => {
try { try {
const res = await getEntityInfo() const res = await getEntityInfo();
if (res && res.code === 200) { if (res && res.code === 200) {
entityInfo.value = res.data entityInfo.value = res.data;
} }
} catch (error) { } catch (error) {
console.error("获取实体清单基本信息失败:", error) console.error("获取实体清单基本信息失败:", error);
} }
} };
onMounted(() => { onMounted(() => {
// 获取实体清单基本信息 // 获取实体清单基本信息
getEntityInfoFn() getEntityInfoFn();
// 获取实体清单发布机构 // 获取实体清单发布机构
getPublishInfoFn() getPublishInfoFn();
// 获取实体清单更新历史 // 获取实体清单更新历史
getSanctionUpdate() getSanctionUpdate();
}) });
</script> </script>
<style scoped lang="scss">
<style scoped lang="scss"> * {
* { margin: 0;
margin: 0;
padding: 0; padding: 0;
} }
.introduction-page { .introduction-page {
...@@ -406,18 +426,18 @@ onMounted(() => { ...@@ -406,18 +426,18 @@ onMounted(() => {
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background-color: #fff; background-color: #fff;
.title { .title {
border-bottom: 1px solid rgb(234, 236, 238); border-bottom: 1px solid rgb(234, 236, 238);
.filters { .filters {
margin-left: auto; margin-left: auto;
display: flex; display: flex;
align-items: center; align-items: center;
margin-right: 20px; margin-right: 20px;
} }
.btn { .btn {
margin-left: 0; margin-left: 0;
} }
} }
.left-bottom-main { .left-bottom-main {
padding: 16px 42px 0 25px; padding: 16px 42px 0 25px;
.sanction-list { .sanction-list {
...@@ -436,12 +456,12 @@ onMounted(() => { ...@@ -436,12 +456,12 @@ onMounted(() => {
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
font-weight: 700; font-weight: 700;
.year { .year {
font-size: 16px; font-size: 16px;
line-height: 24px; line-height: 24px;
} }
.date { .date {
font-size: 16px; font-size: 16px;
line-height: 24px; line-height: 24px;
...@@ -459,6 +479,7 @@ onMounted(() => { ...@@ -459,6 +479,7 @@ onMounted(() => {
padding-top: 14px; padding-top: 14px;
position: relative; position: relative;
.main-title { .main-title {
cursor: pointer;
width: 700px; width: 700px;
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
...@@ -531,210 +552,210 @@ onMounted(() => { ...@@ -531,210 +552,210 @@ onMounted(() => {
} }
.right { .right {
width: 520px; width: 520px;
height: 941px; height: 1020px;
border-radius: 10px; border-radius: 10px;
padding-bottom: 20px; padding-bottom: 20px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background-color: #fff; background-color: #fff;
.right-main { .right-main {
padding: 7px 24px 0px 23px; padding: 7px 24px 0px 23px;
.right-main-title { .right-main-title {
width: 473px; width: 473px;
height: 110px; height: auto;
border-radius: 4px; border-radius: 4px;
background-color: rgb(247, 248, 249); background-color: rgb(247, 248, 249);
border: 1px solid rgb(234, 236, 238); border: 1px solid rgb(234, 236, 238);
display: flex; display: flex;
margin-bottom: 20px; margin-bottom: 20px;
padding: 16px; padding: 16px;
img { img {
width: 64px; width: 64px;
height: 64px; height: 64px;
margin-right: 14px; margin-right: 14px;
} }
.title-text { .title-text {
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
line-height: 26px; line-height: 26px;
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
margin-bottom: 6px; margin-bottom: 6px;
} }
.title-entext { .title-entext {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
line-height: 24px; line-height: 24px;
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
color: rgb(95, 101, 108); color: rgb(95, 101, 108);
} }
} }
.right-main-key-person { .right-main-key-person {
width: 100%; width: 100%;
padding-bottom: 20px; padding-bottom: 20px;
border-bottom: 1px solid rgb(234, 236, 238); border-bottom: 1px solid rgb(234, 236, 238);
margin-bottom: 18px; margin-bottom: 18px;
.key-person-title { .key-person-title {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 19px; margin-bottom: 19px;
img { img {
width: 14px; width: 14px;
height: 14px; height: 14px;
margin-right: 12px; margin-right: 12px;
} }
span { span {
font-size: 18px; font-size: 18px;
font-weight: 700; font-weight: 700;
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
} }
} }
.key-person-list { .key-person-list {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
.person-item { .person-item {
width: 185px; width: 185px;
// height: 49px; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 16px; margin-bottom: 16px;
&:nth-child(2n-1) { &:nth-child(2n-1) {
margin-left: 28px; margin-left: 28px;
margin-right: 39px; margin-right: 39px;
} }
img { img {
width: 48px; width: 48px;
height: 48px; height: 48px;
border-radius: 50%; border-radius: 50%;
margin-right: 8px; margin-right: 8px;
object-fit: cover; object-fit: cover;
flex-shrink: 0; flex-shrink: 0;
} }
.person-info { .person-info {
.name { .name {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
margin-bottom: 1px; margin-bottom: 1px;
white-space: nowrap; white-space: nowrap;
} }
.title1 { .title1 {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
color: rgb(95, 101, 108); color: rgb(95, 101, 108);
line-height: 1.2; line-height: 1.2;
// white-space: nowrap; // white-space: nowrap;
} }
} }
} }
} }
} }
.right-main-dynamic { .right-main-dynamic {
width: 100%; width: 100%;
.dynamic-title { .dynamic-title {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 19px; margin-bottom: 19px;
img { img {
width: 14px; width: 14px;
height: 14px; height: 14px;
margin-right: 12px; margin-right: 12px;
} }
span { span {
font-size: 18px; font-size: 18px;
font-weight: 700; font-weight: 700;
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
} }
} }
.dynamic-list { .dynamic-list {
max-height: 500px; max-height: 500px;
overflow-y: auto; overflow-y: auto;
padding-right: 10px; padding-right: 10px;
/* 滚动条样式 */ /* 滚动条样式 */
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 6px; width: 6px;
} }
&::-webkit-scrollbar-track { &::-webkit-scrollbar-track {
background: #f1f1f1; background: #f1f1f1;
border-radius: 3px; border-radius: 3px;
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
background: #c1c1c1; background: #c1c1c1;
border-radius: 3px; border-radius: 3px;
&:hover { &:hover {
background: #a8a8a8; 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;
.dynamic-item { span {
display: flex; margin-right: 4px;
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 { .title {
...@@ -784,11 +805,11 @@ onMounted(() => { ...@@ -784,11 +805,11 @@ onMounted(() => {
line-height: 30px !important; line-height: 30px !important;
color: #fff !important; color: #fff !important;
border: none !important; border: none !important;
max-width: 500px !important; max-width: 500px !important;
} }
.common-prompt-popper.el-popper .el-popper__arrow::before { .common-prompt-popper.el-popper .el-popper__arrow::before {
background-color: rgb(59, 65, 75) !important; background-color: rgb(59, 65, 75) !important;
border-color: rgb(59, 65, 75) !important; border-color: rgb(59, 65, 75) !important;
} }
</style> </style>
\ No newline at end of file
...@@ -94,6 +94,10 @@ const props = defineProps({ ...@@ -94,6 +94,10 @@ const props = defineProps({
totalCount: { totalCount: {
type: Number, type: Number,
default: 0 default: 0
},
dataList: {
type: Array,
default: () => []
} }
}); });
...@@ -107,92 +111,21 @@ const visible = computed({ ...@@ -107,92 +111,21 @@ const visible = computed({
const currentPage = ref(1); const currentPage = ref(1);
const pageSize = ref(10); const pageSize = ref(10);
// Mock Data const tableData = computed(() => {
const tableData = ref([ const start = (currentPage.value - 1) * pageSize.value;
{ const end = start + pageSize.value;
id: 1, return props.dataList.slice(start, end).map(item => ({
name: "讯飞智元信息科技有限公司", ...item,
domains: ["人工智能"], name: item.orgName,
equityRatio: "100%", domains: item.techDomains || [],
location: "深圳", equityRatio: item.equityRatio ? (item.equityRatio * 100).toFixed(2) + '%' : '--',
revenue: 325 location: '--',
}, revenue: item.revenue || '--'
{ }));
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 => { const handleCurrentChange = val => {
console.log("Page changed:", val); currentPage.value = val;
}; };
const tableRowClassName = ({ rowIndex }) => { const tableRowClassName = ({ rowIndex }) => {
......
...@@ -104,8 +104,8 @@ ...@@ -104,8 +104,8 @@
<el-table-column label="50%规则子企业" min-width="280" show-overflow-tooltip align="right"> <el-table-column label="50%规则子企业" min-width="280" show-overflow-tooltip align="right">
<template #default="{ row }"> <template #default="{ row }">
<div class="rule-cell" v-if="row.ruleOrgCount > 0"> <div class="rule-cell" v-if="row.ruleOrgCount > 0">
<div class="rule-text" :title="row.ruleOrgList && row.ruleOrgList.length > 0 ? row.ruleOrgList[0].orgName : ''"> <div class="rule-text" :title="row.ruleOrgList?.[0]?.orgName || ''">
{{ row.ruleOrgList && row.ruleOrgList.length > 0 ? row.ruleOrgList[0].orgName : '' }}...等 {{ row.ruleOrgList?.[0]?.orgName || '' }}...等
</div> </div>
<el-link class="rule-link" type="primary" :underline="false" @click="handleRuleClick(row)">{{ row.ruleOrgCount }}家 ></el-link> <el-link class="rule-link" type="primary" :underline="false" @click="handleRuleClick(row)">{{ row.ruleOrgCount }}家 ></el-link>
</div> </div>
...@@ -132,6 +132,7 @@ ...@@ -132,6 +132,7 @@
v-model="ruleDialogVisible" v-model="ruleDialogVisible"
:company-name="currentRuleCompany" :company-name="currentRuleCompany"
:total-count="currentRuleCount" :total-count="currentRuleCount"
:data-list="currentRuleList"
/> />
</template> </template>
...@@ -227,10 +228,12 @@ const customDateRange = ref(""); ...@@ -227,10 +228,12 @@ const customDateRange = ref("");
const ruleDialogVisible = ref(false); const ruleDialogVisible = ref(false);
const currentRuleCompany = ref(""); const currentRuleCompany = ref("");
const currentRuleCount = ref(0); const currentRuleCount = ref(0);
const currentRuleList = ref([]);
const handleRuleClick = (row) => { const handleRuleClick = (row) => {
currentRuleCompany.value = row.entityName; currentRuleCompany.value = row.entityNameZh || row.entityName;
currentRuleCount.value = row.ruleOrgCount; currentRuleCount.value = row.ruleOrgCount;
currentRuleList.value = row.ruleOrgList || [];
ruleDialogVisible.value = true; ruleDialogVisible.value = true;
}; };
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<div class="title">中国实体数量</div> <div class="title">中国实体数量</div>
</div> </div>
<div class="number"> <div class="number">
<span class="num">24</span> <span class="num">{{ totalCount.cnCount }}</span>
<span class="unit"></span> <span class="unit"></span>
</div> </div>
</div> </div>
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
<div class="subtitle">50%规则涉及</div> <div class="subtitle">50%规则涉及</div>
</div> </div>
<div class="number"> <div class="number">
<span class="num">102</span> <span class="num">{{ totalCount.cn50RuleCount }}</span>
<span class="unit"></span> <span class="unit"></span>
</div> </div>
</div> </div>
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
<div class="title">涉及领域</div> <div class="title">涉及领域</div>
</div> </div>
<div class="number"> <div class="number">
<span class="num">3</span> <span class="num">{{ totalCount.techDomainCount }}</span>
<span class="unit"></span> <span class="unit"></span>
</div> </div>
</div> </div>
...@@ -154,16 +154,16 @@ ...@@ -154,16 +154,16 @@
<div class="map-wrapper"> <div class="map-wrapper">
<div class="map-chart" ref="mapChartRef"></div> <div class="map-chart" ref="mapChartRef"></div>
<div class="rank-list"> <div class="rank-list">
<div class="rank-item" v-for="(item, index) in rankData" :key="index"> <div class="rank-item" v-for="(item, index) in regionDistribution" :key="index">
<div class="rank-index" :class="'rank-' + (index + 1)">{{ index + 1 }}</div> <div class="rank-index" :class="'rank-' + (index + 1)">{{ index + 1 }}</div>
<div class="rank-name">{{ item.name }}</div> <div class="rank-name">{{ item.name }}</div>
<div class="rank-bar-bg"> <div class="rank-bar-bg">
<div <div
class="rank-bar-fill" class="rank-bar-fill"
:style="{ width: (item.value / 50) * 100 + '%', background: getBarColor(index) }" :style="{ width: (maxRegionCount > 0 ? (item.count / maxRegionCount) * 100 : 0) + '%', background: getBarColor(index) }"
></div> ></div>
</div> </div>
<div class="rank-value">{{ item.value }}</div> <div class="rank-value">{{ item.count }}</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -184,108 +184,210 @@ ...@@ -184,108 +184,210 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { ref, onMounted, watch } from "vue";
import * as echarts from "echarts"; import * as echarts from "echarts";
import chinaJson from "../../../utils/China.json"; import chinaJson from "../../../utils/China.json";
import ai from "./assets/ai.png"; import ai from "./assets/ai.png";
import right from "./assets/right.png"; import right from "./assets/right.png";
import flag from "../../assets/default-icon2.png" import flag from "../../assets/default-icon2.png"
import { useRouter } from "vue-router";
import { getSingleSanctionTotalCount, getSingleSanctionDomainCount, getSingleSanctionEntityTypeCount, getSingleSanctionEntityCountryCount, getSingleSanctionEntityRegionCount } from "@/api/exportControlV2.0";
// 单次制裁-数据统计-制裁实体地域分布情况
const regionDistribution = ref([]);
const maxRegionCount = ref(0);
// 单次制裁-数据统计-制裁实体地域分布情况-请求
const getRegionData = async () => {
if (!sanRecordId.value) return;
try {
const params = { sanRecordId: sanRecordId.value };
if (regionTime.value && regionTime.value !== 'all') {
params.startDate = `${regionTime.value}-01-01`;
params.endDate = `${regionTime.value}-12-31`;
}
const res = await getSingleSanctionEntityRegionCount(params);
if (res.code === 200) {
regionDistribution.value = res.data || [];
maxRegionCount.value = Math.max(...regionDistribution.value.map(item => item.count), 0);
initMapChart();
}
} catch (error) {
console.log(error);
}
}
// 单次制裁-数据统计-制裁实体国家分布情况
const countryDistribution = ref([]);
// 单次制裁-数据统计-制裁实体国家分布情况-请求
const getCountryCount = async () => {
if (!sanRecordId.value) return;
try {
const params = { sanRecordId: sanRecordId.value };
const res = await getSingleSanctionEntityCountryCount(params);
if (res.code === 200) {
const rawData = res.data || [];
// 找出最大值用于计算比例
const maxCount = Math.max(...rawData.map(item => item.count), 0);
countryDistribution.value = rawData.map((item, index) => {
// 计算宽度,最大值对应 80%
const width = maxCount > 0 ? (item.count / maxCount) * 80 + '%' : '0%';
// 根据索引分配渐变色
let gradient = "";
if (index === 0) {
gradient = "linear-gradient(90deg, rgba(205, 66, 70, 0) 0%, rgba(205, 66, 70, 1) 100%)";
} else if (index === 1 || index === 2) {
gradient = "linear-gradient(90deg, rgba(255, 172, 77, 0) 0%, rgba(255, 172, 77, 1) 100%)";
} else {
gradient = "linear-gradient(90deg, rgba(5, 95, 194, 0) 0%, rgba(5, 95, 194, 1) 100%)";
}
return {
...item,
width,
gradient
};
});
}
} catch (error) {
console.log(error);
}
}
// 单次制裁-数据统计-制裁实体类型分布情况
const entityTypeCount = ref([]);
// 单次制裁-数据统计-制裁实体类型分布情况-请求
const getEntityTypeCount = async () => {
if (!sanRecordId.value) return;
try {
const params = { sanRecordId: sanRecordId.value };
if (typeTime.value && typeTime.value !== 'all') {
params.startDate = `${typeTime.value}-01-01`;
params.endDate = `${typeTime.value}-12-31`;
}
const res = await getSingleSanctionEntityTypeCount(params);
if (res.code === 200) {
entityTypeCount.value = res.data || [];
initTypeChart();
}
} catch (error) {
console.log(error);
}
}
// 单次制裁-数据统计-制裁实体领域分布情况
const domainCount = ref([]);
// 单次制裁-数据统计-制裁实体领域分布情况-请求
const getDomainCount = async () => {
if (!sanRecordId.value) return;
try {
const params = { sanRecordId: sanRecordId.value };
if (domainTime.value && domainTime.value !== 'all') {
params.startDate = `${domainTime.value}-01-01`;
params.endDate = `${domainTime.value}-12-31`;
}
const res = await getSingleSanctionDomainCount(params);
if (res.code === 200) {
domainCount.value = res.data || [];
initDomainChart();
}
} catch (error) {
console.log(error);
}
}
// 单次制裁-数据统计-总量统计
const totalCount = ref({});
// 单次制裁-数据统计-总量统计-请求
const getTotalCount = async () => {
if (!sanRecordId.value) return;
try {
const res = await getSingleSanctionTotalCount({ sanRecordId: sanRecordId.value });
if (res.code === 200) {
totalCount.value = res.data || {};
}
} catch (error) {
console.log(error);
}
}
const router = useRouter();
const sanRecordId = ref("")
const getUrlParams = () => {
const urlParams = new URLSearchParams(window.location.search);
sanRecordId.value = urlParams.get("id") || ""
}
const regionTime = ref("all"); const regionTime = ref("all");
const domainTime = ref("all"); const domainTime = ref("all");
const typeTime = ref("all"); const typeTime = ref("all");
watch(domainTime, () => {
getDomainCount();
});
watch(typeTime, () => {
getEntityTypeCount();
});
watch(regionTime, () => {
getRegionData();
});
const timeOptions = [ const timeOptions = [
{ label: "全部时间", value: "all" }, { label: "全部时间", value: "all" }
{ label: "2024年", value: "2024" },
{ label: "2023年", value: "2023" }
]; ];
// 生成2000-2025年的选项
for (let i = 2025; i >= 2000; i--) {
timeOptions.push({ label: `${i}年`, value: `${i}` });
}
const countryDistribution = [ // 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: 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: "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: 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%)" } // { 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 mapChartRef = ref(null);
const domainChartRef = ref(null); const domainChartRef = ref(null);
const typeChartRef = 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 => { const getBarColor = index => {
if (index === 0) return "linear-gradient(90deg, rgba(255, 77, 79, 0) 0%, rgba(255, 77, 79, 1) 100%)"; 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%)"; 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%)"; 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 = () => { const initMapChart = () => {
if (!mapChartRef.value) return; if (!mapChartRef.value) return;
// 销毁旧实例
const oldChart = echarts.getInstanceByDom(mapChartRef.value);
if (oldChart) {
oldChart.dispose();
}
const chart = echarts.init(mapChartRef.value); const chart = echarts.init(mapChartRef.value);
echarts.registerMap("china", chinaJson); echarts.registerMap("china", chinaJson);
const scatterData = regionDistribution.value.map(item => {
return {
name: item.name,
value: [item.lon, item.lat, item.count]
};
});
const option = { const option = {
tooltip: { tooltip: {
show: false show: false
...@@ -317,7 +419,7 @@ const initMapChart = () => { ...@@ -317,7 +419,7 @@ const initMapChart = () => {
{ {
type: "scatter", type: "scatter",
coordinateSystem: "geo", coordinateSystem: "geo",
data: convertData(rankData), data: scatterData,
symbolSize: 8, symbolSize: 8,
itemStyle: { itemStyle: {
color: "#ff4d4f", color: "#ff4d4f",
...@@ -343,17 +445,17 @@ const initMapChart = () => { ...@@ -343,17 +445,17 @@ const initMapChart = () => {
const initDomainChart = () => { const initDomainChart = () => {
if (!domainChartRef.value) return; if (!domainChartRef.value) return;
// 销毁旧实例,防止内存泄漏或状态残留
const oldChart = echarts.getInstanceByDom(domainChartRef.value);
if (oldChart) {
oldChart.dispose();
}
const chart = echarts.init(domainChartRef.value); const chart = echarts.init(domainChartRef.value);
const data = [ const data = domainCount.value.map(item => ({
{ value: 215, name: "集成电路" }, value: item.count,
{ value: 198, name: "人工智能" }, name: item.name
{ value: 117, name: "通信网络" }, }));
{ value: 98, name: "量子科技" },
{ value: 91, name: "先进制造" },
{ value: 80, name: "新材料" },
{ value: 62, name: "航空航天" }
];
const option = { const option = {
tooltip: { tooltip: {
...@@ -438,13 +540,17 @@ const initDomainChart = () => { ...@@ -438,13 +540,17 @@ const initDomainChart = () => {
const initTypeChart = () => { const initTypeChart = () => {
if (!typeChartRef.value) return; if (!typeChartRef.value) return;
// 销毁旧实例,防止内存泄漏或状态残留
const oldChart = echarts.getInstanceByDom(typeChartRef.value);
if (oldChart) {
oldChart.dispose();
}
const chart = echarts.init(typeChartRef.value); const chart = echarts.init(typeChartRef.value);
const data = [ const data = entityTypeCount.value.map(item => ({
{ value: 50, name: "企业" }, value: item.count,
{ value: 32, name: "高校" }, name: item.name
{ value: 32, name: "科研院所" } }));
];
const option = { const option = {
tooltip: { tooltip: {
...@@ -524,9 +630,22 @@ const initTypeChart = () => { ...@@ -524,9 +630,22 @@ const initTypeChart = () => {
}; };
onMounted(() => { onMounted(() => {
// 获取url参数
getUrlParams();
// 单次制裁-数据统计-总量统计-请求
getTotalCount();
// 单次制裁-数据统计-制裁实体领域分布情况-请求
getDomainCount();
// 单次制裁-数据统计-制裁实体类型分布情况-请求
getEntityTypeCount();
// 单次制裁-数据统计-制裁实体国家分布情况-请求
getCountryCount();
// 单次制裁-数据统计-制裁实体地域分布情况-请求
getRegionData();
initMapChart(); initMapChart();
initDomainChart(); // initDomainChart(); // 移到 getDomainCount 回调中初始化
initTypeChart(); // initTypeChart(); // 移到 getEntityTypeCount 回调中初始化
}); });
</script> </script>
...@@ -626,6 +745,7 @@ onMounted(() => { ...@@ -626,6 +745,7 @@ onMounted(() => {
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
border-radius: 10px; border-radius: 10px;
.country-list { .country-list {
overflow: auto;
width: 100%; width: 100%;
height: 280px; height: 280px;
padding: 0 20px; padding: 0 20px;
......
...@@ -29,8 +29,20 @@ ...@@ -29,8 +29,20 @@
<div class="filter-bar"> <div class="filter-bar">
<el-select v-model="searchDomain" placeholder="全部领域" class="domain-select"> <el-select v-model="searchDomain" placeholder="全部领域" class="domain-select">
<el-option label="全部领域" value="" /> <el-option label="全部领域" value="" />
<el-option label="集成电路" value="集成电路" /> <el-option label="人工智能" value="1" />
<el-option label="人工智能" value="人工智能" /> <el-option label="生物科技" value="2" />
<el-option label="新一代信息技术" value="3" />
<el-option label="量子科技" value="4" />
<el-option label="新能源" value="5" />
<el-option label="集成电路" value="6" />
<el-option label="海洋" value="7" />
<el-option label="先进制造" value="8" />
<el-option label="新材料" value="9" />
<el-option label="航空航天" value="10" />
<el-option label="深海" value="11" />
<el-option label="极地" value="12" />
<el-option label="太空" value="13" />
<el-option label="核" value="14" />
</el-select> </el-select>
<el-input v-model="searchText" placeholder="搜索实体" class="search-input"> <el-input v-model="searchText" placeholder="搜索实体" class="search-input">
<template #suffix> <template #suffix>
...@@ -89,12 +101,26 @@ ...@@ -89,12 +101,26 @@
<div class="rule-checkbox" v-if="rightActiveTab === 'equity'"> <div class="rule-checkbox" v-if="rightActiveTab === 'equity'">
<el-checkbox v-model="is50PercentRule" size="large">50%规则涉及实体</el-checkbox> <el-checkbox v-model="is50PercentRule" size="large">50%规则涉及实体</el-checkbox>
</div> </div>
<el-select v-model="filterType" placeholder="全部类型" class="header-select"> <!-- <el-select v-model="filterType" placeholder="全部类型" class="header-select">
<el-option label="全部类型" value="" /> <el-option label="全部类型" value="" />
</el-select> </el-select>
<el-select v-model="filterDomain" placeholder="全部领域" class="header-select last-select"> <el-select v-model="filterDomain" placeholder="全部领域" class="header-select last-select">
<el-option label="全部领域" value="" /> <el-option label="全部领域" value="" />
</el-select> <el-option label="人工智能" value="1" />
<el-option label="生物科技" value="2" />
<el-option label="新一代信息技术" value="3" />
<el-option label="量子科技" value="4" />
<el-option label="新能源" value="5" />
<el-option label="集成电路" value="6" />
<el-option label="海洋" value="7" />
<el-option label="先进制造" value="8" />
<el-option label="新材料" value="9" />
<el-option label="航空航天" value="10" />
<el-option label="深海" value="11" />
<el-option label="极地" value="12" />
<el-option label="太空" value="13" />
<el-option label="核" value="14" />
</el-select> -->
<div class="btn"> <div class="btn">
<img src="../../assets/数据库按钮.png" alt="" /> <img src="../../assets/数据库按钮.png" alt="" />
<img src="../../assets/下载按钮.png" alt="" /> <img src="../../assets/下载按钮.png" alt="" />
...@@ -124,10 +150,14 @@ ...@@ -124,10 +150,14 @@
<div class="popup-body"> <div class="popup-body">
<div class="tag-row"> <div class="tag-row">
<span class="red-dot"></span> <span class="red-dot"></span>
<span class="red-text">2025年7月15日 《实体清单》</span> <span class="red-text">
<!-- 2025年7月15日 《实体清单》 -->
暂无数据
</span>
</div> </div>
<div class="desc"> <div class="desc">
因获取和试图获取美国原产物品以支持中国军事和国防相关空间领域活动以及中国量子技术能力而被列入。 <!-- 因获取和试图获取美国原产物品以支持中国军事和国防相关空间领域活动以及中国量子技术能力而被列入。 -->
暂无数据
</div> </div>
</div> </div>
</div> </div>
...@@ -140,8 +170,9 @@ ...@@ -140,8 +170,9 @@
<script setup> <script setup>
import { ref, onMounted, nextTick, watch, onUnmounted } from "vue"; import { ref, onMounted, nextTick, watch, onUnmounted } from "vue";
import { debounce } from "lodash";
import * as echarts from "echarts"; import * as echarts from "echarts";
import { ArrowLeft, ArrowRight, Search, CaretRight, Close, CircleCheckFilled } from "@element-plus/icons-vue"; import { Search, CaretRight, Close } from "@element-plus/icons-vue";
import defaultTitle from "../../assets/default-icon2.png"; import defaultTitle from "../../assets/default-icon2.png";
import icon01 from "./assets/icon01.png" import icon01 from "./assets/icon01.png"
import icon02 from "./assets/icon02.png" import icon02 from "./assets/icon02.png"
...@@ -152,6 +183,94 @@ import echartsIcon02 from "./assets/echartsIcon02.png" ...@@ -152,6 +183,94 @@ import echartsIcon02 from "./assets/echartsIcon02.png"
import echartsIcon03 from "./assets/echartsIcon03.png" import echartsIcon03 from "./assets/echartsIcon03.png"
import company from "./assets/company.png" import company from "./assets/company.png"
import companyActive from "./assets/company-active.png" import companyActive from "./assets/company-active.png"
import { getSingleSanctionEntityList, getSingleSanctionEntitySupplyChain, getSingleSanctionEntityEquity } from "@/api/exportControlV2.0";
// 单次制裁-深度挖掘-制裁实体股权信息-列表
const singleSanctionEntityEquityData = ref(null);
// 单次制裁-深度挖掘-制裁实体股权信息-请求
const getSingleSanctionEntityEquityRequest = async () => {
try {
const res = await getSingleSanctionEntityEquity({
orgId: activeEntityId.value,
rule: is50PercentRule.value,
})
if (res.code === 200) {
singleSanctionEntityEquityData.value = res.data || null;
initChart();
}
} catch (error) {
console.log(error)
}
}
// 单次制裁-深度挖掘-制裁实体供应链信息-列表
const singleSanctionEntitySupplyChainData = ref(null);
// 单次制裁-深度挖掘-制裁实体供应链信息-请求
const getSingleSanctionEntitySupplyChainRequest = async () => {
try {
const res = await getSingleSanctionEntitySupplyChain({
orgId: activeEntityId.value,
})
if (res.code === 200) {
singleSanctionEntitySupplyChainData.value = res.data || null;
initChart();
}
} catch (error) {
console.log(error)
}
}
// 单次制裁-深度挖掘-本次制裁实体清单列表
const singleSanctionEntityList = ref([]);
// 单次制裁-深度挖掘-本次制裁实体清单列表-请求
const getSingleSanctionEntityListRequest = async () => {
try {
const res = await getSingleSanctionEntityList({
sanRecordId: sanRecordId.value,
isOnlyCn: false,
domainId: searchDomain.value || undefined,
searchText: searchText.value || undefined,
})
if (res.code === 200) {
entityList.value = (res.data || []).map((group, index) => ({
id: `group-${index}`,
name: group.orgType,
count: group.orgInfoList ? group.orgInfoList.length : 0,
expanded: index === 0, // 默认展开第一个分组
children: (group.orgInfoList || []).map(org => ({
id: org.id,
name: org.orgNameZh
}))
}));
// 如果有数据,且当前没有选中的实体,默认选中第一个分组的第一个实体
if (entityList.value.length > 0 && entityList.value[0].children && entityList.value[0].children.length > 0) {
const firstEntity = entityList.value[0].children[0];
if (!activeEntityId.value) {
activeEntityId.value = firstEntity.id;
currentEntityName.value = firstEntity.name;
}
}
}
} catch (error) {
console.log(error)
}
}
const sanRecordId = ref("")
const getUrlParams = () => {
const urlParams = new URLSearchParams(window.location.search);
sanRecordId.value = urlParams.get("id") || ""
}
const activeTab = ref(["实体穿透分析", "重点实体识别"]); const activeTab = ref(["实体穿透分析", "重点实体识别"]);
const activeIndex = ref(0); const activeIndex = ref(0);
...@@ -163,41 +282,9 @@ const filterType = ref(""); ...@@ -163,41 +282,9 @@ const filterType = ref("");
const filterDomain = ref(""); const filterDomain = ref("");
const activeEntityId = ref(""); const activeEntityId = ref("");
const currentEntityName = ref(""); const currentEntityName = ref("");
const is50PercentRule = ref(true); const is50PercentRule = ref(false);
const entityList = ref([ 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) => { const toggleGroup = (group) => {
group.expanded = !group.expanded; group.expanded = !group.expanded;
...@@ -206,16 +293,6 @@ const toggleGroup = (group) => { ...@@ -206,16 +293,6 @@ const toggleGroup = (group) => {
const selectEntity = (item) => { const selectEntity = (item) => {
activeEntityId.value = item.id; activeEntityId.value = item.id;
currentEntityName.value = item.name; 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 chartRef = ref(null);
...@@ -257,48 +334,149 @@ const initChart = () => { ...@@ -257,48 +334,149 @@ const initChart = () => {
}; };
const getSupplyChainOption = () => { const getSupplyChainOption = () => {
const centerName = formatEntityName(currentEntityName.value); if (!singleSanctionEntitySupplyChainData.value) return {};
const nodes = [ const data = singleSanctionEntitySupplyChainData.value;
{ 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) => { const nodes = [];
acc[node.id] = node.category; const links = [];
return acc; const centerX = 550;
}, {}); const centerY = 400;
const links = [ // 中心节点
{ source: '1', target: '0', value: '供应商' }, nodes.push({
{ source: '2', target: '0', value: '供应商' }, id: '0',
{ source: '3', target: '0', value: '供应商' }, name: data.orgName,
{ source: '4', target: '3', value: '供应商' }, category: 0, // 强制为制裁中
{ source: '5', target: '3', value: '供应商' }, symbol: 'image://' + companyActive, // 强制使用制裁中图标
{ source: '6', target: '0', value: '供应商' }, x: centerX,
{ source: '7', target: '0', value: '供应商' }, y: centerY,
{ source: '8', target: '0', value: '供应商' }, symbolSize: 50,
{ source: '0', target: '9', value: '客户' }, label: {
{ source: '0', target: '10', value: '客户' }, fontSize: 16,
{ source: '0', target: '11', value: '客户' }, fontWeight: 'bold',
{ source: '0', target: '12', value: '客户' }, color: '#055FC2', // 使用制裁蓝,在图标下方更清晰
{ source: '0', target: '13', value: '客户' }, position: 'bottom',
]; distance: 10,
width: 150,
overflow: 'break'
}
});
// 父级节点 (供应商)
const parentList = data.parentOrgList || [];
parentList.forEach((item, index) => {
// 使用 (index + 0.5) 使节点在半圆弧内居中分布
const angle = -Math.PI + ((index + 0.5) / parentList.length) * Math.PI;
// 交错半径:偶数索引使用 340,奇数索引使用 440,拉开垂直距离
const radius = index % 2 === 0 ? 340 : 440;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
// 动态计算标签位置:根据余弦值判断左右,根据正弦值判断上下
let position = 'right';
let align = 'left';
const cosA = Math.cos(angle);
const sinA = Math.sin(angle);
if (Math.abs(cosA) < 0.3) {
// 顶部区域
position = 'top';
align = 'center';
} else if (cosA < 0) {
// 左侧区域
position = 'left';
align = 'right';
}
nodes.push({
id: `p-${item.id}`,
name: item.name,
category: item.isSanctioned ? 0 : 1,
symbol: 'image://' + (item.isSanctioned ? companyActive : company),
x: x,
y: y,
isSanctioned: item.isSanctioned,
label: {
position: position,
align: align,
distance: 8,
width: 110,
overflow: 'break',
lineHeight: 14,
fontSize: 11
}
});
links.push({
source: `p-${item.id}`,
target: '0',
value: '供应商',
isSanctioned: item.isSanctioned && data.isSanctioned
});
});
// 子级节点 (客户)
const childList = data.childrenOrgList || [];
childList.forEach((item, index) => {
const angle = ((index + 0.5) / childList.length) * Math.PI;
// 交错半径
const radius = index % 2 === 0 ? 340 : 440;
const x = centerX + radius * Math.cos(angle);
const y = centerY + radius * Math.sin(angle);
// 动态计算标签位置
let position = 'right';
let align = 'left';
const cosA = Math.cos(angle);
const sinA = Math.sin(angle);
if (Math.abs(cosA) < 0.3) {
// 底部区域
position = 'bottom';
align = 'center';
} else if (cosA < 0) {
// 左侧区域
position = 'left';
align = 'right';
}
nodes.push({
id: `c-${item.id}`,
name: item.name,
category: item.isSanctioned ? 0 : 1,
symbol: 'image://' + (item.isSanctioned ? companyActive : company),
x: x,
y: y,
isSanctioned: item.isSanctioned,
label: {
position: position,
align: align,
distance: 8,
width: 110,
overflow: 'break',
lineHeight: 14,
fontSize: 11
}
});
links.push({
source: '0',
target: `c-${item.id}`,
value: '客户',
isSanctioned: item.isSanctioned && data.isSanctioned
});
});
return { return {
tooltip: { show: false }, tooltip: {
show: true,
formatter: (params) => {
if (params.dataType === 'node') {
return `<div style="padding: 8px; max-width: 300px; white-space: normal; word-break: break-all;">${params.data.name}</div>`;
}
return '';
}
},
series: [ series: [
{ {
type: 'graph', type: 'graph',
...@@ -307,10 +485,9 @@ const getSupplyChainOption = () => { ...@@ -307,10 +485,9 @@ const getSupplyChainOption = () => {
roam: true, roam: true,
label: { label: {
show: true, show: true,
position: 'bottom',
formatter: '{b}', formatter: '{b}',
fontSize: 12, fontSize: 12,
lineHeight: 16 hideOverlap: true
}, },
edgeSymbol: ['none', 'arrow'], edgeSymbol: ['none', 'arrow'],
edgeSymbolSize: [4, 8], edgeSymbolSize: [4, 8],
...@@ -336,15 +513,14 @@ const getSupplyChainOption = () => { ...@@ -336,15 +513,14 @@ const getSupplyChainOption = () => {
} }
})), })),
links: links.map(link => { links: links.map(link => {
const isSanctioned = nodeCategoryMap[link.source] === 0 && nodeCategoryMap[link.target] === 0;
return { return {
...link, ...link,
lineStyle: { lineStyle: {
color: isSanctioned ? 'rgba(100, 180, 255, 1)' : 'rgb(180, 181, 182)', color: link.isSanctioned ? 'rgba(100, 180, 255, 1)' : 'rgb(180, 181, 182)',
width: 1, width: 1,
curveness: 0 curveness: 0
}, },
label: isSanctioned ? { label: link.isSanctioned ? {
show: true, show: true,
formatter: '{c}', formatter: '{c}',
backgroundColor: 'rgba(231, 243, 255, 1)', backgroundColor: 'rgba(231, 243, 255, 1)',
...@@ -364,47 +540,116 @@ const getSupplyChainOption = () => { ...@@ -364,47 +540,116 @@ const getSupplyChainOption = () => {
}; };
const getEquityOption = () => { const getEquityOption = () => {
const centerName = formatEntityName(currentEntityName.value); if (!singleSanctionEntityEquityData.value) return {};
const nodes = [ const data = singleSanctionEntityEquityData.value;
{ id: '0', name: centerName, category: 0, symbol: 'image://' + companyActive, x: 550, y: 350, symbolSize: 50, label: { fontSize: 16, fontWeight: 'bold', color: '#055FC2' } },
const nodes = [];
// Shareholders (Top) const links = [];
{ id: '1', name: '上海复芯凡高集成\n电路技术有限公司', category: 0, symbol: 'image://' + companyActive, x: 150, y: 50 }, const centerX = 550;
{ id: '2', name: '中信证券股份有限\n公司-嘉实上证科创\n板芯片交易型开...', category: 1, symbol: 'image://' + company, x: 350, y: 50 }, const centerY = 400;
{ 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 }, nodes.push({
id: '0',
// Investments (Bottom) name: data.orgName,
{ id: '6', name: '上海复龙鸿芯微系\n统技术有限公司', category: 0, symbol: 'image://' + companyActive, x: 50, y: 650 }, category: 0, // 强制为制裁中
{ id: '7', name: '上海复微迅捷数字\n技术有限公司', category: 0, symbol: 'image://' + companyActive, x: 200, y: 650 }, symbol: 'image://' + companyActive, // 强制使用制裁中图标
{ id: '8', name: '上海索辰信息技术\n有限公司', category: 0, symbol: 'image://' + companyActive, x: 350, y: 650 }, x: centerX,
{ id: '9', name: '上海复旦微电子\n(香港)有限公司', category: 0, symbol: 'image://' + companyActive, x: 500, y: 650 }, y: centerY,
{ id: '10', name: '北京复旦微电子技\n术有限公司', category: 0, symbol: 'image://' + companyActive, x: 650, y: 650 }, symbolSize: 50,
{ id: '11', name: '深圳复旦微电子有\n限公司', category: 0, symbol: 'image://' + companyActive, x: 800, y: 650 }, label: {
{ id: '12', name: '上海华岭集成电路\n技术股份有限公司', category: 1, symbol: 'image://' + company, x: 950, y: 650 }, fontSize: 16,
{ id: '13', name: '上海复旦通讯股份\n有限公司', category: 1, symbol: 'image://' + company, x: 1100, y: 650 }, fontWeight: 'bold',
]; color: '#055FC2', // 使用制裁蓝,在图标下方更清晰
position: 'bottom',
const links = [ distance: 10,
{ source: '1', target: '0', value: '持股51%', isSanctioned: true }, width: 150,
{ source: '2', target: '0', value: '持股12%', isSanctioned: false }, overflow: 'break'
{ 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 }, const parentList = data.parentOrgList || [];
{ source: '0', target: '7', value: '持股60%', isSanctioned: true }, parentList.forEach((item, index) => {
{ source: '0', target: '8', value: '持股54%', isSanctioned: true }, // 在顶部水平排列
{ source: '0', target: '9', value: '持股51%', isSanctioned: true }, const total = parentList.length;
{ source: '0', target: '10', value: '持股51%', isSanctioned: true }, const gap = 200;
{ source: '0', target: '11', value: '持股51%', isSanctioned: true }, const startX = centerX - ((total - 1) * gap) / 2;
{ source: '0', target: '12', value: '持股15%', isSanctioned: false }, const x = startX + index * gap;
{ source: '0', target: '13', value: '持股12%', isSanctioned: false }, const y = centerY - 250; // 向上偏移
];
nodes.push({
id: `p-${index}`,
name: item.name,
category: item.isSanctioned ? 0 : 1,
symbol: 'image://' + (item.isSanctioned ? companyActive : company),
x: x,
y: y,
isSanctioned: item.isSanctioned,
label: {
position: 'top',
distance: 8,
width: 110,
overflow: 'break',
lineHeight: 14,
fontSize: 11
}
});
links.push({
source: `p-${index}`,
target: '0',
value: item.type || '持股', // 使用 type 字段
isSanctioned: item.isSanctioned // 中心节点强制制裁,高亮取决于对方
});
});
// 子级节点 (对外投资)
const childList = data.childrenOrgList || [];
childList.forEach((item, index) => {
// 在底部水平排列
const total = childList.length;
const gap = 240; // 稍微拉开一点间距
const startX = centerX - ((total - 1) * gap) / 2;
const x = startX + index * gap;
const y = centerY + 250; // 向下偏移
nodes.push({
id: `c-${index}`,
name: item.name,
category: item.isSanctioned ? 0 : 1,
symbol: 'image://' + (item.isSanctioned ? companyActive : company),
x: x,
y: y,
isSanctioned: item.isSanctioned,
label: {
position: 'bottom',
distance: 8,
width: 110,
overflow: 'break',
lineHeight: 14,
fontSize: 11
}
});
links.push({
source: '0',
target: `c-${index}`,
value: item.type || '持股', // 使用 type 字段
isSanctioned: item.isSanctioned // 中心节点强制制裁,高亮取决于对方
});
});
return { return {
tooltip: { show: false }, tooltip: {
show: true,
formatter: (params) => {
if (params.dataType === 'node') {
return `<div style="padding: 8px; max-width: 300px; white-space: normal; word-break: break-all;">${params.data.name}</div>`;
}
return '';
}
},
series: [ series: [
{ {
type: 'graph', type: 'graph',
...@@ -413,16 +658,15 @@ const getEquityOption = () => { ...@@ -413,16 +658,15 @@ const getEquityOption = () => {
roam: true, roam: true,
label: { label: {
show: true, show: true,
position: 'bottom',
formatter: '{b}', formatter: '{b}',
fontSize: 12, fontSize: 12,
lineHeight: 16 hideOverlap: true
}, },
edgeSymbol: ['none', 'arrow'], edgeSymbol: ['none', 'arrow'],
edgeSymbolSize: [4, 8], edgeSymbolSize: [4, 8],
edgeLabel: { edgeLabel: {
position: 'middle', position: 'middle',
offset: [0, 13], offset: [0, 13],
fontSize: 12, fontSize: 12,
fontWeight: 400, fontWeight: 400,
fontFamily: 'Microsoft YaHei', fontFamily: 'Microsoft YaHei',
...@@ -468,7 +712,58 @@ const getEquityOption = () => { ...@@ -468,7 +712,58 @@ const getEquityOption = () => {
}; };
}; };
const debouncedGetList = debounce(() => {
getSingleSanctionEntityListRequest();
}, 1000);
watch(searchText, () => {
debouncedGetList();
});
watch(searchDomain, () => {
getSingleSanctionEntityListRequest();
});
watch(activeIndex, (val) => {
if (val === 0) {
nextTick(() => {
if (activeEntityId.value) {
if (rightActiveTab.value === 'supplyChain') {
getSingleSanctionEntitySupplyChainRequest();
} else {
getSingleSanctionEntityEquityRequest();
}
}
initChart();
});
}
});
watch(activeEntityId, (val) => {
if (val) {
if (rightActiveTab.value === 'supplyChain') {
getSingleSanctionEntitySupplyChainRequest();
} else {
getSingleSanctionEntityEquityRequest();
}
initChart();
}
});
watch(is50PercentRule, () => {
if (rightActiveTab.value === 'equity' && activeEntityId.value) {
getSingleSanctionEntityEquityRequest();
}
});
watch(rightActiveTab, (val) => { watch(rightActiveTab, (val) => {
if (activeEntityId.value) {
if (val === 'supplyChain') {
getSingleSanctionEntitySupplyChainRequest();
} else {
getSingleSanctionEntityEquityRequest();
}
}
nextTick(() => { nextTick(() => {
initChart(); initChart();
}); });
...@@ -476,16 +771,11 @@ watch(rightActiveTab, (val) => { ...@@ -476,16 +771,11 @@ watch(rightActiveTab, (val) => {
onMounted(() => { onMounted(() => {
// 默认选中第一个实体 // 获取URL参数
if (entityList.value.length > 0 && entityList.value[0].children && entityList.value[0].children.length > 0) { getUrlParams();
const firstEntity = entityList.value[0].children[0]; // 单次制裁-深度挖掘-本次制裁实体清单列表-请求
activeEntityId.value = firstEntity.id; getSingleSanctionEntityListRequest();
currentEntityName.value = firstEntity.name;
}
nextTick(() => {
initChart();
});
window.addEventListener('resize', handleResize); window.addEventListener('resize', handleResize);
}); });
...@@ -493,6 +783,9 @@ onUnmounted(() => { ...@@ -493,6 +783,9 @@ onUnmounted(() => {
if (chartInstance.value) { if (chartInstance.value) {
chartInstance.value.dispose(); chartInstance.value.dispose();
} }
if (debouncedGetList && debouncedGetList.cancel) {
debouncedGetList.cancel();
}
window.removeEventListener('resize', handleResize); window.removeEventListener('resize', handleResize);
}); });
......
...@@ -15,8 +15,22 @@ ...@@ -15,8 +15,22 @@
</div> </div>
<div class="left-main"> <div class="left-main">
<div class="top-bar"> <div class="top-bar">
<el-select v-model="selectedDomain" class="domain-select" placeholder="Select"> <el-select v-model="searchDomain" placeholder="全部领域" class="domain-select">
<el-option label="全部领域" value="全部领域" /> <el-option label="全部领域" value="" />
<el-option label="人工智能" value="1" />
<el-option label="生物科技" value="2" />
<el-option label="新一代信息技术" value="3" />
<el-option label="量子科技" value="4" />
<el-option label="新能源" value="5" />
<el-option label="集成电路" value="6" />
<el-option label="海洋" value="7" />
<el-option label="先进制造" value="8" />
<el-option label="新材料" value="9" />
<el-option label="航空航天" value="10" />
<el-option label="深海" value="11" />
<el-option label="极地" value="12" />
<el-option label="太空" value="13" />
<el-option label="核" value="14" />
</el-select> </el-select>
<el-input v-model="searchKeyword" class="search-input" placeholder="搜索实体" :suffix-icon="Search" /> <el-input v-model="searchKeyword" class="search-input" placeholder="搜索实体" :suffix-icon="Search" />
</div> </div>
...@@ -26,7 +40,7 @@ ...@@ -26,7 +40,7 @@
<div <div
class="company-item" class="company-item"
:class="{ active: selectedCompanyId === item.id }" :class="{ active: selectedCompanyId === item.id }"
v-for="item in companyList" v-for="item in entityList"
:key="item.id" :key="item.id"
@click="selectedCompanyId = item.id" @click="selectedCompanyId = item.id"
> >
...@@ -51,7 +65,7 @@ ...@@ -51,7 +65,7 @@
:class="{ active: activeScale === item }" :class="{ active: activeScale === item }"
v-for="item in scaleOptions" v-for="item in scaleOptions"
:key="item" :key="item"
@click="activeScale = item" @click="handleScaleClick(item)"
> >
{{ item }} {{ item }}
</div> </div>
...@@ -163,12 +177,298 @@ ...@@ -163,12 +177,298 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, nextTick, computed } from "vue"; import { ref, onMounted, nextTick, watch } from "vue";
import { debounce } from "lodash";
import * as echarts from "echarts"; import * as echarts from "echarts";
import { Search } from "@element-plus/icons-vue"; import { Search } from "@element-plus/icons-vue";
import defaultTitle from "../../../../assets/default-icon2.png"; import defaultTitle from "../../../../assets/default-icon2.png";
import ai from "../../assets/ai.png"; import ai from "../../assets/ai.png";
import right from "../../assets/right.png"; import right from "../../assets/right.png";
import {getSingleSanctionEntityList, getSingleSanctionEntityRevenue, getSingleSanctionEntityNetProfit, getSingleSanctionEntityPersonnel ,getSingleSanctionEntityMarketValue ,getSingleSanctionEntityRDInvestment, getSingleSanctionEntityMarketShare} from "@/api/exportControlV2.0";
// 单次制裁-影响分析-企业市场占比
const marketShareData = ref([]);
const getMarketShare = async () => {
if (!selectedCompanyId.value) return;
try {
const res = await getSingleSanctionEntityMarketShare({
id: selectedCompanyId.value,
})
if (res.code === 200) {
marketShareData.value = res.data || [];
// 格式化市场占比数据
const sortedData = [...marketShareData.value].sort((a, b) => a.year - b.year);
if (sortedData.length > 0) {
shareChartData.value = {
dates: sortedData.map(item => item.year.toString()),
values: sortedData.map(item => Number(item.count?.toFixed(2) || 0)),
unit: "%",
sanctionDate: "2024\nQ1" // 默认制裁日期
};
nextTick(() => {
initShareChart();
});
}
}
} catch (error) {
console.log(error)
}
}
// 单次制裁-影响分析-企业研发投入
const rdInvestmentData = ref([]);
const getRDInvestment = async () => {
if (!selectedCompanyId.value) return;
try {
const res = await getSingleSanctionEntityRDInvestment({
id: selectedCompanyId.value,
})
if (res.code === 200) {
rdInvestmentData.value = res.data || [];
// 格式化研发投入数据
const sortedData = [...rdInvestmentData.value].sort((a, b) => a.year - b.year);
if (sortedData.length > 0) {
const lastItem = sortedData[sortedData.length - 1];
rdChartData.value = {
dates: sortedData.map(item => item.year.toString()),
values: sortedData.map(item => Number(item.count?.toFixed(2) || 0)),
unit: " 万",
endValue: Number(lastItem.count?.toFixed(2) || 0),
sanctionDate: "2024\nQ1" // 默认制裁日期
};
nextTick(() => {
initRDChart();
});
}
}
} catch (error) {
console.log(error)
}
}
// 单次制裁-影响分析-企业市值变化
const marketValueData = ref([]);
const getMarketValue = async () => {
if (!selectedCompanyId.value) return;
try {
const res = await getSingleSanctionEntityMarketValue({
id: selectedCompanyId.value,
})
if (res.code === 200) {
marketValueData.value = res.data || [];
// 格式化市值数据
const sortedData = [...marketValueData.value].sort((a, b) => a.year - b.year);
if (sortedData.length > 0) {
const lastItem = sortedData[sortedData.length - 1];
marketChartData.value = {
dates: sortedData.map(item => item.year.toString()),
values: sortedData.map(item => Number(item.count?.toFixed(2) || 0)),
unit: " 万",
endValue: Number(lastItem.count?.toFixed(2) || 0),
sanctionDate: "2024\nQ1" // 默认制裁日期
};
nextTick(() => {
initMarketChart();
});
}
}
} catch (error) {
console.log(error)
}
}
// 单次制裁-影响分析-企业规模-人员
const personnelData = ref([]);
// 单次制裁-影响分析-企业规模-人员-调查
const getPersonnel = async () => {
if (!selectedCompanyId.value) return;
try {
const res = await getSingleSanctionEntityPersonnel({
id: selectedCompanyId.value,
})
if (res.code === 200) {
personnelData.value = res.data || [];
// 将数据格式化为图表所需格式
const sortedData = [...personnelData.value].sort((a, b) => a.year - b.year);
chartData.value = {
dates: sortedData.map(item => item.year.toString()),
values: sortedData.map(item => Number(item.count?.toFixed(0) || 0)), // 人员通常是整数
unit: " 人",
endValue: Number(sortedData[sortedData.length - 1]?.count || 0)
};
// 重新初始化图表
nextTick(() => {
initRevenueChart();
});
}
} catch (error) {
console.log(error)
}
}
// 单次制裁-影响分析-企业规模-净利润
const netProfitData = ref([]);
// 单次制裁-影响分析-企业规模-净利润-调查
const getNetProfitData = async () => {
if (!selectedCompanyId.value) return;
try {
const res = await getSingleSanctionEntityNetProfit({
id: selectedCompanyId.value,
})
if (res.code === 200) {
netProfitData.value = res.data || [];
// 将数据格式化为图表所需格式
const sortedData = [...netProfitData.value].sort((a, b) => a.year - b.year);
chartData.value = {
dates: sortedData.map(item => item.year.toString()),
values: sortedData.map(item => Number(item.count?.toFixed(2) || 0)),
unit: " 万",
endValue: Number(sortedData[sortedData.length - 1]?.count.toFixed(2) || 0)
};
// 重新初始化图表
nextTick(() => {
initRevenueChart();
});
}
} catch (error) {
console.log(error)
}
}
// 单次制裁-影响分析-制裁企业列表
const entityList = ref([]);
const searchDomain = ref("");
const searchKeyword = ref("");
const selectedCompanyId = ref(null);
const sanRecordId = ref("");
// 单次制裁-影响分析-企业规模-营收
const revenueData = ref([]);
// 单次制裁-影响分析-企业规模-营收-查询
const getRevenueData = async () => {
if (!selectedCompanyId.value) return;
try {
const res = await getSingleSanctionEntityRevenue({
id: selectedCompanyId.value,
})
if (res.code === 200) {
revenueData.value = res.data || [];
// 将数据格式化为图表所需格式
const sortedData = [...revenueData.value].sort((a, b) => a.year - b.year);
chartData.value = {
dates: sortedData.map(item => item.year.toString()),
values: sortedData.map(item => Number(item.count?.toFixed(2) || 0)), // 防御空值
unit: " 万",
endValue: Number(sortedData[sortedData.length - 1]?.count.toFixed(2) || 0)
};
// 重新初始化图表
nextTick(() => {
initRevenueChart();
});
}
} catch (error) {
console.log(error)
}
}
const handleScaleClick = (item) => {
activeScale.value = item;
if (item === '营收') {
getRevenueData();
} else if (item === '净利润') {
getNetProfitData();
} else if (item === '人员') {
getPersonnel();
}
}
watch(selectedCompanyId, (val) => {
if (val) {
// 切换企业时,根据当前激活的按钮更新企业规模数据
if (activeScale.value === '营收') {
getRevenueData();
} else if (activeScale.value === '净利润') {
getNetProfitData();
} else if (activeScale.value === '人员') {
getPersonnel();
}
// 更新其他图表数据
getMarketValue();
getRDInvestment();
getMarketShare();
}
});
// 单次制裁-影响分析-制裁企业列表-查询
const getEntityList = async () => {
try {
const res = await getSingleSanctionEntityList({
sanRecordId: sanRecordId.value,
isOnlyCn: false,
domainId: searchDomain.value || undefined,
searchText: searchKeyword.value || undefined,
})
if (res.code === 200) {
entityList.value = (res.data || []).reduce((acc, group) => {
if (group.orgType === "企业" && group.orgInfoList) {
acc.push(...group.orgInfoList.map(org => ({
id: org.id,
name: org.orgNameZh
})));
}
return acc;
}, []);
// 默认选中第一个
if (entityList.value.length > 0 && !selectedCompanyId.value) {
selectedCompanyId.value = entityList.value[0].id;
}
}
} catch (error) {
console.log(error)
}
}
const debouncedGetEntityList = debounce(() => {
getEntityList();
}, 500);
watch(searchDomain, () => {
getEntityList();
});
watch(searchKeyword, () => {
debouncedGetEntityList();
});
const getUrlParams = () => {
const urlParams = new URLSearchParams(window.location.search);
sanRecordId.value = urlParams.get("id") || ""
}
const activeScale = ref("营收"); const activeScale = ref("营收");
const scaleOptions = ["营收", "净利润", "人员"]; const scaleOptions = ["营收", "净利润", "人员"];
...@@ -179,23 +479,6 @@ const rdOptions = ["研发经费"]; ...@@ -179,23 +479,6 @@ const rdOptions = ["研发经费"];
const activeMarketShare = ref("总体市场份额"); const activeMarketShare = ref("总体市场份额");
const marketShareOptions = ["总体市场份额"]; 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({ const chartData = ref({
dates: [ dates: [
"2023\nQ3", "2023\nQ3",
...@@ -248,7 +531,6 @@ const rdChartData = ref({ ...@@ -248,7 +531,6 @@ const rdChartData = ref({
values: [62, 65, 60, 55, 62, 68, 83, 92, 89, 92], values: [62, 65, 60, 55, 62, 68, 83, 92, 89, 92],
sanctionDate: "2024\nQ1", sanctionDate: "2024\nQ1",
endValue: 92, endValue: 92,
maxY: 100
}); });
const shareChartData = ref({ const shareChartData = ref({
...@@ -266,9 +548,7 @@ const shareChartData = ref({ ...@@ -266,9 +548,7 @@ const shareChartData = ref({
], ],
values: [65, 70, 72, 70, 58, 65, 68, 72, 72, 68], values: [65, 70, 72, 70, 58, 65, 68, 72, 72, 68],
sanctionDate: "2024\nQ1", sanctionDate: "2024\nQ1",
type: "bar",
unit: "%", unit: "%",
maxY: 100
}); });
const chartRef = ref(null); const chartRef = ref(null);
...@@ -276,186 +556,307 @@ const marketChartRef = ref(null); ...@@ -276,186 +556,307 @@ const marketChartRef = ref(null);
const rdChartRef = ref(null); const rdChartRef = ref(null);
const shareChartRef = ref(null); const shareChartRef = ref(null);
const initChart = (domRef, data) => { // 提取通用配置生成器
if (!domRef) return; const getBaseOption = (data) => {
const myChart = echarts.init(domRef); return {
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: { tooltip: {
trigger: "axis", trigger: "axis",
valueFormatter: value => value + unit valueFormatter: value => value + (data.unit || " 亿元")
}, },
grid: { grid: {
top: "15%", top: "15%", // 默认顶部留白
left: "3%", left: "2%",
right: "4%", right: "4%",
bottom: "3%", bottom: "5%",
containLabel: true containLabel: true
}, },
xAxis: { xAxis: {
type: "category", type: "category",
boundaryGap: chartType === "bar", boundaryGap: true,
data: data.dates, data: data.dates,
axisLine: { axisLine: { lineStyle: { color: "#E6EBF5" } },
lineStyle: {
color: "#E6EBF5"
}
},
axisLabel: { axisLabel: {
color: "#606266", color: "#606266",
fontSize: 12, fontSize: 10,
lineHeight: 18 interval: 0,
rotate: 45,
hideOverlap: false
}, },
axisTick: { axisTick: { show: false }
show: false
}
}, },
yAxis: { yAxis: {
type: "value", type: "value",
min: 0, min: 0,
max: maxY, splitNumber: 5,
interval: maxY / 5,
name: chartType === "bar" ? "百分比" : "",
nameTextStyle: {
align: "right",
padding: [0, 8, 0, 0],
color: "#606266"
},
axisLabel: { axisLabel: {
color: "#606266", color: "#606266",
fontSize: 12 fontSize: 12,
formatter: (value) => value.toLocaleString()
}, },
splitLine: { splitLine: { lineStyle: { type: "dashed", color: "rgba(231, 243, 255, 1)" } }
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(() => { // 1. 初始化企业营收图表 (应用原型图置顶样式)
const initRevenueChart = () => {
const dom = chartRef.value;
const data = chartData.value;
if (!dom || !data.values) return;
let myChart = echarts.getInstanceByDom(dom) || echarts.init(dom);
const lastDate = data.dates[data.dates.length - 1];
const lastValue = data.values[data.values.length - 1];
const unit = data.unit || " 亿元";
const maxVal = Math.max(...data.values.map(v => Number(v) || 0));
const maxY = Math.ceil((maxVal * 1.5) / 100) * 100 || 100;
const option = getBaseOption(data);
option.grid.top = "20%"; // 增加顶部空间
option.yAxis.max = maxY;
option.series = [{
data: data.values,
type: "line",
symbol: "none",
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: [
data.sanctionDate ? {
xAxis: data.sanctionDate,
label: {
formatter: "列入实体清单",
position: "end",
color: "#F56C6C",
backgroundColor: "rgba(255, 238, 238, 1)",
borderRadius: 4,
padding: [4, 8]
},
lineStyle: { color: "#F56C6C", type: "dotted", width: 1 }
} : null,
[
{ coord: [lastDate, lastValue], symbol: "none" },
{ coord: [lastDate, maxY], symbol: "none", lineStyle: { color: "#055FC2", type: "dotted" } }
]
].filter(Boolean)
},
markPoint: {
symbol: "circle",
symbolSize: 6,
itemStyle: { color: "#055FC2" },
label: {
show: true,
position: "left",
formatter: "{c}" + unit.trim(),
color: "#055FC2",
fontSize: 16,
fontWeight: "bold",
offset: [-10, 0]
},
data: [{ coord: [lastDate, maxY], value: data.endValue || lastValue }]
}
}];
myChart.setOption(option, true);
};
// 2. 初始化企业市值图表
const initMarketChart = () => {
const dom = marketChartRef.value;
const data = marketChartData.value;
if (!dom || !data.values) return;
let myChart = echarts.getInstanceByDom(dom) || echarts.init(dom);
const lastDate = data.dates[data.dates.length - 1];
const lastValue = data.values[data.values.length - 1];
const unit = data.unit || " 万";
const maxVal = Math.max(...data.values.map(v => Number(v) || 0));
const maxY = Math.ceil((maxVal * 1.5) / 100) * 100 || 100;
const option = getBaseOption(data);
option.grid.top = "20%"; // 增加顶部空间
option.yAxis.max = maxY;
option.series = [{
data: data.values,
type: "line",
symbol: "none",
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: [
data.sanctionDate ? {
xAxis: data.sanctionDate,
label: { formatter: "列入实体清单", position: "end", color: "#F56C6C" },
lineStyle: { color: "#F56C6C", type: "dotted" }
} : null,
[
{ coord: [lastDate, lastValue], symbol: "none" },
{ coord: [lastDate, maxY], symbol: "none", lineStyle: { color: "#055FC2", type: "dotted" } }
]
].filter(Boolean)
},
markPoint: {
symbol: "circle",
symbolSize: 6,
itemStyle: { color: "#055FC2" },
label: {
show: true,
position: "left",
formatter: "{c}" + unit.trim(),
color: "#055FC2",
fontSize: 16,
fontWeight: "bold",
offset: [-10, 0]
},
data: [{ coord: [lastDate, maxY], value: data.endValue || lastValue }]
}
}];
myChart.setOption(option, true);
};
// 3. 初始化研发投入图表
const initRDChart = () => {
const dom = rdChartRef.value;
const data = rdChartData.value;
if (!dom || !data.values) return;
let myChart = echarts.getInstanceByDom(dom) || echarts.init(dom);
const lastDate = data.dates[data.dates.length - 1];
const lastValue = data.values[data.values.length - 1];
const unit = data.unit || " 万";
// 计算 Y 轴最大值,留出顶部空间给数值标签
const maxVal = Math.max(...data.values.map(v => Number(v) || 0));
const maxY = Math.ceil((maxVal * 1.5) / 100) * 100 || 100;
const option = getBaseOption(data);
option.grid.top = "20%"; // 增加顶部空间
option.yAxis.max = maxY;
option.series = [{
data: data.values,
type: "line",
symbol: "none",
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: [
data.sanctionDate ? {
xAxis: data.sanctionDate,
label: { formatter: "列入实体清单", position: "end", color: "#F56C6C" },
lineStyle: { color: "#F56C6C", type: "dotted" }
} : null,
[
{ coord: [lastDate, lastValue], symbol: "none" },
{ coord: [lastDate, maxY], symbol: "none", lineStyle: { color: "#055FC2", type: "dotted" } }
]
].filter(Boolean)
},
markPoint: {
symbol: "circle",
symbolSize: 6,
itemStyle: { color: "#055FC2" },
label: {
show: true,
position: "left",
formatter: "{c}" + unit.trim(),
color: "#055FC2",
fontSize: 16,
fontWeight: "bold",
offset: [-10, 0]
},
data: [{ coord: [lastDate, maxY], value: data.endValue || lastValue }]
}
}];
myChart.setOption(option, true);
};
// 4. 初始化市场占比图表 (柱状图)
const initShareChart = () => {
const dom = shareChartRef.value;
const data = shareChartData.value;
if (!dom || !data.values || data.values.length === 0) return;
let myChart = echarts.getInstanceByDom(dom) || echarts.init(dom);
const option = getBaseOption(data);
// 针对柱状图优化 X 轴
option.xAxis.boundaryGap = true;
option.xAxis.axisLabel.interval = 'auto'; // 自动计算间隔,避免标签重叠导致柱子变细
option.yAxis.name = "百分比";
// 动态计算 Y 轴最大值,避免数据太小时展示不明显
const maxVal = Math.max(...data.values);
const maxY = maxVal === 0 ? 100 : maxVal * 1.5;
option.yAxis.max = maxY;
option.series = [{
data: data.values,
type: "bar",
// 移除固定 barWidth,让其根据数据量自适应,或设置一个合理的最小值
barMaxWidth: 20,
barMinHeight: 2, // 确保即使数值极小也能看到一点
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#055FC2" },
{ offset: 1, color: "rgba(5, 95, 194, 0.1)" }
]),
borderRadius: [8, 8, 0, 0]
},
markLine: {
symbol: "none",
data: [
data.sanctionDate ? {
xAxis: data.sanctionDate,
label: {
formatter: "列入实体清单",
position: "end",
color: "#F56C6C",
backgroundColor: "rgba(245, 108, 108, 0.1)",
padding: [2, 4],
borderRadius: 10
},
lineStyle: { color: "#F56C6C", type: "dotted" }
} : null
].filter(Boolean)
}
}];
myChart.setOption(option, true);
};
onMounted(async () => {
getUrlParams();
// 单次制裁-影响分析-制裁企业列表-查询 (获取到默认的 selectedCompanyId 后会触发 watch 加载其他数据)
await getEntityList();
// 初始化阶段默认调用营收数据
if (selectedCompanyId.value) {
getRevenueData();
}
nextTick(() => { nextTick(() => {
initChart(chartRef.value, chartData.value); initRevenueChart();
initChart(marketChartRef.value, marketChartData.value); initMarketChart();
initChart(rdChartRef.value, rdChartData.value); initRDChart();
initChart(shareChartRef.value, shareChartData.value); initShareChart();
}); });
}); });
</script> </script>
......
...@@ -7,15 +7,15 @@ ...@@ -7,15 +7,15 @@
<div class="text">制裁科研机构列表</div> <div class="text">制裁科研机构列表</div>
<div class="right-group"> <div class="right-group">
<el-select <el-select
v-model="activeDependency" v-model="searchDomain"
class="dependency-select" class="dependency-select"
placeholder="Select" placeholder="全部领域"
> >
<el-option <el-option
v-for="item in dependencyOptions" v-for="item in domainOptions"
:key="item" :key="item.value"
:label="item" :label="item.label"
:value="item" :value="item.value"
/> />
</el-select> </el-select>
<div class="btn-com"> <div class="btn-com">
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
<div <div
class="company-item" class="company-item"
:class="{ active: selectedCompanyId === item.id }" :class="{ active: selectedCompanyId === item.id }"
v-for="item in companyList" v-for="item in entityList"
:key="item.id" :key="item.id"
@click="selectedCompanyId = item.id" @click="selectedCompanyId = item.id"
> >
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
<div class="box"></div> <div class="box"></div>
<div class="text">科研仪器进口国分布</div> <div class="text">科研仪器进口国分布</div>
<div class="right-group"> <div class="right-group">
<el-select <!-- <el-select
v-model="activeInstrument" v-model="activeInstrument"
class="instrument-select" class="instrument-select"
placeholder="Select" placeholder="Select"
...@@ -82,7 +82,7 @@ ...@@ -82,7 +82,7 @@
:label="item" :label="item"
:value="item" :value="item"
/> />
</el-select> </el-select> -->
<div class="btn"> <div class="btn">
<img src="../../../../assets/数据库按钮.png" alt="" /> <img src="../../../../assets/数据库按钮.png" alt="" />
<img src="../../../../assets/下载按钮.png" alt="" /> <img src="../../../../assets/下载按钮.png" alt="" />
...@@ -107,13 +107,13 @@ ...@@ -107,13 +107,13 @@
<el-select <el-select
v-model="activeProjectDomain" v-model="activeProjectDomain"
class="project-domain-select" class="project-domain-select"
placeholder="Select" placeholder="全部领域"
> >
<el-option <el-option
v-for="item in projectDomainOptions" v-for="item in projectDomainOptions"
:key="item" :key="item.value"
:label="item" :label="item.label"
:value="item" :value="item.value"
/> />
</el-select> </el-select>
<div class="btn"> <div class="btn">
...@@ -140,13 +140,13 @@ ...@@ -140,13 +140,13 @@
<el-select <el-select
v-model="activePaperDomain" v-model="activePaperDomain"
class="paper-domain-select" class="paper-domain-select"
placeholder="Select" placeholder="全部领域"
> >
<el-option <el-option
v-for="item in paperDomainOptions" v-for="item in paperDomainOptions"
:key="item" :key="item.value"
:label="item" :label="item.label"
:value="item" :value="item.value"
/> />
</el-select> </el-select>
<div class="btn"> <div class="btn">
...@@ -171,11 +171,197 @@ ...@@ -171,11 +171,197 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, nextTick } from "vue"; import { ref, onMounted, nextTick, watch, onBeforeUnmount } from "vue";
import * as echarts from "echarts"; import * as echarts from "echarts";
import defaultTitle from "../../../../assets/default-icon2.png"; import defaultTitle from "../../../../assets/default-icon2.png";
import ai from "../../assets/ai.png"; import ai from "../../assets/ai.png";
import right from "../../assets/right.png"; import right from "../../assets/right.png";
import { getSingleSanctionEntityList, getSingleSanctionEntityRDInstrumentDependency, getSingleSanctionEntityRDInstrumentImportCountry, getSingleSanctionEntityInternationalCooperation,getSingleSanctionEntityInternationalPaper } from "@/api/exportControlV2.0";
const domainOptions = [
{ label: "全部领域", value: "" },
{ 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 activeInstrument = ref("电子测量仪器");
const instrumentOptions = ["电子测量仪器"];
const activeProjectDomain = ref("");
const projectDomainOptions = domainOptions;
const activePaperDomain = ref("");
const paperDomainOptions = domainOptions;
const searchDomain = ref("");
const selectedCompanyId = ref(null);
const sanRecordId = ref("");
const entityList = ref([]);
const internationalCooperation = ref([]);
const rdInstrumentImportCountry = ref([]);
const rdInstrumentDependency = ref([]);
// 单次制裁-影响分析-新增国际合著论文
const internationalPaper = ref([]);
const getInternationalPaper = async () => {
if (!selectedCompanyId.value) return;
try {
const params = {
id: selectedCompanyId.value
};
if (activePaperDomain.value) {
params.domainId = activePaperDomain.value;
}
const res = await getSingleSanctionEntityInternationalPaper(params);
if (res.code === 200) {
internationalPaper.value = res.data || [];
nextTick(() => {
initShareChart();
});
}
} catch (error) {
console.log(error);
}
}
// 单次制裁-影响分析-新增国际合作项目
const getInternationalCooperation = async () => {
if (!selectedCompanyId.value) return;
try {
const params = {
id: selectedCompanyId.value
};
if (activeProjectDomain.value) {
params.domainId = activeProjectDomain.value;
}
const res = await getSingleSanctionEntityInternationalCooperation(params);
if (res.code === 200) {
internationalCooperation.value = res.data || [];
nextTick(() => {
initRdChart();
});
}
} catch (error) {
console.log(error);
}
}
// 单次制裁-影响分析-科研仪器进口国分布
const getRDInstrumentImportCountry = async () => {
try {
const res = await getSingleSanctionEntityRDInstrumentImportCountry({
id: selectedCompanyId.value
});
if (res.code === 200) {
rdInstrumentImportCountry.value = res.data || [];
nextTick(() => {
initMarketChart();
});
}
} catch (error) {
console.log(error);
}
}
// 单次制裁-影响分析-科研仪器对美依赖情况
const getRDInstrumentDependency = async () => {
try {
const res = await getSingleSanctionEntityRDInstrumentDependency({
id: selectedCompanyId.value
});
if (res.code === 200) {
rdInstrumentDependency.value = res.data || [];
nextTick(() => {
initChart();
});
}
} catch (error) {
console.log(error);
}
}
// 单次制裁-影响分析-科研机构列表-查询
const getEntityList = async () => {
try {
const res = await getSingleSanctionEntityList({
sanRecordId: sanRecordId.value,
isOnlyCn: false,
domainId: searchDomain.value || undefined
});
if (res.code === 200) {
entityList.value = (res.data || []).reduce((acc, group) => {
if (group.orgType === "科研机构" && group.orgInfoList) {
acc.push(
...group.orgInfoList.map((org) => ({
id: org.id,
name: org.orgNameZh
}))
);
}
return acc;
}, []);
// 默认选中第一个
if (entityList.value.length > 0 && !selectedCompanyId.value) {
selectedCompanyId.value = entityList.value[0].id;
}
}
} catch (error) {
console.log(error);
}
};
watch(selectedCompanyId, (val) => {
if (val) {
getRDInstrumentDependency();
getRDInstrumentImportCountry();
getInternationalCooperation();
getInternationalPaper();
}
});
watch(activeProjectDomain, () => {
getInternationalCooperation();
});
watch(activePaperDomain, () => {
getInternationalPaper();
});
watch(searchDomain, () => {
getEntityList();
});
const getUrlParams = () => {
const urlParams = new URLSearchParams(window.location.search);
sanRecordId.value = urlParams.get("id") || ""
}
const chartRef = ref(null); const chartRef = ref(null);
const marketChartRef = ref(null); const marketChartRef = ref(null);
...@@ -183,17 +369,14 @@ const rdChartRef = ref(null); ...@@ -183,17 +369,14 @@ const rdChartRef = ref(null);
const shareChartRef = ref(null); const shareChartRef = ref(null);
const initChart = () => { const initChart = () => {
if (!chartRef.value) return; if (!chartRef.value || rdInstrumentDependency.value.length === 0) return;
const myChart = echarts.init(chartRef.value); const myChart = echarts.getInstanceByDom(chartRef.value) || echarts.init(chartRef.value);
const data = [
{ name: "电子测量仪器", value: 109 }, // 处理接口数据,映射为图表所需的 name 和 value
{ name: "物理性能测试仪器", value: 95 }, const chartData = rdInstrumentDependency.value.map(item => ({
{ name: "激光器", value: 79 }, name: item.name,
{ name: "分析仪器", value: 25 }, value: item.count
{ name: "计量仪器", value: 21 }, }));
{ name: "计算机及其配套设备", value: 10 },
{ name: "地球探测仪器", value: 10 }
];
const option = { const option = {
grid: { grid: {
...@@ -203,6 +386,15 @@ const initChart = () => { ...@@ -203,6 +386,15 @@ const initChart = () => {
bottom: "0%", bottom: "0%",
containLabel: true containLabel: true
}, },
dataZoom: [
{
type: "inside",
orient: "vertical",
start: 0,
end: chartData.length > 7 ? (7 / chartData.length) * 100 : 100,
zoomLock: true
}
],
xAxis: { xAxis: {
show: false, show: false,
type: "value" type: "value"
...@@ -210,7 +402,7 @@ const initChart = () => { ...@@ -210,7 +402,7 @@ const initChart = () => {
yAxis: [ yAxis: [
{ {
type: "category", type: "category",
data: data.map((item) => item.name), data: chartData.map((item) => item.name),
axisLine: { show: false }, axisLine: { show: false },
axisTick: { show: false }, axisTick: { show: false },
axisLabel: { axisLabel: {
...@@ -225,7 +417,7 @@ const initChart = () => { ...@@ -225,7 +417,7 @@ const initChart = () => {
}, },
{ {
type: "category", type: "category",
data: data.map((item) => item.value), data: chartData.map((item) => item.value),
axisLine: { show: false }, axisLine: { show: false },
axisTick: { show: false }, axisTick: { show: false },
axisLabel: { axisLabel: {
...@@ -245,7 +437,7 @@ const initChart = () => { ...@@ -245,7 +437,7 @@ const initChart = () => {
series: [ series: [
{ {
type: "bar", type: "bar",
data: data.map((item, index) => { data: chartData.map((item, index) => {
return { return {
value: item.value, value: item.value,
itemStyle: { itemStyle: {
...@@ -278,23 +470,17 @@ const initChart = () => { ...@@ -278,23 +470,17 @@ const initChart = () => {
] ]
}; };
myChart.setOption(option); myChart.setOption(option);
window.addEventListener("resize", () => {
myChart.resize();
});
}; };
const initMarketChart = () => { const initMarketChart = () => {
if (!marketChartRef.value) return; if (!marketChartRef.value || rdInstrumentImportCountry.value.length === 0) return;
const myChart = echarts.init(marketChartRef.value); const myChart = echarts.getInstanceByDom(marketChartRef.value) || echarts.init(marketChartRef.value);
const data = [
{ name: "美国", value: 27 }, const chartData = rdInstrumentImportCountry.value.map(item => ({
{ name: "日本", value: 22 }, name: item.name,
{ name: "德国", value: 18 }, value: item.count
{ name: "英国", value: 15 }, }));
{ name: "韩国", value: 12 },
{ name: "荷兰", value: 8 },
{ name: "其他", value: 7 }
];
const colors = [ const colors = [
"#66b1ff", "#66b1ff",
"#ffba63", "#ffba63",
...@@ -312,14 +498,18 @@ const initMarketChart = () => { ...@@ -312,14 +498,18 @@ const initMarketChart = () => {
formatter: "{b}: {c}%" formatter: "{b}: {c}%"
}, },
legend: { legend: {
type: "scroll",
orient: "vertical", orient: "vertical",
right: "15%", right: "5%",
top: "middle", top: "middle",
bottom: "20",
itemGap: 20, itemGap: 20,
icon: "circle", icon: "circle",
formatter: function (name) { formatter: function (name) {
const item = data.find((i) => i.name === name); const item = chartData.find((i) => i.name === name);
return `{name|${name}} {value|${item.value}%}`; const total = chartData.reduce((sum, i) => sum + i.value, 0);
const percent = total > 0 ? ((item.value / total) * 100).toFixed(0) : 0;
return `{name|${name}} {value|${percent}%}`;
}, },
textStyle: { textStyle: {
rich: { rich: {
...@@ -357,32 +547,25 @@ const initMarketChart = () => { ...@@ -357,32 +547,25 @@ const initMarketChart = () => {
labelLine: { labelLine: {
show: false show: false
}, },
data: data data: chartData
} }
] ]
}; };
myChart.setOption(option); myChart.setOption(option);
window.addEventListener("resize", () => {
myChart.resize();
});
}; };
const initRdChart = () => { const initRdChart = () => {
if (!rdChartRef.value) return; if (!rdChartRef.value) return;
const myChart = echarts.init(rdChartRef.value); const myChart = echarts.getInstanceByDom(rdChartRef.value) || echarts.init(rdChartRef.value);
const data = [6, 7, 6, 6, 6, 7, 7, 6, 5, 4];
const years = [ // 处理接口返回的数据,按年份升序排序
"2016", const chartData = [...internationalCooperation.value].sort((a, b) => a.year - b.year);
"2017", const years = chartData.map((item) => item.year.toString());
"2018", const data = chartData.map((item) => item.count);
"2019",
"2020", // 动态计算 Y 轴最大值和间隔
"2021", const maxVal = Math.max(...data, 10);
"2022", const yMax = Math.ceil(maxVal / 5) * 5;
"2023",
"2024",
"2025"
];
const option = { const option = {
tooltip: { tooltip: {
...@@ -417,8 +600,8 @@ const initRdChart = () => { ...@@ -417,8 +600,8 @@ const initRdChart = () => {
yAxis: { yAxis: {
type: "value", type: "value",
min: 0, min: 0,
max: 10, max: yMax,
interval: 2, interval: yMax / 5,
axisLabel: { axisLabel: {
color: "#606266", color: "#606266",
fontSize: 12, fontSize: 12,
...@@ -452,54 +635,25 @@ const initRdChart = () => { ...@@ -452,54 +635,25 @@ const initRdChart = () => {
color: "rgba(5, 95, 194, 0)" 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); myChart.setOption(option);
window.addEventListener("resize", () => {
myChart.resize();
});
}; };
const initShareChart = () => { const initShareChart = () => {
if (!shareChartRef.value) return; if (!shareChartRef.value) return;
const myChart = echarts.init(shareChartRef.value); const myChart = echarts.getInstanceByDom(shareChartRef.value) || echarts.init(shareChartRef.value);
const data = [61, 65, 59, 46, 46, 41, 46, 51, 21, 24];
const years = [ // 处理接口返回的数据,按年份升序排序
"2016", const chartData = [...internationalPaper.value].sort((a, b) => a.year - b.year);
"2017", const years = chartData.map((item) => item.year.toString());
"2018", const data = chartData.map((item) => item.count);
"2019",
"2020", // 动态计算 Y 轴最大值和间隔
"2021", const maxVal = Math.max(...data, 10);
"2022", const yMax = Math.ceil(maxVal / 5) * 5;
"2023",
"2024",
"2025"
];
const option = { const option = {
tooltip: { tooltip: {
...@@ -534,8 +688,8 @@ const initShareChart = () => { ...@@ -534,8 +688,8 @@ const initShareChart = () => {
yAxis: { yAxis: {
type: "value", type: "value",
min: 0, min: 0,
max: 100, max: yMax,
interval: 20, interval: yMax / 5,
axisLabel: { axisLabel: {
color: "#606266", color: "#606266",
fontSize: 12, fontSize: 12,
...@@ -569,64 +723,35 @@ const initShareChart = () => { ...@@ -569,64 +723,35 @@ const initShareChart = () => {
color: "rgba(5, 95, 194, 0)" 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); myChart.setOption(option);
window.addEventListener("resize", () => {
myChart.resize();
});
}; };
onMounted(() => { const handleResize = () => {
initChart(); [chartRef, marketChartRef, rdChartRef, shareChartRef].forEach(refItem => {
initMarketChart(); if (refItem.value) {
initRdChart(); const myChart = echarts.getInstanceByDom(refItem.value);
initShareChart(); if (myChart) {
}); myChart.resize();
}
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([ onMounted(async () => {
{ id: 1, name: "空天信息创新研究院" }, getUrlParams();
{ id: 2, name: "中国科学院国家授时中心" } // 单次制裁-影响分析-科研机构列表-查询 (等待获取到默认的 selectedCompanyId 后会触发 watch 加载其他数据)
]); await getEntityList();
window.addEventListener("resize", handleResize);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", handleResize);
});
</script> </script>
......
...@@ -16,38 +16,40 @@ ...@@ -16,38 +16,40 @@
<div class="label">发布机构:</div> <div class="label">发布机构:</div>
<div class="value link"> <div class="value link">
<img :src="title" alt="" class="icon"> <img :src="title" alt="" class="icon">
<span>商务部工业与安全局 ></span> <span>{{ formattedData.postOrgName }} ></span>
</div> </div>
</div> </div>
<div class="info-row"> <div class="info-row">
<div class="label">发布时间:</div> <div class="label">发布时间:</div>
<div class="value">2025年9月16日</div> <div class="value">{{ formattedData.postDate }}</div>
</div> </div>
<div class="info-row"> <div class="info-row">
<div class="label">生效时间:</div> <div class="label">生效时间:</div>
<div class="value">2025年9月12日</div> <div class="value">{{ formattedData.effectiveDate }}</div>
</div> </div>
<div class="info-row"> <div class="info-row">
<div class="label">发布文件:</div> <div class="label">发布文件:</div>
<div class="value">2025-17893 (90 FR 44496)</div> <div class="value">{{ formattedData.fileCode }}</div>
</div> </div>
<div class="info-row"> <div class="info-row">
<div class="label">案卷号:</div> <div class="label">案卷号:</div>
<div class="value">No. 250912-0152</div> <div class="value">{{ formattedData.administrativeOrderId }}</div>
</div> </div>
<div class="info-row"> <div class="info-row">
<div class="label">发布人:</div> <div class="label">发布人:</div>
<div class="value link"> <div class="value link">
<img :src="defaultTitle" alt="" class="icon avatar"> <img :src="defaultTitle" alt="" class="icon avatar">
<span>朱莉哑·A·赫尔松斯基 (战略贸易副助理部长) ></span> <span @click="handleClick">{{ formattedData.postPersonName }} ></span>
</div> </div>
</div> </div>
<div class="info-row"> <div class="info-row">
<div class="label">制裁领域:</div> <div class="label">制裁领域:</div>
<div class="value tags"> <div class="value tags">
<span class="tag">量子科技</span> <span
<span class="tag">人工智能</span> class="tag"
<span class="tag">集成电路</span> v-for="(domain, index) in formattedData.domains"
:key="index"
>{{ domain }}</span>
</div> </div>
</div> </div>
</div> </div>
...@@ -98,7 +100,7 @@ ...@@ -98,7 +100,7 @@
<div class="content">{{ item.content }}</div> <div class="content">{{ item.content }}</div>
</div> </div>
</div> </div>
<div class="view-more"> <div class="view-more" v-if="hasMore" @click="loadMore">
查看更多 <el-icon class="icon-more"><DArrowRight /></el-icon> 查看更多 <el-icon class="icon-more"><DArrowRight /></el-icon>
</div> </div>
</div> </div>
...@@ -115,15 +117,16 @@ ...@@ -115,15 +117,16 @@
</div> </div>
<div class="right-title"> <div class="right-title">
<div class="filter-row"> <div class="filter-row">
<div class="filter-left"> <!-- <div class="filter-left">
<el-select v-model="filterEntity" placeholder="受制裁实体" style="width: 184px"> <el-select v-model="filterEntity" placeholder="受制裁实体" style="width: 184px">
<el-option label="受制裁实体" value="1" /> <el-option label="受制裁实体" value="1" />
</el-select> </el-select>
</div> </div> -->
<div class="filter-right"> <div class="filter-right">
<el-checkbox v-model="onlyChina" label="只看中国实体" /> <el-checkbox v-model="onlyChina" label="只看中国实体" />
<el-select v-model="filterField" placeholder="全部领域" style="width: 150px; margin: 0 12px 0 16px"> <el-select v-model="filterField" placeholder="全部领域" style="width: 150px; margin: 0 12px 0 16px">
<el-option label="全部领域" value="1" /> <el-option label="全部领域" value="" />
<el-option v-for="item in domainOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
<el-input <el-input
v-model="searchKeyword" v-model="searchKeyword"
...@@ -134,20 +137,20 @@ ...@@ -134,20 +137,20 @@
</div> </div>
</div> </div>
<div class="stats-row"> <div class="stats-row">
<div class="tabs"> <!-- <div class="tabs">
<div class="tab-btn" :class="{ active: activeTab === 'add' }" @click="activeTab = 'add'">新增实体</div> <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 class="tab-btn" :class="{ active: activeTab === 'remove' }" @click="activeTab = 'remove'">移除实体</div>
</div> </div> -->
<div class="stats-info"> <div class="stats-info">
<div class="stat-item"> <div class="stat-item">
<span class="dot red"></span> <span class="dot red"></span>
<span class="text">新增 <span class="num red">24</span> 家 (50%规则涉及<span class="num red">51</span>家)</span> <span class="text">新增 <span class="num red">{{ addCount }}</span> 家 (50%规则涉及<span class="num red">{{ addRuleCount }}</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 class="stat-item">
<span class="dot green"></span>
<span class="text">移除 <span class="num green">{{ removeCount }}</span> 家 (50%规则涉及<span class="num green">{{ removeRuleCount }}</span>家)</span>
</div> -->
</div>
</div> </div>
</div> </div>
<div class="right-content"> <div class="right-content">
...@@ -166,14 +169,20 @@ ...@@ -166,14 +169,20 @@
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="涉及领域" width="120" align="center"> <el-table-column label="涉及领域" width="180" align="center">
<template #default="scope"> <template #default="scope">
<span class="tag" :style="getTagStyle(scope.row.field)">{{ scope.row.field }}</span> <span
v-for="(item, index) in scope.row.fields"
:key="index"
class="tag"
:style="getTagStyle(item)"
style="margin: 0 2px;"
>{{ item }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="location" label="上市地点" width="100" align="center" /> <el-table-column prop="location" label="上市地点" width="90" align="center" />
<el-table-column prop="date" label="制裁时间" width="120" align="center" /> <el-table-column prop="date" label="制裁时间" width="150" align="center" />
<el-table-column prop="revenue" label="营收(亿元)" width="120" align="center" /> <el-table-column prop="revenue" label="营收(亿元)" width="110" align="center" />
<el-table-column label="50%规则子企业" width="180" align="center"> <el-table-column label="50%规则子企业" width="180" align="center">
<template #default="scope"> <template #default="scope">
<span v-if="scope.row.subsidiaryCount" class="subsidiary-link"> <span v-if="scope.row.subsidiaryCount" class="subsidiary-link">
...@@ -195,18 +204,220 @@ ...@@ -195,18 +204,220 @@
</template> </template>
<script setup> <script setup>
import { ref } from "vue"; import { ref, defineProps, computed, onMounted, watch } from "vue";
import { useRouter } from "vue-router";
import { DArrowRight, Search } from "@element-plus/icons-vue"; import { DArrowRight, Search } from "@element-plus/icons-vue";
import { debounce } from "lodash";
import title from "../../assets/title.png" import title from "../../assets/title.png"
import defaultTitle from "../../assets/default-icon1.png" import defaultTitle from "../../assets/default-icon1.png"
import flag from "../../assets/default-icon2.png" import flag from "../../assets/default-icon2.png"
import { getSingleSanctionEntityCountry, getSingleSanctionBackground, getSingleSanctionOverviewList } from "@/api/exportControlV2.0";
// 单次制裁-制裁概况-制裁清单
const sanctionList = ref([])
const addCount = ref(0)
const addRuleCount = ref(0)
const removeCount = ref(0)
const removeRuleCount = ref(0)
// 调用单次制裁-制裁概况-制裁清单接口
const getSanctionOverviewList = async () => {
try {
const res = await getSingleSanctionOverviewList({
sanRecordId: sanRecordId.value,
isOnlyCn: onlyChina.value,
domainId: filterField.value || undefined,
searchText: searchKeyword.value || undefined,
})
if (res.code === 200) {
const data = res.data || {}
addCount.value = data.addCount || 0
addRuleCount.value = data.addRuleCount || 0
removeCount.value = data.removeCount || 0
removeRuleCount.value = data.removeRuleCount || 0
const list = data.sanList || []
sanctionList.value = list.map(item => ({
reason: item.sanReason,
entities: (item.orgList || []).map(org => ({
name: org.entityNameZh || org.entityName,
fields: org.techDomains || [],
location: "--", // 接口暂无数据
date: org.startTime ? org.startTime.replace(/^(\d{4})-(\d{2})-(\d{2}).*$/, '$1年$2月$3日') : '--', // 格式化日期
revenue: "--", // 接口暂无数据
subsidiaryCount: org.ruleOrgCount || 0,
subsidiaryText: org.ruleOrgList && org.ruleOrgList.length > 0
? (org.ruleOrgList[0].orgName.length > 10 ? org.ruleOrgList[0].orgName.slice(0, 10) + '...' : org.ruleOrgList[0].orgName) + '...等'
: ''
}))
}))
}
} catch (error) {
console.log(error)
}
}
// 单次制裁-制裁概况-制裁背景
const timelinePage = ref(1);
const timelinePageSize = ref(3);
const hasMore = ref(true);
const getSanctionBackground = async (isLoadMore = false) => {
try {
const res = await getSingleSanctionBackground({
sanRecordId: sanRecordId.value,
pageNum: timelinePage.value,
pageSize: timelinePageSize.value,
})
if (res.code === 200) {
const list = res.data.content || [];
const formattedList = list.map(item => ({
...item,
date: item.createTime ? item.createTime.split(' ')[0] : '', // 提取日期部分
content: item.summaryZh
}));
if (isLoadMore) {
timelineData.value = [...timelineData.value, ...formattedList];
} else {
timelineData.value = formattedList;
}
// 判断是否还有更多数据
const totalElements = res.data.totalElements || 0;
hasMore.value = timelineData.value.length < totalElements;
}
} catch (error) {
console.log(error)
}
}
const loadMore = () => {
timelinePage.value++;
getSanctionBackground(true);
};
const router = useRouter();
const sanRecordId = ref("")
const getUrlParams = () => {
const urlParams = new URLSearchParams(window.location.search);
sanRecordId.value = urlParams.get("id") || ""
}
// 单次制裁-制裁概况-制裁实体国家分布
const getEntityCountry = async () => {
try {
const res = await getSingleSanctionEntityCountry({
sanRecordId: sanRecordId.value
})
if (res.code === 200) {
const rawData = res.data || [];
const maxCount = Math.max(...rawData.map(item => item.count || 0), 1);
// 颜色池 - 用于循环分配
const gradientPool = [
"linear-gradient(270deg, rgba(255,170,0,1) 0%, rgba(255,255,255,0) 100%)", // 橙
"linear-gradient(270deg, rgba(5,95,194,1) 0%, rgba(255,255,255,0) 100%)", // 蓝
"linear-gradient(270deg, rgba(114,46,209,1) 0%, rgba(255,255,255,0) 100%)", // 紫
"linear-gradient(270deg, rgba(16,178,166,1) 0%, rgba(255,255,255,0) 100%)" // 青
];
// 固定颜色映射 - 特定国家使用固定颜色
const fixedColorMap = {
"中国": "linear-gradient(270deg, rgba(206,79,81,1) 0%, rgba(255,255,255,0) 100%)" // 红
};
entityDistribution.value = rawData.map((item, index) => ({
...item,
width: `${((item.count || 0) / maxCount) * 100}%`,
// 优先使用固定颜色,否则循环使用颜色池中的颜色
gradient: fixedColorMap[item.name] || gradientPool[index % gradientPool.length]
}));
}
} catch (error) {
console.log(error)
}
}
const props = defineProps({
data: {
type: Object,
default: () => ({})
}
});
// 跳转到人物页
const handleClick = () => {
const route = router.resolve({
path: "/characterPage",
query: {
// type: props.data.type,
personId: props.data.postPersonId
}
});
window.open(route.href, "_blank");
};
// 计算属性处理数据
const formattedData = computed(() => {
const info = props.data || {};
// 日期格式化
const formatDate = (dateStr) => {
if (!dateStr) return '';
const date = new Date(dateStr);
if (isNaN(date.getTime())) return dateStr;
return `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}日`;
};
return {
postOrgName: info.postOrgName,
postDate: formatDate(info.postDate),
effectiveDate: formatDate(info.effectiveDate),
fileCode: info.fileCode ? `${info.fileCode} ` : '',
administrativeOrderId: info.administrativeOrderId ? `No. ${info.administrativeOrderId}` : '',
postPersonName: info.postPersonName,
domains: info.domainNames
};
});
const filterEntity = ref('') const filterEntity = ref('')
const onlyChina = ref(true) const onlyChina = ref(false)
const filterField = ref('') const filterField = ref('')
const searchKeyword = ref('') const searchKeyword = ref('')
const activeTab = ref('add') const activeTab = ref('add')
// 监听筛选条件变化
watch([onlyChina, filterField], () => {
getSanctionOverviewList()
})
// 搜索框防抖
const debouncedSearch = debounce(() => {
getSanctionOverviewList()
}, 500)
watch(searchKeyword, () => {
debouncedSearch()
})
const domainOptions = ref([
{ 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 tagColors = [ const tagColors = [
{ bg: 'rgb(242, 235, 255)', border: 'rgb(211, 190, 255)', text: 'rgb(114, 46, 209)' }, // Purple { 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(225, 250, 248)', border: 'rgb(178, 242, 238)', text: 'rgb(16, 178, 166)' }, // Cyan
...@@ -231,282 +442,7 @@ const getTagStyle = (tagName) => { ...@@ -231,282 +442,7 @@ const getTagStyle = (tagName) => {
}; };
} }
const sanctionList = ref([ const timelineData = 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([ const entityDistribution = ref([
{ {
...@@ -534,6 +470,17 @@ const entityDistribution = ref([ ...@@ -534,6 +470,17 @@ const entityDistribution = ref([
gradient: "linear-gradient(270deg, rgba(5,95,194,1) 0%, rgba(255,255,255,0) 100%)" gradient: "linear-gradient(270deg, rgba(5,95,194,1) 0%, rgba(255,255,255,0) 100%)"
} }
]) ])
onMounted(() => {
// 获取URL参数
getUrlParams()
// 单次制裁-制裁概况-制裁实体国家分布
getEntityCountry()
// 单次制裁-制裁概况-制裁背景
getSanctionBackground()
// 单次制裁-制裁概况-制裁清单
getSanctionOverviewList()
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
...@@ -563,7 +510,7 @@ const entityDistribution = ref([ ...@@ -563,7 +510,7 @@ const entityDistribution = ref([
background-color: #fff; background-color: #fff;
margin-bottom: 16px; margin-bottom: 16px;
.left-top-title { .left-top-title {
padding: 22px 0 22px 27px; padding: 22px 20px 22px 27px;
width: 100%; width: 100%;
height: 286px; height: 286px;
border-bottom: 1px solid rgb(234, 236, 238); border-bottom: 1px solid rgb(234, 236, 238);
...@@ -597,6 +544,7 @@ const entityDistribution = ref([ ...@@ -597,6 +544,7 @@ const entityDistribution = ref([
font-size: 16px; font-size: 16px;
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
line-height: 24px; line-height: 24px;
overflow: hidden; // Ensure it respects the container width
&.link { &.link {
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
...@@ -618,8 +566,24 @@ const entityDistribution = ref([ ...@@ -618,8 +566,24 @@ const entityDistribution = ref([
&.tags { &.tags {
gap: 8px; gap: 8px;
overflow-x: auto; // Allow horizontal scrolling
white-space: nowrap; // Prevent wrapping
padding-bottom: 4px; // Add some space for scrollbar
/* Custom Scrollbar */
&::-webkit-scrollbar {
height: 4px;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 2px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
.tag { .tag {
flex-shrink: 0; // Prevent tags from shrinking
padding: 1px 8px; padding: 1px 8px;
background: rgba(246, 250, 255, 1); background: rgba(246, 250, 255, 1);
border-radius: 4px; border-radius: 4px;
...@@ -636,7 +600,7 @@ const entityDistribution = ref([ ...@@ -636,7 +600,7 @@ const entityDistribution = ref([
width: 100%; width: 100%;
height: 234px; height: 234px;
padding: 19px 29px 22px 27px; padding: 19px 29px 22px 27px;
overflow: auto;
.content-title { .content-title {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
...@@ -651,7 +615,7 @@ const entityDistribution = ref([ ...@@ -651,7 +615,7 @@ const entityDistribution = ref([
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 16px;
overflow: auto;
.list-item { .list-item {
display: flex; display: flex;
align-items: center; align-items: center;
...@@ -718,14 +682,32 @@ const entityDistribution = ref([ ...@@ -718,14 +682,32 @@ const entityDistribution = ref([
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background-color: #fff; background-color: #fff;
.left-bottom-content { .left-bottom-content {
padding: 26px 30px 0 25px; padding: 26px 30px 0 25px;
height: calc(100% - 56px); // 减去标题高度
.timeline-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 24px;
margin-bottom: 24px; .timeline-list {
} display: flex;
flex-direction: column;
gap: 24px;
margin-bottom: 24px;
overflow-y: auto; // 允许垂直滚动
flex: 1; // 占据剩余空间
padding-right: 10px; // 防止滚动条遮挡内容
/* Custom Scrollbar */
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 3px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
.timeline-item { .timeline-item {
display: flex; display: flex;
...@@ -819,7 +801,8 @@ const entityDistribution = ref([ ...@@ -819,7 +801,8 @@ const entityDistribution = ref([
.filter-row { .filter-row {
display: flex; display: flex;
justify-content: space-between; // 隐藏使用right,解除使用space-between
justify-content: right;
align-items: center; align-items: center;
margin-bottom: 20px; margin-bottom: 20px;
...@@ -851,7 +834,8 @@ const entityDistribution = ref([ ...@@ -851,7 +834,8 @@ const entityDistribution = ref([
.stats-row { .stats-row {
display: flex; display: flex;
justify-content: space-between; // 隐藏使用right,解除使用space-between
justify-content: right;
align-items: center; align-items: center;
.tabs { .tabs {
...@@ -1073,4 +1057,4 @@ const entityDistribution = ref([ ...@@ -1073,4 +1057,4 @@ const entityDistribution = ref([
} }
} }
} }
</style> </style>
\ No newline at end of file
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<div> <div>
<div class="title"> <div class="title">
{{ headerTitle.title }} {{ headerTitle.title }}
<span>{{ headerTitle.titleEn }}</span> <!-- <span>{{ headerTitle.titleEn }}</span> -->
</div> </div>
<div class="department"> <div class="department">
{{ headerTitle.department }} {{ headerTitle.department }}
...@@ -35,7 +35,10 @@ ...@@ -35,7 +35,10 @@
</div> </div>
</div> </div>
<div class="main"> <div class="main">
<sanctions-overview v-if="activeIndex === 0"></sanctions-overview> <sanctions-overview
v-if="activeIndex === 0"
:data="singleSanctionOverview"
></sanctions-overview>
<data-statistics v-if="activeIndex === 1"></data-statistics> <data-statistics v-if="activeIndex === 1"></data-statistics>
<deep-mining v-if="activeIndex === 2"></deep-mining> <deep-mining v-if="activeIndex === 2"></deep-mining>
<impact-analysis v-if="activeIndex === 3"></impact-analysis> <impact-analysis v-if="activeIndex === 3"></impact-analysis>
...@@ -44,7 +47,7 @@ ...@@ -44,7 +47,7 @@
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref, onMounted } from 'vue'
import sanctionsOverview from "./components/sanctionsOverview/index.vue" import sanctionsOverview from "./components/sanctionsOverview/index.vue"
import dataStatistics from "./components/dataStatistics/index.vue" import dataStatistics from "./components/dataStatistics/index.vue"
...@@ -62,12 +65,55 @@ import icon2Active from "../assets/icons/icon2_active.png"; ...@@ -62,12 +65,55 @@ import icon2Active from "../assets/icons/icon2_active.png";
import icon3 from "../assets/icons/icon3.png"; import icon3 from "../assets/icons/icon3.png";
import icon3Active from "../assets/icons/icon3_active.png"; import icon3Active from "../assets/icons/icon3_active.png";
import { getSingleSanctionOverview } from "@/api/exportControlV2.0.js"
// 获取URL参数
const sanRecordId = ref("")
const getUrlParams = () => {
const urlParams = new URLSearchParams(window.location.search);
sanRecordId.value = urlParams.get("id") || ""
}
// console.log(sanRecordId.value)
// 单次制裁-制裁概况-基本信息
const singleSanctionOverview = ref({})
const getSingleSanctionOverviewData = async () => {
if (!sanRecordId.value) return
try {
const res = await getSingleSanctionOverview({
sanRecordId: sanRecordId.value
})
if (res.code === 200) {
singleSanctionOverview.value = res.data || {}
// 格式化日期
let dateStr = "";
if (singleSanctionOverview.value.postDate) {
const date = new Date(singleSanctionOverview.value.postDate);
if (!isNaN(date.getTime())) {
dateStr = `${date.getFullYear()}${date.getMonth() + 1}${date.getDate()}日`;
} else {
dateStr = singleSanctionOverview.value.postDate;
}
}
// 更新头部信息
headerTitle.value = {
...headerTitle.value,
title: `${dateStr}${singleSanctionOverview.value.sanTitleZh || singleSanctionOverview.value.sanTitle}》`,
titleEn: singleSanctionOverview.value.sanTitle || "",
department: singleSanctionOverview.value.fileCode || ""
}
}
} catch (error) {
console.error("获取制裁概况失败:", error)
}
}
const headerTitle = ref({ const headerTitle = ref({
img: title, img: title,
title: "2025年9月12日《对实体清单的添加和修订 》",
titleEn: "",
department: "2025-17893(90 FR 44496)"
}) })
const activeIndex = ref(0) const activeIndex = ref(0)
...@@ -96,7 +142,10 @@ const headerNavList = ref([ ...@@ -96,7 +142,10 @@ const headerNavList = ref([
]) ])
onMounted(() => {
getUrlParams()
getSingleSanctionOverviewData()
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论