提交 dd2b632a authored 作者: yanpeng's avatar yanpeng

finance api

上级 b9c11ff5
......@@ -498,14 +498,15 @@ export function getEntitiesUpdateCount(sanTypeId = 1) {
* @param {string} rule - 规则
* @param {string} type - 类型
*/
export function getSanDomainCount(rule, type) {
export function getSanDomainCount(rule, sanTypeIds, type) {
return request200(
request({
method: "GET",
url: "/api/entitiesDataCount/getSanDomainCount",
params: {
rule,
type
sanTypeIds
// type
}
})
);
......
......@@ -136,3 +136,27 @@ export function getRelateNews(sanRecordId) {
export function getReasonAndSan(sanRecordId) {
return http.get(`/api/sanctionList/invFin/getReasonAndSan?sanRecordId=${sanRecordId}`);
}
/**
* 制裁历程
* url:/entitiesDataCount/getSanRecord
*/
export function getSanRecord(params) {
return http.get("/api/entitiesDataCount/getSanRecord", params);
}
/**
* 限制关系分析-限制举措关系图
* url:/sanctionList/invFin/recordRelation
*/
export function getRecordRelation(sanRecordIds) {
return http.get(`/api/sanctionList/invFin/recordRelation?sanRecordIds=${sanRecordIds}`);
}
/**
* 查询投融资限制关联-图谱-节点详情
* url:/sanctionList/invFin/getVertexInfo
*/
export function getVertexInfo(sanRecordId) {
return http.get(`/api/sanctionList/invFin/getVertexInfo?sanRecordId=${sanRecordId}`);
}
\ No newline at end of file
......@@ -1231,7 +1231,7 @@ const radarOption = ref({
// 获取雷达图数据
const fetchRadarData = async checked => {
try {
const data = await getSanDomainCount(checked, "export");
const data = await getSanDomainCount(checked, allSanTypeIds.value.join(","));
if (data && Array.isArray(data) && data.length > 0) {
// 收集所有可能的领域名称
const allDomains = new Set();
......@@ -1817,15 +1817,6 @@ const handleMediaClick = item => {
</script>
<style lang="scss" scoped>
// * {
// margin: 0;
// padding: 0;
// }
:deep(.el-input__wrapper) {
// box-shadow: none;
}
.list-page {
padding-top: 0;
}
......@@ -3278,10 +3269,10 @@ const handleMediaClick = item => {
}
.text {
font-size: 20px;
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
font-family: "Source Han Sans CN";
line-height: 24px;
color: rgb(5, 95, 194);
}
}
......
......@@ -2,14 +2,14 @@
<div class="list-page">
<div class="search-box">
<div style="display: flex; justify-content: center">
<el-select v-model="currentCCLVersion" style="width: 388px; height: 32px; margin-right: 14px">
<el-select v-model="currentCCLVersion" style="width: 360px; height: 32px; margin-right: 14px">
<el-option v-for="item in cclVersionList" :key="item.key" :label="item.value" :value="item.key" />
</el-select>
<el-input v-model="searchKeyword" class="search-input" placeholder="搜索物项或ECCN编码" :suffix-icon="Search" />
</div>
<div class="filters">
<el-checkbox v-model="viewNew" label="查看最近更新内容" />
<el-select placeholder="全部类别" v-model="currentCCLType" style="width: 388px; height: 32px; margin-right: 14px">
<el-select placeholder="全部类别" v-model="currentCCLType" style="width: 360px; height: 32px; margin-right: 14px">
<el-option v-for="item in CCLTypeList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</div>
......@@ -381,14 +381,15 @@ onMounted(async () => {
align-items: center;
.search-input {
width: 388px;
width: 360px;
height: 32px;
:deep(.el-input__wrapper) {
padding: 0 11px;
border: 1.5px solid #dcdfe6;
// border: 1.5px solid #dcdfe6;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
}
:deep(.el-input__inner) {
......@@ -439,25 +440,26 @@ onMounted(async () => {
.left {
padding-bottom: 20px;
width: 388px;
width: 360px;
height: auto;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background-color: #fff;
.checkbox-group {
display: flex;
flex-wrap: wrap;
padding: 0 0 0 24px;
display: grid;
grid-template-columns: repeat(2, 160px);
gap: 8px 4px;
padding-left: 24px;
.el-checkbox {
width: 50%;
margin-right: 0;
margin-bottom: 4px;
// margin-bottom: 4px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
height: 24px;
color: rgb(95, 101, 108);
}
......@@ -504,7 +506,7 @@ onMounted(async () => {
}
.right {
width: 1196px;
width: 1223px;
height: auto;
.title {
width: 100%;
......
......@@ -514,11 +514,6 @@ watch(customDateRange, () => {
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.list-page {
width: 1601px;
padding-bottom: 50px;
......@@ -532,7 +527,7 @@ watch(customDateRange, () => {
align-items: center;
.search-input {
width: 388px;
width: 360px;
height: 32px;
:deep(.el-input__wrapper) {
......@@ -583,7 +578,7 @@ watch(customDateRange, () => {
.left {
padding-bottom: 20px;
width: 388px;
width: 360px;
height: auto;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
......@@ -654,7 +649,7 @@ watch(customDateRange, () => {
}
.right {
width: 1196px;
width: 1223px;
height: auto;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
......
<template>
<div class="main main-association">
<div class="left">
<!-- ... 左侧代码保持不变 ... -->
<AnalysisBox title="制裁历程">
<div class="left-main">
<div class="date-picker-box">
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator="--"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
style="width: 100%"
:clearable="false"
@change="handleDateChange"
/>
</div>
<div class="list-header">
<el-checkbox v-model="isAllSelected" :indeterminate="indeterminate" @change="handleCheckAllChange">
全选
</el-checkbox>
<div class="count">{{ sanctionList.length }}次制裁</div>
</div>
<div class="list-content" v-loading="loading">
<div class="list-item" v-for="item in sanctionList" :key="item.id" @click="handleSanctionSelect(item.id)">
<el-checkbox v-model="item.checked" @change="val => handleCheckOneChange(val, item)" @click.stop>
<div class="item-label">
<div class="item-left">{{ item.date }}-{{ "SDN清单更新" }}</div>
<div class="item-right">{{ item.count }}{{ item.unit }}</div>
</div>
</el-checkbox>
</div>
</div>
<el-button type="primary" @click="handleAssociationClick" style="height: 36px">
多投融资限制举措关联分析
<el-icon><Right /></el-icon>
</el-button>
</div>
</AnalysisBox>
</div>
<div class="right">
<AnalysisBox title="投融资限制举措关系图">
<div class="right-main">
<div class="relation-empty" v-if="selectedSanctionIds.length == 0">
<el-empty :image="emptyImg" :image-size="200">
<template #description>
<div class="empty">请在左侧勾选多次投融资限制制裁后点击“开始分析”查看结果</div>
</template>
</el-empty>
</div>
<div class="relation-content" v-else>
<!-- 修改点:绑定转换后的 graphNodes 和 graphLinks -->
<GraphChart :nodes="graphNodes" :links="graphLinks" />
</div>
</div>
</AnalysisBox>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick, onUnmounted, computed } from "vue";
import defaultTitle from "../../../assets/default-icon2.png";
import GraphChart from "@/components/base/GraphChart/index.vue";
import emptyImg from "../assets/empty.png";
import markIcon from "../assets/icon-mark.png";
import { getSanRecord, getRecordRelation } from "@/api/finance";
import { useRoute } from "vue-router";
const route = useRoute();
const colors = {
// 相似
similarity: {
fontColor: "rgba(5, 95, 194)",
color: "rgb(231, 243, 255)"
},
// 继承
inheritance: {
fontColor: "rgba(19, 168, 168)",
color: "rgba(230, 255, 251, 1)"
},
// 冲突
conflict: {
fontColor: "rgb(206, 79, 81)",
color: "rgba(5, 95, 194)"
}
};
// ... 其他原有变量保持不变 ...
const selectedSanctionIds = ref([]);
const isAllSelected = computed({
get() {
return sanctionList.value.length > 0 && sanctionList.value.every(item => item.checked);
},
set(val) {}
});
const handleCheckAllChange = val => {
sanctionList.value.forEach(item => {
item.checked = val;
});
updateSelectedIds();
};
const handleCheckOneChange = (val, item) => {
item.checked = val;
updateSelectedIds();
};
const updateSelectedIds = () => {
selectedSanctionIds.value = sanctionList.value.filter(item => item.checked).map(item => item.id);
if (selectedSanctionIds.value.length === 1) {
currentSanctionId.value = selectedSanctionIds.value[0];
}
};
const indeterminate = computed(() => {
const checkedCount = sanctionList.value.filter(item => item.checked).length;
return checkedCount > 0 && checkedCount < sanctionList.value.length;
});
const recordRelation = ref({ noRelationVertices: [], relationVoList: [] });
// 【新增】计算属性:处理图表数据
const graphNodes = computed(() => {
const nodesMap = new Map();
// 1. 处理无关联节点 (noRelationVertices)
if (recordRelation.value.noRelationVertices) {
recordRelation.value.noRelationVertices.forEach(node => {
if (!nodesMap.has(node.id)) {
nodesMap.set(node.id, {
id: node.id,
name: node.name,
// 可以根据需要添加 category 或其他样式属性
itemStyle: {
color: "#91cc75" // 例如:无关联节点用绿色区分
}
});
}
});
}
// 2. 处理关联节点 (relationVoList 中的 fromVertex 和 toVertex)
if (recordRelation.value.relationVoList) {
recordRelation.value.relationVoList.forEach(rel => {
const from = rel.fromVertex;
const to = rel.toVertex;
if (from && !nodesMap.has(from.id)) {
nodesMap.set(from.id, {
id: from.id,
name: from.name,
itemStyle: {
color: "#5470c6" // 例如:有关联节点用蓝色
}
});
}
if (to && !nodesMap.has(to.id)) {
nodesMap.set(to.id, {
id: to.id,
name: to.name,
itemStyle: {
color: "#5470c6"
}
});
}
});
}
return Array.from(nodesMap.values());
});
const graphLinks = computed(() => {
if (!recordRelation.value.relationVoList) return [];
return recordRelation.value.relationVoList.map(rel => {
return {
source: rel.fromVertex.id,
target: rel.toVertex.id,
// 将 edgeInfo 挂载到 data 上,以便在 formatter 中访问
label: {
formatter: rel.edgeInfo ? rel.edgeInfo.value : ""
}
};
});
});
const handleAssociationClick = () => {
console.log("handleAssociationClick", selectedSanctionIds.value);
fetchRecordRelation();
};
const fetchRecordRelation = async () => {
if (selectedSanctionIds.value.length === 0) {
return;
}
try {
const res = await getRecordRelation(selectedSanctionIds.value);
console.log("getRecordRelation", res);
if (!!res) {
recordRelation.value = res;
} else {
recordRelation.value = { noRelationVertices: [], relationVoList: [] };
}
} catch (error) {
console.error("获取制裁记录关联信息失败:", error);
recordRelation.value = { noRelationVertices: [], relationVoList: [] };
}
};
const loading = ref(false);
const currentPage = ref(1);
const fetchSanRecord = async () => {
loading.value = true;
const params = {
startDate: dateRange.value && dateRange.value[0] ? dateRange.value[0] : "",
endDate: dateRange.value && dateRange.value[1] ? dateRange.value[1] : "",
sanTypeId: sanTypeId.value || 1
};
try {
const res = await getSanRecord(params);
if (res && res.length > 0) {
sanctionList.value = res
.map(item => ({
id: item.sanRecordId,
date: item.sanRecordDate,
title: item.sanRecordName,
count: item.cnEntitiesNum,
unit: "家中国实体"
}))
.reverse();
if (sanctionList.value.length > 0) {
currentSanctionId.value = sanctionList.value[0].id;
}
} else {
sanctionList.value = [];
}
} catch (error) {
console.error("获取选择制裁数据失败:", error);
sanctionList.value = [];
} finally {
loading.value = false;
}
};
const handleDateChange = () => {
fetchSanRecord();
};
const handleSanctionSelect = id => {
currentSanctionId.value = id;
};
const dateRange = ref(["2025-01-01", "2025-12-31"]);
const sanctionList = ref([]);
const currentSanctionId = ref(5);
const sanTypeId = ref("");
onMounted(() => {
sanTypeId.value = route.query.sanTypeId || "";
fetchSanRecord();
});
</script>
<style lang="scss" scoped>
/* 样式保持不变 */
.main {
width: 100%;
padding-top: 12px;
padding-bottom: 50px;
display: flex;
justify-content: space-between;
.left {
width: 480px;
height: 828px;
.left-main {
margin-top: 11px;
padding: 0 22px 0 23px;
display: flex;
flex-direction: column;
height: calc(100% - 25px);
.date-picker-box {
margin-bottom: 16px;
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
font-size: 14px;
color: #666;
.count {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
color: rgb(95, 101, 108);
}
}
.list-content {
flex: 1;
overflow-y: auto;
padding-bottom: 20px;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 3px;
}
.list-item {
border: 1px solid rgb(234, 236, 238);
border-radius: 4px;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px 16px;
cursor: pointer;
transition: all 0.3s;
position: relative;
background: #fff;
.item-label {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.item-left {
width: 260px;
font-weight: 700;
color: rgb(59, 65, 75);
font-size: 16px;
font-family: "Microsoft YaHei";
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 10px;
}
.item-right {
color: rgb(132, 136, 142);
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
flex-shrink: 0;
}
&:hover {
border-color: #055fc2;
}
&.active {
border-color: rgb(5, 95, 194);
background-color: rgba(246, 250, 255, 1);
.item-left,
.item-right {
color: rgb(5, 95, 194);
}
&::after {
content: "";
position: absolute;
right: 0;
top: 10px;
bottom: 10px;
width: 4px;
background-color: rgb(5, 95, 194);
}
}
}
}
}
}
.right {
width: 1105px;
height: 828px;
.right-main {
margin-top: 11px;
height: calc(100% - 10px);
padding: 0 16px 16px 16px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.relation-empty {
display: flex;
justify-content: center;
align-items: center;
height: 300px;
width: 300px;
.empty {
font-size: 16px;
font-weight: 400;
font-family: Source Han Sans CN;
color: rgb(132, 136, 142);
line-height: 30px;
}
}
.relation-content {
width: 100%;
height: 100%;
}
}
}
}
.main-association {
justify-content: flex-start !important;
gap: 16px;
}
</style>
<template>
<div class="main main-association">
<div class="left">
<!-- ... 左侧代码保持不变 ... -->
<AnalysisBox title="制裁历程">
<div class="left-main">
<div class="date-picker-box">
......@@ -21,250 +22,154 @@
全选
</el-checkbox>
<div class="count">{{ sanctionList.length }}次制裁</div>
<!-- 暂时隐藏,说这里可能是轮播图的效果 -->
<!-- <div class="pagination">
<div class="page-btn prev" @click="handlePrevClick">
<el-icon><ArrowLeft /></el-icon>
</div>
<div class="page-btn next" @click="handleNextClick">
<el-icon><ArrowRight /></el-icon>
</div>
</div> -->
</div>
<div class="list-content" v-loading="loading">
<div
class="list-item"
v-for="item in sanctionList"
:key="item.id"
:class="{ active: currentSanctionId === item.id }"
@click="handleSanctionSelect(item.id)"
>
<div class="list-item" v-for="item in sanctionList" :key="item.id" @click="handleSanctionSelect(item.id)">
<el-checkbox v-model="item.checked" @change="val => handleCheckOneChange(val, item)" @click.stop>
<span class="item-label">
<span class="item-left">{{ item.date }}-{{ item.title }}</span>
<span class="item-right">{{ item.count }}{{ item.unit }}</span>
</span>
<div class="item-label">
<div class="item-left">
{{ dayjs(item.date).format("YYYY年MM月DD日") }}-{{ "SDN清单更新" }}
</div>
<div class="item-right">{{ item.count }}{{ item.unit }}</div>
</div>
</el-checkbox>
<!-- <div class="item-left">{{ item.date }}-{{ item.title }}</div>
<div class="item-right">{{ item.count }}{{ item.unit }}</div> -->
</div>
</div>
<el-button type="primary" @click="handleAssociationClick" style="height: 36px">
多投融资限制举措关联分析
<el-icon><Right /></el-icon>
</el-button>
</div>
</AnalysisBox>
</div>
<div class="right">
<AnalysisBox title="制裁产业链时序图">
<template #header-btn>
<el-select
v-model="selectedIndustryId"
placeholder="请选择"
class="industry-select"
@change="
() => {
getFishboneData();
getCnEntityOnChainData();
}
"
>
<el-option v-for="item in industryList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</template>
<AnalysisBox title="投融资限制举措关系图">
<div class="right-main">
<div class="right-main-content">
<div class="hintWrap">
<div class="icon1"></div>
<div class="title">
2025年实体清单制裁范围扩大至芯片制造环节,为中国的芯片制造能力划定“技术天花板”,阻止其向更先进水平发展。制裁范围向上游设备和材料、下游先进封装以及关键工具(如EDA软件)延伸,意图瓦解中国构建自主可控产业链的努力。
</div>
<div class="icon2Wrap">
<div class="icon2"></div>
</div>
</div>
<div class="right-main-content-main">
<div class="fishbone-wrapper">
<div class="fishbone-scroll-container" ref="scrollContainerRef">
<div class="fishbone" ref="fishboneRef" v-if="fishboneDataList.length > 0">
<div class="main-line" :style="{ width: fishboneDataList.length * 200 + 300 + 'px' }">
<!-- 主轴上的标签 -->
<div
class="main-line-text"
v-for="(item, index) in mainLineLabels"
:key="'label-' + index"
:class="{
'blue-theme': index < 2,
'green-theme': index >= 2 && index < 4,
'purple-theme': index >= 4
}"
:style="{ left: index * 200 + 220 + 'px' }"
>
{{ item }}
</div>
</div>
<!-- 奇数索引的数据组放在上方 -->
<div
v-for="(causeGroup, groupIndex) in getOddGroups(fishboneDataList)"
:key="'top-' + groupIndex"
:class="getTopBoneClass(groupIndex)"
:style="{ left: groupIndex * 400 + 420 + 'px' }"
>
<div class="left-bone">
<div
class="left-bone-item"
v-for="(item, index) in getLeftItems(causeGroup.causes)"
:key="'left-' + index"
>
<img :src="defaultTitle || item.picture" alt="" class="company-icon" />
<div class="text" :title="item.name">{{ item.name }}</div>
<div class="line"></div>
</div>
</div>
<div class="right-bone">
<div
class="right-bone-item"
v-for="(item, index) in getRightItems(causeGroup.causes)"
:key="'right-' + index"
>
<div class="line"></div>
<img :src="defaultTitle || item.picture" alt="" class="company-icon" />
<div class="text" :title="item.name">{{ item.name }}</div>
</div>
</div>
</div>
<!-- 偶数索引的数据组放在下方 -->
<div
v-for="(causeGroup, groupIndex) in getEvenGroups(fishboneDataList)"
:key="'bottom-' + groupIndex"
:class="getBottomBoneClass(groupIndex)"
:style="{ left: groupIndex * 400 + 220 + 'px' }"
>
<div class="left-bone">
<div
class="left-bone-item"
v-for="(item, index) in getLeftItems(causeGroup.causes)"
:key="'left-bottom-' + index"
>
<img :src="defaultTitle || item.picture" alt="" class="company-icon" />
<div class="text" :title="item.name">{{ item.name }}</div>
<div class="line"></div>
</div>
</div>
<div class="right-bone">
<div
class="right-bone-item"
v-for="(item, index) in getRightItems(causeGroup.causes)"
:key="'right-bottom-' + index"
>
<div class="line"></div>
<img :src="defaultTitle || item.picture" alt="" class="company-icon" />
<div class="text" :title="item.name">{{ item.name }}</div>
</div>
</div>
</div>
</div>
<div
v-else
style="
display: flex;
justify-content: center;
align-items: center;
height: 200px;
width: 100%;
"
>
<el-empty description="暂无相关数据" />
</div>
</div>
</div>
<div class="relation-empty" v-if="selectedSanctionIds.length == 0">
<el-empty :image="emptyImg" :image-size="200">
<template #description>
<div class="empty">请在左侧勾选多次投融资限制制裁后点击“开始分析”查看结果</div>
</template>
</el-empty>
</div>
<div class="right-main-content-footer">
<div class="footer-item1">
<div class="footer-item1-bottom">
<div class="icon">
<img src="../../../../assets/images/warning.png" alt="" />
<div class="relation-content" v-else>
<!-- 绑定转换后的 graphNodes 和 graphLinks -->
<GraphChart :nodes="graphNodes" :links="graphLinks" @handleClickNode="handleClickNode" />
</div>
<div class="text">
{{
`中国企业${cnEntityOnChainData.upstreamInternalCount || 0}家(${formatRate(
cnEntityOnChainData.upstreamInternalRate
)}%),受制裁${cnEntityOnChainData.upstreamEntityCount || 0}家(${formatRate(
cnEntityOnChainData.upstreamEntityRate
)}%)`
}}
</div>
</AnalysisBox>
</div>
<div class="footer-item1-top">{{ "上游" }}</div>
<el-dialog v-model="visible" :title="curNode.name" width="960">
<div class="dialog-content">
<div class="content-item">
<div class="item-label">制裁标题:</div>
<div class="item-desc item-label">
{{ vertexInfo.name }}
</div>
<div class="footer-item2">
<div class="footer-item2-bottom">
<div class="icon">
<img src="../../../../assets/images/warning.png" alt="" />
</div>
<div class="text">
{{
`中国企业${cnEntityOnChainData.midstreamInternalCount || 0}家(${formatRate(
cnEntityOnChainData.midstreamInternalRate
)}%),受制裁${cnEntityOnChainData.midstreamEntityCount || 0}家(${formatRate(
cnEntityOnChainData.midstreamEntityRate
)}%)`
}}
<div class="content-item">
<div class="item-label">制裁领域:</div>
<div class="item-desc">
<AreaTag v-for="item in vertexInfo.domainList" :key="item" :tagName="item" />
</div>
</div>
<div class="footer-item2-top">{{ "中游" }}</div>
<div class="content-item">
<div class="item-label">依托文件:</div>
<div class="item-desc">
<div class="item-file" v-for="item in vertexInfo.relyFileList" :key="item.id">{{ item.name }}</div>
</div>
<div class="footer-item3">
<div class="footer-item3-bottom">
<div class="icon">
<img src="../../../../assets/images/warning.png" alt="" />
</div>
<div class="text">
{{
`中国企业${cnEntityOnChainData.downstreamInternalCount || 0}家(${formatRate(
cnEntityOnChainData.downstreamInternalRate
)}%),受制裁${cnEntityOnChainData.downstreamEntityCount || 0}家(${formatRate(
cnEntityOnChainData.downstreamEntityRate
)}%)`
}}
<div class="content-item">
<div class="item-label">依托制裁:</div>
<div class="item-desc">
<div class="item-file" v-for="item in vertexInfo.relySanList" :key="item.id">{{ item.title }}</div>
</div>
</div>
<div class="footer-item3-top">{{ "下游" }}</div>
<div class="content-item">
<div class="item-label">制裁原因:</div>
<div class="item-desc">
<div class="item-file" v-for="item in vertexInfo.sanReasonList" :key="item">{{ item }}</div>
</div>
</div>
<div class="content-item">
<div class="item-label">制裁对象:</div>
<div class="item-desc">
<span class="item-table-desc" v-if="vertexInfo.addObjectList?.length || vertexInfo.delObjectList?.length">
{{ formatChangeSummary(vertexInfo.addObjectList, vertexInfo.delObjectList) }}
</span>
<el-table :data="vertexInfo.sanList" stripe>
<el-table-column property="entityNameZh" label="制裁对象" width="350" />
<el-table-column property="domainNames" label="所属领域" width="400">
<template #default="scope">
<AreaTag v-for="item in scope.row.domainNames" :key="item" :tagName="item" />
</template>
</el-table-column>
</el-table>
</div>
</div>
</AnalysisBox>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick, onUnmounted, computed } from "vue";
import { ArrowLeft, ArrowRight } from "@element-plus/icons-vue";
import defaultTitle from "../../../assets/default-icon2.png";
import {
getDeepMiningSelect,
getDeepMiningIndustry,
getDeepMiningIndustryFishbone,
getDeepMiningIndustryEntity
} from "@/api/exportControlV2.0";
import { ref, onMounted, computed } from "vue";
import GraphChart from "@/components/base/GraphChart/index.vue";
import emptyImg from "../assets/empty.png";
import markIcon from "../assets/icon-mark.png"; // 引入图标
import { getSanRecord, getRecordRelation, getVertexInfo } from "@/api/finance";
import { useRoute } from "vue-router";
import dayjs from "dayjs";
const route = useRoute();
// 存储选中的ID列表 (如果需要获取选中项,可以使用这个,或者直接遍历 sanctionList 找 checked=true 的)
const selectedSanctionIds = ref([]);
// 定义颜色映射
const colors = {
// 相似
similarity: {
fontColor: "rgba(5, 95, 194)",
color: "rgb(231, 243, 255)", // 连线颜色 & 标签背景
lineColor: "rgba(5, 95, 194)" // 专门用于连线的颜色,如果需要和背景色区分
},
// 继承
inheritance: {
fontColor: "rgba(19, 168, 168)",
color: "rgba(230, 255, 251, 1)",
lineColor: "rgba(19, 168, 168)"
},
// 冲突
conflict: {
fontColor: "rgb(206, 79, 81)",
color: "rgba(255, 241, 240, 1)", // 修正:冲突背景通常偏红/浅红,这里沿用你提供的蓝色背景可能不太符合直觉,但我保留你的配置或微调
lineColor: "rgb(206, 79, 81)"
}
};
// 计算属性:是否全选
// 辅助函数:获取关系类型对应的颜色配置
const getRelationStyle = relationType => {
switch (relationType) {
case "相似":
return colors.similarity;
case "继承":
return colors.inheritance;
case "冲突":
return colors.conflict;
default:
return {
fontColor: "#666",
color: "#f0f2f5",
lineColor: "#AED6FF"
};
}
};
const selectedSanctionIds = ref([]);
const isAllSelected = computed({
get() {
return sanctionList.value.length > 0 && sanctionList.value.every(item => item.checked);
},
set(val) {
// set 方法由 handleCheckAllChange 处理,这里主要是为了配合 v-model 的读取
}
});
// 全选/取消全选
const handleCheckAllChange = val => {
sanctionList.value.forEach(item => {
item.checked = val;
......@@ -272,167 +177,235 @@ const handleCheckAllChange = val => {
updateSelectedIds();
};
// 单个选中变化
const handleCheckOneChange = (val, item) => {
item.checked = val;
updateSelectedIds();
};
// 点击行触发复选框切换
const handleItemClick = item => {
item.checked = !item.checked;
updateSelectedIds();
};
// 更新选中ID数组(供外部业务使用,如获取选中数据进行查询等)
const updateSelectedIds = () => {
selectedSanctionIds.value = sanctionList.value.filter(item => item.checked).map(item => item.id);
// 如果业务需要:当选中项变化时,可能需要触发右侧图表更新?
// 原逻辑是点击选中一个就更新右侧。现在如果是多选,右侧图表如何展示?
// 假设:如果只选中了一个,更新右侧;如果选中多个或没选中,保持现状或显示提示?
// 这里暂时保留原逻辑的触发点,但建议根据实际需求调整。
// 例如:只有当选中项为1个时,才调用 getFishboneData
if (selectedSanctionIds.value.length === 1) {
currentSanctionId.value = selectedSanctionIds.value[0];
getFishboneData();
getCnEntityOnChainData();
} else if (selectedSanctionIds.value.length === 0) {
// 可选:清空右侧或显示默认状态
}
};
// 计算属性:是否半选(不确定状态)
const indeterminate = computed(() => {
const checkedCount = sanctionList.value.filter(item => item.checked).length;
return checkedCount > 0 && checkedCount < sanctionList.value.length;
});
// 实体清单-深度挖掘-产业链中国企业实体信息查询
const getCnEntityOnChainData = async () => {
const currentSanction = sanctionList.value.find(item => item.id === currentSanctionId.value);
const date = currentSanction ? currentSanction.date : "";
// 确保 date 格式正确
const formattedDate = date && date.includes("年") ? date.replace("年", "-").replace("月", "-").replace("日", "") : date;
const params = {
date: formattedDate
};
if (selectedIndustryId.value) {
params.chainId = selectedIndustryId.value;
const recordRelation = ref({ noRelationVertices: [], relationVoList: [] });
// 【修改】处理图表节点数据
const graphNodes = computed(() => {
const nodesMap = new Map();
// 1. 处理无关联节点
if (recordRelation.value.noRelationVertices) {
recordRelation.value.noRelationVertices.forEach(node => {
if (!nodesMap.has(node.id)) {
nodesMap.set(node.id, {
id: node.id,
// name: node.name,
name: dayjs(node.date).format("YYYY年MM月DD日") + " " + "SDN清单更新",
symbol: `image://${markIcon}`, // 设置自定义图标
symbolSize: [50, 50], // 根据图标实际大小调整
itemStyle: {
color: "#91cc75"
}
try {
const res = await getDeepMiningIndustryEntity(params);
if (res.code === 200 && res.data) {
cnEntityOnChainData.value = res.data;
} else {
cnEntityOnChainData.value = {};
});
}
} catch (error) {
console.error("获取产业链中国企业实体信息失败:", error);
cnEntityOnChainData.value = {};
});
}
};
// 实体清单-深度挖掘-产业链鱼骨图信息
const fishboneDataList = ref([]);
const getFishboneData = async () => {
const currentSanction = sanctionList.value.find(item => item.id === currentSanctionId.value);
const date = currentSanction ? currentSanction.date : "";
// 确保 date 格式正确
const formattedDate = date && date.includes("年") ? date.replace("年", "-").replace("月", "-").replace("日", "") : date;
// 2. 处理关联节点
if (recordRelation.value.relationVoList) {
recordRelation.value.relationVoList.forEach(rel => {
const from = rel.fromVertex;
const to = rel.toVertex;
const params = {
date: formattedDate
};
if (selectedIndustryId.value) {
params.chainId = selectedIndustryId.value;
if (from && !nodesMap.has(from.id)) {
nodesMap.set(from.id, {
id: from.id,
// name: from.name,
name: dayjs(from.date).format("YYYY年MM月DD日") + " " + "SDN清单更新",
symbol: `image://${markIcon}`, // 设置自定义图标
symbolSize: [50, 50],
itemStyle: {
color: "#5470c6"
}
try {
const res = await getDeepMiningIndustryFishbone(params);
if (res.code === 200 && res.data && res.data.causes && res.data.causes.length > 0) {
const rootCauses = res.data.causes;
if (rootCauses.length > 0 && rootCauses[0].causes) {
fishboneDataList.value = rootCauses.map(group => {
});
}
if (to && !nodesMap.has(to.id)) {
nodesMap.set(to.id, {
id: to.id,
// name: to.name,
name: dayjs(to.date).format("YYYY年MM月DD日") + " " + "SDN清单更新",
symbol: `image://${markIcon}`, // 设置自定义图标
symbolSize: [50, 50],
itemStyle: {
color: "#5470c6"
}
});
}
});
}
return Array.from(nodesMap.values());
});
// 【修改】处理图表连线数据
const graphLinks = computed(() => {
if (!recordRelation.value.relationVoList) return [];
return recordRelation.value.relationVoList.map(rel => {
const relationType = rel.edgeInfo ? rel.edgeInfo.value : "";
const style = getRelationStyle(relationType);
return {
causes: group.causes || []
source: rel.fromVertex.id,
target: rel.toVertex.id,
// 将样式信息挂载到 label 或 data 上,供 formatter 和 lineStyle 使用
label: {
formatter: relationType,
show: true,
color: style.fontColor, // 字体颜色
backgroundColor: style.color, // 标签背景色
borderColor: style.color, // 标签边框色
padding: [4, 8],
borderRadius: 4,
fontSize: 12
},
lineStyle: {
color: style.lineColor, // 连线颜色
width: 2
// curveness: 0.1 // 稍微有点弧度可能更好看
},
// 额外存储原始数据,以备后用
relationType: relationType
};
});
});
mainLineLabels.value = rootCauses.map(group => group.text || "");
} else {
fishboneDataList.value = [];
mainLineLabels.value = [];
const handleAssociationClick = () => {
console.log("handleAssociationClick", selectedSanctionIds.value);
fetchRecordRelation();
};
const fetchRecordRelation = async () => {
if (selectedSanctionIds.value.length === 0) {
return;
}
try {
const res = await getRecordRelation(selectedSanctionIds.value);
console.log("getRecordRelation", res);
if (!!res) {
recordRelation.value = res;
} else {
fishboneDataList.value = [];
mainLineLabels.value = [];
recordRelation.value = { noRelationVertices: [], relationVoList: [] };
}
} catch (error) {
console.error("获取产业链鱼骨图数据失败:", error);
fishboneDataList.value = [];
console.error("获取制裁记录关联信息失败:", error);
recordRelation.value = { noRelationVertices: [], relationVoList: [] };
}
};
// 实体清单-深度挖掘-产业链列表信息
const industryList = ref([]);
const selectedIndustryId = ref(null);
const getIndustryList = async () => {
try {
const res = await getDeepMiningIndustry();
if (res.code === 200 && res.data && res.data.length > 0) {
industryList.value = res.data;
selectedIndustryId.value = res.data[0].id;
getFishboneData();
getCnEntityOnChainData();
const vertexInfo = ref({});
const curNode = ref({});
const visible = ref(false);
const handleClickNode = node => {
console.log("节点点击", node);
curNode.value = node.data;
getVertexInfo(node.data.id).then(res => {
console.log("getVertexInfo", res);
if (!!res) {
vertexInfo.value = res;
visible.value = true;
} else {
industryList.value = [];
selectedIndustryId.value = null;
vertexInfo.value = {};
}
} catch (error) {
console.error("获取产业链列表数据失败:", error);
industryList.value = [];
selectedIndustryId.value = null;
});
};
// 【新增/修改】格式化变动 summary 的函数
const formatChangeSummary = (addList, delList) => {
const parts = [];
// 处理新增列表
if (addList && addList.length > 0) {
// 将每个对象转换为 "value个实体" 或 "value名个人" 的形式
const addItems = addList.map(item => {
let unit = "个";
let noun = "实体";
if (item.key === "人物") {
unit = "名";
noun = "个人";
} else if (item.key === "机构") {
// 默认机构对应实体,也可以根据需求调整
unit = "个";
noun = "实体";
}
return `${item.value}${unit}${noun}`;
});
// 拼接:新增 + item1 + , + item2 ...
parts.push(`新增${addItems.join(",")}`);
}
// 处理移除列表
if (delList && delList.length > 0) {
// 将每个对象转换为 "value个实体" 或 "value名个人" 的形式
const delItems = delList.map(item => {
let unit = "个";
let noun = "实体";
if (item.key === "人物") {
unit = "名";
noun = "个人";
} else if (item.key === "机构") {
unit = "个";
noun = "实体";
}
return `${item.value}${unit}${noun}`;
});
// 拼接:移除 + item1 + , + item2 ...
// 注意:题目要求“删除”,但之前代码用的是“移除”,这里统一使用“移除”或“删除”。
// 根据题目描述“展示样本为:新增12个实体,3名个人,移除1个实体”,这里使用“移除”更贴切上下文,
// 如果必须用“删除”,请将下面的 '移除' 改为 '删除'。
parts.push(`移除${delItems.join(",")}`);
}
return parts.length > 0 ? parts.join(",") : "无变动";
};
// 获取选择制裁
const loading = ref(false);
const currentPage = ref(1);
const pageSize = ref(100);
const total = ref(0);
const totalPage = ref(0);
const sanctionList = ref([]);
const currentSanctionId = ref(5);
const dateRange = ref(["2025-01-01", "2025-12-31"]);
const sanTypeId = ref("");
const getDeepMiningSelectData = async () => {
const fetchSanRecord = async () => {
loading.value = true;
const params = {
startDate: dateRange.value && dateRange.value[0] ? dateRange.value[0] : "",
endDate: dateRange.value && dateRange.value[1] ? dateRange.value[1] : "",
// typeName: "实体清单",
isCn: false,
pageNum: currentPage.value,
pageSize: pageSize.value,
sanTypeIds: [Number(sanTypeId.value)] || 1 // 实体清单固定1
sanTypeId: sanTypeId.value || 1
};
try {
const res = await getDeepMiningSelect(params);
if (res.code === 200 && res.data && res.data.content) {
sanctionList.value = res.data.content
const res = await getSanRecord(params);
if (res && res.length > 0) {
sanctionList.value = res
.map(item => ({
id: item.id,
date: item.postDate,
title: item.name,
count: item.cnEntityCount,
unit: "家中国实体", // 接口未返回单位,暂时固定
summary: item.summary, // 保留额外信息备用
techDomainList: item.techDomainList // 保留额外信息备用
id: item.sanRecordId,
date: item.sanRecordDate,
title: item.sanRecordName,
count: item.cnEntitiesNum,
unit: "家中国实体"
}))
.reverse();
// 默认选中第一条
if (sanctionList.value.length > 0) {
currentSanctionId.value = sanctionList.value[0].id;
// getFishboneData(); // 这里不需要调用,因为getIndustryList会调用
}
} else {
sanctionList.value = [];
......@@ -445,102 +418,25 @@ const getDeepMiningSelectData = async () => {
}
};
// 日期选择变化
const handleDateChange = () => {
currentPage.value = 1;
getDeepMiningSelectData();
};
// ✅ 自动轮播定时器
const autoPlayTimer = ref(null);
// ✅ 自动下一个(支持循环)
const handleNextClickAuto = () => {
const currentIndex = sanctionList.value.findIndex(item => item.id === currentSanctionId.value);
let nextItem;
if (currentIndex < sanctionList.value.length - 1) {
nextItem = sanctionList.value[currentIndex + 1];
} else {
nextItem = sanctionList.value[0]; // 循环到第一个
}
if (nextItem) {
handleSanctionSelect(nextItem.id);
}
fetchSanRecord();
};
const handleSanctionSelect = id => {
currentSanctionId.value = id;
getFishboneData();
getCnEntityOnChainData();
};
const activeTab = ref(["制裁时序分析", "限制关联分析"]);
const activeIndex = ref(0);
const dateRange = ref(["2025-01-01", "2025-12-31"]);
const sanctionList = ref([]);
const currentSanctionId = ref(5);
const cnEntityOnChainData = ref({});
const mainLineLabels = ref(["关键原材料", "电池材料", "电子元器件", "动力电池", "电子控制系统", "动力电池"]);
// 获取奇数索引的数据组(放在上方)
const getOddGroups = data => {
return data.filter((_, index) => index % 2 !== 0);
};
// 获取偶数索引的数据组(放在下方)
const getEvenGroups = data => {
return data.filter((_, index) => index % 2 === 0);
};
// 获取上方鱼骨图位置类名
const getTopBoneClass = index => {
const positions = ["top-bone", "top-bone1", "top-bone2"];
return positions[index % 3] || "top-bone";
};
// 获取下方鱼骨图位置类名
const getBottomBoneClass = index => {
const positions = ["bottom-bone", "bottom-bone1", "bottom-bone2"];
return positions[index % 3] || "bottom-bone";
};
// 获取左侧显示的项目(前半部分)
const getLeftItems = items => {
const midpoint = Math.ceil(items.length / 2);
return items.slice(0, midpoint);
};
// 获取右侧显示的项目(后半部分)
const getRightItems = items => {
const midpoint = Math.ceil(items.length / 2);
return items.slice(midpoint);
};
// 格式化比率
const formatRate = rate => {
if (rate === undefined || rate === null) return "0.00";
return (rate * 100).toFixed(2);
};
const sanTypeId = ref("");
onMounted(() => {
// 获取路由参数中的sanTypeId
sanTypeId.value = route.query.sanTypeId || "";
// 获取选择制裁
getDeepMiningSelectData();
// 获取产业链信息
getIndustryList();
fetchSanRecord();
});
</script>
<style lang="scss" scoped>
/* 样式保持不变 */
.main {
width: 100%;
padding-top: 16px;
padding-top: 12px;
padding-bottom: 50px;
display: flex;
justify-content: space-between;
......@@ -554,7 +450,7 @@ onMounted(() => {
padding: 0 22px 0 23px;
display: flex;
flex-direction: column;
height: calc(100% - 56px);
height: calc(100% - 25px);
.date-picker-box {
margin-bottom: 16px;
......@@ -574,34 +470,6 @@ onMounted(() => {
font-family: "Microsoft YaHei";
color: rgb(95, 101, 108);
}
.pagination {
display: flex;
gap: 12px;
.page-btn {
width: 28px;
height: 28px;
background: rgba(231, 243, 255, 1);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: rgb(5, 95, 194);
font-size: 16px;
&.disabled {
cursor: not-allowed;
background: #f5f7fa;
color: #c0c4cc;
}
&:not(.disabled):hover {
background: #e1eeff;
}
}
}
}
.list-content {
......@@ -619,7 +487,6 @@ onMounted(() => {
}
.list-item {
// height: 60px;
border: 1px solid rgb(234, 236, 238);
border-radius: 4px;
margin-bottom: 8px;
......@@ -631,6 +498,12 @@ onMounted(() => {
transition: all 0.3s;
position: relative;
background: #fff;
.item-label {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.item-left {
width: 260px;
......@@ -638,6 +511,10 @@ onMounted(() => {
color: rgb(59, 65, 75);
font-size: 16px;
font-family: "Microsoft YaHei";
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 10px;
}
.item-right {
......@@ -645,6 +522,7 @@ onMounted(() => {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
flex-shrink: 0;
}
&:hover {
......@@ -668,7 +546,6 @@ onMounted(() => {
bottom: 10px;
width: 4px;
background-color: rgb(5, 95, 194);
// border-radius: 4px 0 0 4px;
}
}
}
......@@ -682,508 +559,61 @@ onMounted(() => {
.right-main {
margin-top: 11px;
height: calc(100% - 56px);
height: calc(100% - 10px);
padding: 0 16px 16px 16px;
.right-main-content {
height: 100%;
display: flex;
flex-direction: column;
.hintWrap {
display: flex;
align-items: center;
padding: 7px 12px;
border: 1px solid rgba(231, 243, 255, 1);
border-radius: 4px;
background: rgba(246, 250, 255, 1);
margin-bottom: 9px;
.icon1 {
width: 19px;
height: 20px;
background-image: url("../assets/ai.png");
background-size: 100% 100%;
flex-shrink: 0;
}
.title {
color: rgb(5, 95, 194);
font-size: 16px;
font-weight: 400;
line-height: 24px;
margin-left: 13px;
flex: 1;
}
.icon2Wrap {
width: 24px;
height: 24px;
background-color: rgba(231, 243, 255, 1);
display: flex;
justify-content: center;
align-items: center;
border-radius: 12px;
margin-left: 20px;
flex-shrink: 0;
.icon2 {
width: 24px;
height: 24px;
background-image: url("../assets/right.png");
background-size: 100% 100%;
}
}
}
.right-main-content-main {
flex: 1;
// border: 1px solid #eaecee;
// border-radius: 4px;
// background: #f7f8f9;
position: relative;
overflow: hidden;
.fishbone-wrapper {
position: relative;
width: 100%;
height: 100%;
}
.fishbone-scroll-container {
.relation-empty {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: thin;
scrollbar-color: rgba(144, 202, 249, 0.5) transparent;
&::-webkit-scrollbar {
height: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(144, 202, 249, 0.5);
border-radius: 3px;
height: 300px;
width: 300px;
.empty {
font-size: 16px;
font-weight: 400;
font-family: Source Han Sans CN;
color: rgb(132, 136, 142);
line-height: 30px;
}
}
.fishbone {
position: relative;
width: fit-content;
height: 100%;
margin-top: 40px;
min-width: 100%;
padding-left: 275px;
margin-left: 40px;
.main-line {
margin-top: 280px;
width: 1888px;
height: 3px;
background: rgb(230, 231, 232);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 100px;
// 虚线
&::after {
content: "";
position: absolute;
top: 0;
left: 0;
.relation-content {
width: 100%;
height: 100%;
// background: repeating-linear-gradient(to right, rgba(174, 208, 255, 1) 0, rgba(174, 208, 255, 1) 10px, transparent 10px, transparent 20px);
}
// 添加中间的文字块
.main-line-text {
position: absolute;
// top: -14px;
font-size: 16px;
color: #055fc2;
font-weight: bold;
background-color: #f7f8f9;
padding: 0 10px;
z-index: 2;
// 箭头背景
height: 32px;
line-height: 32px;
width: 160px;
text-align: center;
background: rgba(231, 243, 255, 1);
clip-path: polygon(0% 0%, 90% 0%, 100% 50%, 90% 100%, 0% 100%, 10% 50%);
&.blue-theme {
background: rgba(231, 243, 255, 1);
color: rgba(22, 119, 255, 1);
}
&.green-theme {
background: rgba(225, 255, 251, 1);
color: rgba(19, 168, 168, 1);
}
&.purple-theme {
background: rgba(246, 235, 255, 1);
color: rgba(146, 84, 222, 1);
}
}
}
}
.company-icon {
width: 16px;
height: 16px;
margin: 0 4px;
object-fit: contain;
}
.top-bone {
position: absolute;
top: 20px;
right: 200px;
width: 3px;
height: 260px;
background: rgb(230, 231, 232);
transform: skew(30deg);
z-index: 1;
.left-bone {
color: #777;
position: absolute;
top: 0;
left: -150px;
width: 150px;
height: 50px;
// overflow: hidden;
.left-bone-item {
transform: skew(-30deg);
height: 45px;
margin-bottom: 2px;
margin-top: 2px;
display: flex;
justify-content: flex-end;
align-items: center;
.text {
margin-left: 4px;
height: 25px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
width: 40px;
height: 2px;
background: rgb(230, 231, 232);
}
}
}
.right-bone {
color: #777;
position: absolute;
top: 0;
right: -150px;
width: 150px;
height: 210px;
overflow: hidden;
.right-bone-item {
transform: skew(-30deg);
height: 39px;
margin-bottom: 2px;
margin-top: 2px;
display: flex;
justify-content: flex-start;
align-items: center;
.line {
margin-right: 7px;
width: 30px;
height: 2px;
background: rgb(230, 231, 232);
}
.text {
max-width: 100px;
margin-right: 4px;
height: 25px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.top-bone1 {
@extend .top-bone;
right: 500px;
}
.top-bone2 {
@extend .top-bone;
right: 800px;
}
.bottom-bone {
position: absolute;
top: 280px;
right: 360px;
width: 3px;
height: 260px;
background: rgb(230, 231, 232);
transform: skew(-30deg);
z-index: 1;
.left-bone {
color: #777;
position: absolute;
top: 50px;
left: -150px;
width: 150px;
height: 260px;
.left-bone-item {
transform: skew(30deg);
height: 39px;
margin-bottom: 2px;
margin-top: 2px;
}
.main-association {
padding-top: 12px !important;
justify-content: flex-start !important;
gap: 16px;
}
.dialog-content {
padding: 20px;
display: flex;
justify-content: flex-end;
align-items: center;
.text {
margin-left: 4px;
height: 25px;
max-width: 130px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
width: 40px;
height: 2px;
background: rgb(230, 231, 232);
}
}
}
.right-bone {
color: #777;
position: absolute;
top: 50px;
right: -150px;
width: 150px;
height: 260px;
.right-bone-item {
transform: skew(30deg);
height: 35px;
margin-bottom: 2px;
margin-top: 2px;
flex-direction: column;
gap: 8px;
border-top: 1px solid rgb(238, 238, 238);
.content-item {
display: flex;
justify-content: flex-start;
align-items: center;
.line {
margin-right: 7px;
width: 30px;
height: 2px;
background: rgb(230, 231, 232);
}
.text {
max-width: 100px;
margin-right: 4px;
height: 25px;
line-height: 25px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
.bottom-bone1 {
@extend .bottom-bone;
right: 660px;
}
.bottom-bone2 {
@extend .bottom-bone;
right: 960px;
}
}
.right-main-content-footer {
height: 84px;
margin-top: 16px;
display: flex;
justify-content: space-between;
.footer-item1,
.footer-item2,
.footer-item3 {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.footer-item1 {
.footer-item1-top {
height: 28px;
text-align: center;
line-height: 28px;
background: url("../../../../assets/images/bg3.png");
background-size: 100% 100%;
color: rgba(22, 119, 255, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
margin-top: 15px;
margin-right: -10px; // Negative margin to overlap/connect
position: relative; // Ensure z-index works if needed
z-index: 1;
}
.footer-item1-bottom {
display: flex;
justify-content: center;
.icon {
margin-top: 9px;
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
margin-top: 7px;
margin-left: 8px;
height: 22px;
color: rgba(206, 79, 81, 1);
font-size: 14px;
}
}
}
.footer-item2 {
.footer-item2-top {
height: 28px;
text-align: center;
line-height: 28px;
background: url("../../../../assets/images/bg2.png");
background-size: 100% 100%;
color: rgba(19, 168, 168, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
margin-top: 15px;
margin-right: -10px; // Negative margin to connect with next item
margin-left: -10px; // Negative margin to connect with prev item
position: relative;
z-index: 1;
}
.footer-item2-bottom {
display: flex;
justify-content: center;
.icon {
margin-top: 9px;
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
margin-top: 7px;
margin-left: 8px;
height: 22px;
color: rgba(206, 79, 81, 1);
font-size: 14px;
}
}
}
.footer-item3 {
.footer-item3-top {
height: 28px;
text-align: center;
line-height: 28px;
background: url("../../../../assets/images/bg1.png");
background-size: 100% 100%;
color: rgba(146, 84, 222, 1);
font-family: Microsoft YaHei;
font-size: 16px;
gap: 8px;
.item-label {
font-size: 146x;
font-weight: 700;
margin-top: 15px;
margin-left: -10px; // Negative margin to connect
position: relative;
z-index: 1;
}
.footer-item3-bottom {
display: flex;
justify-content: center;
.icon {
margin-top: 9px;
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
font-family: Source Han Sans CN;
color: rgba(59, 65, 75, 1);
line-height: 24px;
}
.text {
margin-top: 7px;
margin-left: 8px;
height: 22px;
color: rgba(206, 79, 81, 1);
.item-desc {
font-size: 14px;
font-family: Source Han Sans CN;
margin-top: 3px;
}
}
}
}
}
}
}
}
.main-association {
justify-content: flex-start !important;
gap: 16px;
}
</style>
{
"id": 2140,
"name": "美国以伊朗石油非法贸易为由实施制裁,多家中国企业被列入制裁名单",
"domainList": [
"海洋"
],
"relyFileList": [
{
"id": null,
"name": "第13382号行政命令"
},
{
"id": null,
"name": "第14530号行政命令"
}
],
"relySanList": [
{
"sanTypeId": 2,
"id": 2145,
"title": "OFAC将38个实体及4名个人列入SDN清单,涉及中国关联主体",
"postDate": "2025-10-14"
}
],
"sanReasonList": [
"参与了从伊朗购买、收购、销售、运输或营销石油化工产品",
"参与了与采购、获取、销售、运输或销售伊朗石油及石油制品相关的重大交易"
],
"addObjectList": [
{
"key": "机构",
"value": 3
}
],
"delObjectList": [
{
"key": "人物",
"value": 1
}
],
"sanList": [
{
"entityId": "91310115MA1HBB8PXH",
"entityName": "SHANGHAI QIZHANG SHIP MANAGEMENT CO., LTD.",
"entityNameZh": "上海启章船舶管理有限公司",
"entityTypeId": 2,
"entityTypeName": "机构",
"domainNames": [
"海洋"
]
},
{
"entityId": "71180883",
"entityName": "ALL WIN SHIPPING MANAGEMENT LIMITED",
"entityNameZh": "誠安船舶管理有限公司",
"entityTypeId": 2,
"entityTypeName": "机构",
"domainNames": [
"海洋"
]
},
{
"entityId": "91370211MAEBUA7E2Q",
"entityName": "QINGDAO OCEAN KIMO SHIP MANAGEMENT CO LTD",
"entityNameZh": "青岛明洋凯茂船舶管理有限公司",
"entityTypeId": 2,
"entityTypeName": "机构",
"domainNames": [
"海洋"
]
}
]
}
\ No newline at end of file
......@@ -13,7 +13,7 @@
</div>
</div>
<div class="main">
<div v-if="activeIndex == 0">
<div class="sanctionTime" v-if="activeIndex == 0">
<div class="left">
<AnalysisBox title="选择制裁">
<div class="left-main">
......@@ -76,7 +76,7 @@
</template>
<div class="right-main">
<div class="right-main-content">
<div class="hintWrap">
<!-- <div class="hintWrap">
<div class="icon1"></div>
<div class="title">
2025年实体清单制裁范围扩大至芯片制造环节,为中国的芯片制造能力划定“技术天花板”,阻止其向更先进水平发展。制裁范围向上游设备和材料、下游先进封装以及关键工具(如EDA软件)延伸,意图瓦解中国构建自主可控产业链的努力。
......@@ -84,7 +84,7 @@
<div class="icon2Wrap">
<div class="icon2"></div>
</div>
</div>
</div> -->
<div class="right-main-content-main">
<div class="fishbone-wrapper">
<div class="fishbone-scroll-container" ref="scrollContainerRef">
......@@ -557,8 +557,6 @@ onUnmounted(() => {
</script>
<style scoped lang="scss">
.deep-mining {
width: 1601px;
margin: 0 auto;
......@@ -610,6 +608,11 @@ onUnmounted(() => {
padding-bottom: 50px;
display: flex;
justify-content: space-between;
.sanctionTime {
display: flex;
padding-top: 12px;
gap: 10px;
}
.left {
width: 480px;
......
......@@ -433,11 +433,6 @@ onMounted(() => {
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.introduction-page {
width: 1601px;
margin: 0 auto;
......
......@@ -532,12 +532,12 @@ watch(customDateRange, () => {
align-items: center;
.search-input {
width: 388px;
width: 360px;
height: 32px;
:deep(.el-input__wrapper) {
padding: 0 11px;
border: 1px solid rgba(170, 173, 177, 1);
border: 1px solid rgba(170, 173, 177, 0.5);
background-color: #fff;
border-radius: 3px;
}
......@@ -579,7 +579,7 @@ watch(customDateRange, () => {
.left {
padding-bottom: 20px;
width: 388px;
width: 360px;
height: auto;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
......@@ -598,11 +598,12 @@ watch(customDateRange, () => {
.el-checkbox {
width: 50%;
margin-right: 0;
margin-bottom: 4px;
// margin-bottom: 4px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
height: 24px;
color: rgb(95, 101, 108);
}
......@@ -649,7 +650,7 @@ watch(customDateRange, () => {
}
.right {
width: 1196px;
width: 1223px;
height: auto;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
......
......@@ -724,7 +724,7 @@ import {
getCountDomainByYear,
getEntitiesList,
getSanctionProcess,
// getSanDomainCount,
getSanDomainCount,
// getRiskSignal,
// getSocialMediaInfo,
// getNewsInfo,
......@@ -738,7 +738,7 @@ import {
getNewsInfo,
getSocialMediaInfo,
getReleaseCount,
getSanDomainCount,
// getSanDomainCount,
getAnnualSanDomain
// getSanctionProcess
} from "@/api/finance";
......@@ -1143,7 +1143,7 @@ const radarOption = ref({
// 获取雷达图数据
const fetchRadarData = async checked => {
try {
const data = await getSanDomainCount(checked, "export");
const data = await getSanDomainCount(checked, allSanTypeIds.value.join(","));
if (data && Array.isArray(data) && data.length > 0) {
// 收集所有可能的领域名称
const allDomains = new Set();
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论