提交 2fecb118 authored 作者: yanpeng's avatar yanpeng

update

上级 12a9f4b2
...@@ -23,7 +23,8 @@ export function getEntitiesDataCount() { ...@@ -23,7 +23,8 @@ export function getEntitiesDataCount() {
return request200( return request200(
request({ request({
method: "GET", method: "GET",
url: "/api/entitiesDataCount/countData" // url: "/api/entitiesDataCount/countData",
url: "/api/sanctionList/export/getTotalInfo"
}) })
); );
} }
...@@ -62,12 +63,15 @@ export function getEntitiesDataInfo() { ...@@ -62,12 +63,15 @@ export function getEntitiesDataInfo() {
* maxCount: number * maxCount: number
* }[]>} * }[]>}
*/ */
export function getIndustryCountByYear() { export function getIndustryCountByYear(sanTypeId) {
return request200( return request200(
request({ request({
method: "GET", method: "GET",
// url: "/api/entitiesDataCount/industryCountByYear" // url: "/api/entitiesDataCount/industryCountByYear"
url: "/api/entitiesDataCount/getAnnualCount" url: "/api/entitiesDataCount/getAnnualCount",
params: {
sanTypeId
}
}) })
); );
} }
...@@ -84,11 +88,16 @@ export function getIndustryCountByYear() { ...@@ -84,11 +88,16 @@ export function getIndustryCountByYear() {
* domains: string[] * domains: string[]
* }>} * }>}
*/ */
export function getCountDomainByYear() { export function getCountDomainByYear(isRule, startYear = "2020", endYear = new Date().getFullYear()) {
return request200( return request200(
request({ request({
method: "GET", method: "POST",
url: "/api/entitiesDataCount/countDomainByYear" url: "/api/entitiesDataCount/getAnnualSanDomain",
data: {
isRule,
startYear,
endYear
}
}) })
); );
} }
...@@ -187,6 +196,21 @@ export function getKeyEntityList(date, keyword = "") { ...@@ -187,6 +196,21 @@ export function getKeyEntityList(date, keyword = "") {
); );
} }
/**
* 区域分布查询
*/
export function getAreaDistribution(date) {
return request200(
request({
method: "GET",
url: "/api/entitiesDataInfo/getRegionDistribution",
params: {
sanctionDate: date || "2025-11-11"
}
})
);
}
/** /**
* 不同领域实体统计 * 不同领域实体统计
* @param {string} startTime - 统计开始时间,格式为 'YYYY-MM-DD' * @param {string} startTime - 统计开始时间,格式为 'YYYY-MM-DD'
...@@ -292,7 +316,7 @@ export function getDomainDistribution(sanctionDate = "2025-11-11") { ...@@ -292,7 +316,7 @@ export function getDomainDistribution(sanctionDate = "2025-11-11") {
* startTime: string * startTime: string
* }[]>} * }[]>}
*/ */
export function getEntitiesList(typeName = "实体清单", pageNum = 1, pageSize = 10) { export function getEntitiesList(typeName = "实体清单", pageNum = 1, pageSize = 10, sanctionDate = "", rule = false) {
return request200( return request200(
request({ request({
method: "POST", method: "POST",
...@@ -300,7 +324,9 @@ export function getEntitiesList(typeName = "实体清单", pageNum = 1, pageSize ...@@ -300,7 +324,9 @@ export function getEntitiesList(typeName = "实体清单", pageNum = 1, pageSize
data: { data: {
typeName, typeName,
pageNum, pageNum,
pageSize pageSize,
sanctionDate,
rule
} }
}) })
); );
...@@ -340,15 +366,16 @@ export function getCompareCountSan(startTime) { ...@@ -340,15 +366,16 @@ export function getCompareCountSan(startTime) {
* count:number * count:number
* }[]>} * }[]>}
*/ */
export function getEntitiesChangeCount(domain, type) { export function getEntitiesChangeCount(domianId, typeId) {
return request200( return request200(
request({ request({
method: "GET", method: "GET",
// url: '/api/entitiesDataCount/entitiesChangeCount', // url: '/api/entitiesDataCount/entitiesChangeCount',
url: "/api/entitiesDataCount/sanCountByYear", // url: "/api/entitiesDataCount/sanCountByYear",
url: "/api/entitiesDataInfo/getCountByDomianAndType",
params: { params: {
domain, domianId,
type typeId
} }
}) })
); );
...@@ -377,11 +404,14 @@ export function getEntitiesGrowthTrend() { ...@@ -377,11 +404,14 @@ export function getEntitiesGrowthTrend() {
* xAxis: string[] * xAxis: string[]
* }>} * }>}
*/ */
export function getEntitiesUpdateCount() { export function getEntitiesUpdateCount(sanTypeId = 1) {
return request200( return request200(
request({ request({
method: "GET", method: "GET",
url: "/api/entitiesDataCount/entitiesUpdateCount" url: "/api/entitiesDataCount/getAnnualCount",
params: {
sanTypeId
}
}) })
); );
} }
...@@ -389,11 +419,14 @@ export function getEntitiesUpdateCount() { ...@@ -389,11 +419,14 @@ export function getEntitiesUpdateCount() {
/** /**
* 制裁领域分析 * 制裁领域分析
*/ */
export function getSanDomainCount() { export function getSanDomainCount(rule) {
return request200( return request200(
request({ request({
method: "GET", method: "GET",
url: "/api/entitiesDataCount/getSanDomainCount" url: "/api/entitiesDataCount/getSanDomainCount",
params: {
rule
}
}) })
); );
} }
......
...@@ -32,6 +32,7 @@ const emit = defineEmits(["click"]); ...@@ -32,6 +32,7 @@ const emit = defineEmits(["click"]);
cursor: pointer; cursor: pointer;
padding-left: 8px; padding-left: 8px;
padding-right: 8px; padding-right: 8px;
flex-shrink: 0;
} }
.activeButton { .activeButton {
border: 1px solid rgb(10, 87, 166); border: 1px solid rgb(10, 87, 166);
......
...@@ -61,5 +61,6 @@ function setActiveIndex(item) { ...@@ -61,5 +61,6 @@ function setActiveIndex(item) {
.buttonList { .buttonList {
display: flex; display: flex;
gap: 8px; gap: 8px;
overflow-x: auto;
} }
</style> </style>
<template> <template>
<div class="fishbone"> <div class="fishbone">
<div class="main-line"></div> <div class="main-line"></div>
<div class="top-bone"> <div v-for="(causeGroup, groupIndex) in fishboneData.causes" :key="groupIndex" :class="getBoneClass(groupIndex)">
<div class="left-bone"> <div class="left-bone">
<div class="left-bone-item"> <div class="left-bone-item" v-for="(item, index) in getLeftItems(causeGroup.causes)" :key="index">
<div class="icon"> <!-- <div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" /> <img :src="item.picture" alt="" />
</div> </div> -->
<div class="text">{{ "商汤科技" }}</div> <div class="text">{{ item.name }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
</div>
<div class="right-bone">
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
</div>
</div>
<div class="top-bone1">
<div class="left-bone">
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
</div>
<div class="right-bone">
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
</div>
</div>
<div class="top-bone2">
<div class="left-bone">
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
</div>
<div class="right-bone">
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
</div>
</div>
<div class="bottom-bone">
<div class="left-bone">
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div> <div class="line"></div>
</div> </div>
</div> </div>
<div class="right-bone"> <div class="right-bone">
<div class="right-bone-item"> <div class="right-bone-item" v-for="(item, index) in getRightItems(causeGroup.causes)" :key="index">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div> <div class="line"></div>
<div class="text">{{ "华为" }}</div> <div class="text">{{ item.name }}</div>
<div class="icon"> <!-- <div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" /> <img :src="item.picture" :alt="item.name" />
</div> </div> -->
</div>
</div>
</div>
<div class="bottom-bone1">
<div class="left-bone">
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
</div>
<div class="right-bone">
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
</div>
</div>
<div class="bottom-bone2">
<div class="left-bone">
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
<div class="left-bone-item">
<div class="icon">
<img src="../../assets/images/company-logo1.png" alt="" />
</div>
<div class="text">{{ "商汤科技" }}</div>
<div class="line"></div>
</div>
</div>
<div class="right-bone">
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div>
<div class="right-bone-item">
<div class="line"></div>
<div class="text">{{ "华为" }}</div>
<div class="icon">
<img src="../../assets/images/company-logo2.png" alt="" />
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -474,12 +27,39 @@ ...@@ -474,12 +27,39 @@
<script setup> <script setup>
import { getChainFishbone } from "@/api/exportControl"; import { getChainFishbone } from "@/api/exportControl";
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
const chainId = ref("");
const chainFishbone = ref([]); const chainId = ref(1);
const fishboneData = ref({
text: "",
causes: []
});
// 根据索引确定鱼骨图位置类名
const getBoneClass = index => {
const positions = ["top-bone", "top-bone1", "top-bone2", "bottom-bone", "bottom-bone1", "bottom-bone2"];
return positions[index] || "top-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);
};
onMounted(async () => { onMounted(async () => {
try { try {
const chainFishboneData = await getChainFishbone(chainId.value); const chainFishboneData = await getChainFishbone(chainId.value);
chainFishbone.value = chainFishboneData ?? []; fishboneData.value = chainFishboneData ?? {
text: "",
causes: []
};
console.log("鱼骨图数据:", fishboneData.value);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
...@@ -487,11 +67,14 @@ onMounted(async () => { ...@@ -487,11 +67,14 @@ onMounted(async () => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
/* ... 原有的样式保持不变 ... */
.fishbone { .fishbone {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
margin-top: 40px; margin-top: 40px;
overflow-x: auto;
.main-line { .main-line {
position: absolute; position: absolute;
top: 280px; top: 280px;
...@@ -501,6 +84,7 @@ onMounted(async () => { ...@@ -501,6 +84,7 @@ onMounted(async () => {
background: rgba(174, 208, 255, 1); background: rgba(174, 208, 255, 1);
} }
} }
.top-bone { .top-bone {
position: absolute; position: absolute;
top: 20px; top: 20px;
...@@ -517,13 +101,11 @@ onMounted(async () => { ...@@ -517,13 +101,11 @@ onMounted(async () => {
left: -150px; left: -150px;
width: 150px; width: 150px;
height: 260px; height: 260px;
// background: orange;
.left-bone-item { .left-bone-item {
transform: skew(-30deg); transform: skew(-30deg);
height: 35px; height: 35px;
margin-bottom: 5px; margin-bottom: 5px;
margin-top: 15px; margin-top: 15px;
// background: #fff;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.icon { .icon {
...@@ -537,8 +119,11 @@ onMounted(async () => { ...@@ -537,8 +119,11 @@ onMounted(async () => {
} }
.text { .text {
margin-left: 4px; margin-left: 4px;
height: 35px; height: 70px;
line-height: 35px; line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.line { .line {
margin-left: 7px; margin-left: 7px;
...@@ -556,7 +141,6 @@ onMounted(async () => { ...@@ -556,7 +141,6 @@ onMounted(async () => {
right: -150px; right: -150px;
width: 150px; width: 150px;
height: 260px; height: 260px;
// background: pink;
.right-bone-item { .right-bone-item {
transform: skew(-30deg); transform: skew(-30deg);
height: 35px; height: 35px;
...@@ -564,7 +148,6 @@ onMounted(async () => { ...@@ -564,7 +148,6 @@ onMounted(async () => {
margin-top: 5px; margin-top: 5px;
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
.line { .line {
margin-right: 7px; margin-right: 7px;
margin-top: 16px; margin-top: 16px;
...@@ -572,11 +155,14 @@ onMounted(async () => { ...@@ -572,11 +155,14 @@ onMounted(async () => {
height: 2px; height: 2px;
background: rgba(174, 208, 255, 1); background: rgba(174, 208, 255, 1);
} }
.text { .text {
width: 100px;
margin-right: 4px; margin-right: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.icon { .icon {
margin-top: 7px; margin-top: 7px;
...@@ -590,6 +176,7 @@ onMounted(async () => { ...@@ -590,6 +176,7 @@ onMounted(async () => {
} }
} }
} }
.top-bone1 { .top-bone1 {
position: absolute; position: absolute;
top: 20px; top: 20px;
...@@ -606,13 +193,11 @@ onMounted(async () => { ...@@ -606,13 +193,11 @@ onMounted(async () => {
left: -150px; left: -150px;
width: 150px; width: 150px;
height: 260px; height: 260px;
// background: orange;
.left-bone-item { .left-bone-item {
transform: skew(-30deg); transform: skew(-30deg);
height: 35px; height: 35px;
margin-bottom: 5px; margin-bottom: 5px;
margin-top: 15px; margin-top: 15px;
// background: #fff;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.icon { .icon {
...@@ -625,9 +210,13 @@ onMounted(async () => { ...@@ -625,9 +210,13 @@ onMounted(async () => {
} }
} }
.text { .text {
width: 100px;
margin-left: 4px; margin-left: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.line { .line {
margin-left: 7px; margin-left: 7px;
...@@ -645,7 +234,6 @@ onMounted(async () => { ...@@ -645,7 +234,6 @@ onMounted(async () => {
right: -150px; right: -150px;
width: 150px; width: 150px;
height: 260px; height: 260px;
// background: pink;
.right-bone-item { .right-bone-item {
transform: skew(-30deg); transform: skew(-30deg);
height: 35px; height: 35px;
...@@ -653,7 +241,6 @@ onMounted(async () => { ...@@ -653,7 +241,6 @@ onMounted(async () => {
margin-top: 5px; margin-top: 5px;
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
.line { .line {
margin-right: 7px; margin-right: 7px;
margin-top: 16px; margin-top: 16px;
...@@ -661,11 +248,14 @@ onMounted(async () => { ...@@ -661,11 +248,14 @@ onMounted(async () => {
height: 2px; height: 2px;
background: rgba(174, 208, 255, 1); background: rgba(174, 208, 255, 1);
} }
.text { .text {
width: 100px;
margin-right: 4px; margin-right: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.icon { .icon {
margin-top: 7px; margin-top: 7px;
...@@ -679,6 +269,7 @@ onMounted(async () => { ...@@ -679,6 +269,7 @@ onMounted(async () => {
} }
} }
} }
.top-bone2 { .top-bone2 {
position: absolute; position: absolute;
top: 20px; top: 20px;
...@@ -695,13 +286,11 @@ onMounted(async () => { ...@@ -695,13 +286,11 @@ onMounted(async () => {
left: -150px; left: -150px;
width: 150px; width: 150px;
height: 260px; height: 260px;
// background: orange;
.left-bone-item { .left-bone-item {
transform: skew(-30deg); transform: skew(-30deg);
height: 35px; height: 35px;
margin-bottom: 5px; margin-bottom: 5px;
margin-top: 15px; margin-top: 15px;
// background: #fff;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.icon { .icon {
...@@ -714,9 +303,13 @@ onMounted(async () => { ...@@ -714,9 +303,13 @@ onMounted(async () => {
} }
} }
.text { .text {
width: 100px;
margin-left: 4px; margin-left: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.line { .line {
margin-left: 7px; margin-left: 7px;
...@@ -734,7 +327,6 @@ onMounted(async () => { ...@@ -734,7 +327,6 @@ onMounted(async () => {
right: -150px; right: -150px;
width: 150px; width: 150px;
height: 260px; height: 260px;
// background: pink;
.right-bone-item { .right-bone-item {
transform: skew(-30deg); transform: skew(-30deg);
height: 35px; height: 35px;
...@@ -742,7 +334,6 @@ onMounted(async () => { ...@@ -742,7 +334,6 @@ onMounted(async () => {
margin-top: 5px; margin-top: 5px;
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
.line { .line {
margin-right: 7px; margin-right: 7px;
margin-top: 16px; margin-top: 16px;
...@@ -750,11 +341,14 @@ onMounted(async () => { ...@@ -750,11 +341,14 @@ onMounted(async () => {
height: 2px; height: 2px;
background: rgba(174, 208, 255, 1); background: rgba(174, 208, 255, 1);
} }
.text { .text {
width: 100px;
margin-right: 4px; margin-right: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.icon { .icon {
margin-top: 7px; margin-top: 7px;
...@@ -768,6 +362,7 @@ onMounted(async () => { ...@@ -768,6 +362,7 @@ onMounted(async () => {
} }
} }
} }
.bottom-bone { .bottom-bone {
position: absolute; position: absolute;
top: 280px; top: 280px;
...@@ -784,13 +379,11 @@ onMounted(async () => { ...@@ -784,13 +379,11 @@ onMounted(async () => {
left: -150px; left: -150px;
width: 150px; width: 150px;
height: 260px; height: 260px;
// background: orange;
.left-bone-item { .left-bone-item {
transform: skew(30deg); transform: skew(30deg);
height: 35px; height: 35px;
margin-bottom: 5px; margin-bottom: 5px;
margin-top: 15px; margin-top: 15px;
// background: #fff;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.icon { .icon {
...@@ -803,9 +396,13 @@ onMounted(async () => { ...@@ -803,9 +396,13 @@ onMounted(async () => {
} }
} }
.text { .text {
width: 100px;
margin-left: 4px; margin-left: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.line { .line {
margin-left: 7px; margin-left: 7px;
...@@ -823,7 +420,6 @@ onMounted(async () => { ...@@ -823,7 +420,6 @@ onMounted(async () => {
right: -150px; right: -150px;
width: 150px; width: 150px;
height: 260px; height: 260px;
// background: pink;
.right-bone-item { .right-bone-item {
transform: skew(30deg); transform: skew(30deg);
height: 35px; height: 35px;
...@@ -831,7 +427,6 @@ onMounted(async () => { ...@@ -831,7 +427,6 @@ onMounted(async () => {
margin-top: 5px; margin-top: 5px;
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
.line { .line {
margin-right: 7px; margin-right: 7px;
margin-top: 16px; margin-top: 16px;
...@@ -839,11 +434,14 @@ onMounted(async () => { ...@@ -839,11 +434,14 @@ onMounted(async () => {
height: 2px; height: 2px;
background: rgba(174, 208, 255, 1); background: rgba(174, 208, 255, 1);
} }
.text { .text {
width: 100px;
margin-right: 4px; margin-right: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.icon { .icon {
margin-top: 7px; margin-top: 7px;
...@@ -857,6 +455,7 @@ onMounted(async () => { ...@@ -857,6 +455,7 @@ onMounted(async () => {
} }
} }
} }
.bottom-bone1 { .bottom-bone1 {
position: absolute; position: absolute;
top: 280px; top: 280px;
...@@ -873,13 +472,11 @@ onMounted(async () => { ...@@ -873,13 +472,11 @@ onMounted(async () => {
left: -150px; left: -150px;
width: 150px; width: 150px;
height: 260px; height: 260px;
// background: orange;
.left-bone-item { .left-bone-item {
transform: skew(30deg); transform: skew(30deg);
height: 35px; height: 35px;
margin-bottom: 5px; margin-bottom: 5px;
margin-top: 15px; margin-top: 15px;
// background: #fff;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.icon { .icon {
...@@ -892,9 +489,13 @@ onMounted(async () => { ...@@ -892,9 +489,13 @@ onMounted(async () => {
} }
} }
.text { .text {
width: 100px;
margin-left: 4px; margin-left: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.line { .line {
margin-left: 7px; margin-left: 7px;
...@@ -912,7 +513,6 @@ onMounted(async () => { ...@@ -912,7 +513,6 @@ onMounted(async () => {
right: -150px; right: -150px;
width: 150px; width: 150px;
height: 260px; height: 260px;
// background: pink;
.right-bone-item { .right-bone-item {
transform: skew(30deg); transform: skew(30deg);
height: 35px; height: 35px;
...@@ -920,7 +520,6 @@ onMounted(async () => { ...@@ -920,7 +520,6 @@ onMounted(async () => {
margin-top: 5px; margin-top: 5px;
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
.line { .line {
margin-right: 7px; margin-right: 7px;
margin-top: 16px; margin-top: 16px;
...@@ -928,11 +527,14 @@ onMounted(async () => { ...@@ -928,11 +527,14 @@ onMounted(async () => {
height: 2px; height: 2px;
background: rgba(174, 208, 255, 1); background: rgba(174, 208, 255, 1);
} }
.text { .text {
width: 100px;
margin-right: 4px; margin-right: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.icon { .icon {
margin-top: 7px; margin-top: 7px;
...@@ -946,6 +548,7 @@ onMounted(async () => { ...@@ -946,6 +548,7 @@ onMounted(async () => {
} }
} }
} }
.bottom-bone2 { .bottom-bone2 {
position: absolute; position: absolute;
top: 280px; top: 280px;
...@@ -962,13 +565,11 @@ onMounted(async () => { ...@@ -962,13 +565,11 @@ onMounted(async () => {
left: -150px; left: -150px;
width: 150px; width: 150px;
height: 260px; height: 260px;
// background: orange;
.left-bone-item { .left-bone-item {
transform: skew(30deg); transform: skew(30deg);
height: 35px; height: 35px;
margin-bottom: 5px; margin-bottom: 5px;
margin-top: 15px; margin-top: 15px;
// background: #fff;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.icon { .icon {
...@@ -981,9 +582,13 @@ onMounted(async () => { ...@@ -981,9 +582,13 @@ onMounted(async () => {
} }
} }
.text { .text {
width: 100px;
margin-left: 4px; margin-left: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.line { .line {
margin-left: 7px; margin-left: 7px;
...@@ -1001,7 +606,6 @@ onMounted(async () => { ...@@ -1001,7 +606,6 @@ onMounted(async () => {
right: -150px; right: -150px;
width: 150px; width: 150px;
height: 260px; height: 260px;
// background: pink;
.right-bone-item { .right-bone-item {
transform: skew(30deg); transform: skew(30deg);
height: 35px; height: 35px;
...@@ -1009,7 +613,6 @@ onMounted(async () => { ...@@ -1009,7 +613,6 @@ onMounted(async () => {
margin-top: 5px; margin-top: 5px;
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
.line { .line {
margin-right: 7px; margin-right: 7px;
margin-top: 16px; margin-top: 16px;
...@@ -1017,11 +620,14 @@ onMounted(async () => { ...@@ -1017,11 +620,14 @@ onMounted(async () => {
height: 2px; height: 2px;
background: rgba(174, 208, 255, 1); background: rgba(174, 208, 255, 1);
} }
.text { .text {
width: 100px;
margin-right: 4px; margin-right: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.icon { .icon {
margin-top: 7px; margin-top: 7px;
......
[ [
{ {
"id": null, "yearDomainCount": [
"name": "2025-10-09", {
"count": 10 "sanTypeName": null,
}, "year": 2020,
{ "domainCountInfo": [
"id": null, {
"name": "2025-10-08", "year": 2020,
"count": 8 "id": "1",
}, "name": "人工智能",
{ "count": 11
"id": null, },
"name": "2025-09-16", {
"count": 16 "year": 2020,
"id": "2",
"name": "生物科技",
"count": 2
},
{
"year": 2020,
"id": "3",
"name": "新一代信息技术",
"count": 13
},
{
"year": 2020,
"id": "4",
"name": "量子科技",
"count": 0
},
{
"year": 2020,
"id": "5",
"name": "新能源",
"count": 0
},
{
"year": 2020,
"id": "6",
"name": "集成电路",
"count": 1
},
{
"year": 2020,
"id": "7",
"name": "海洋",
"count": 47
},
{
"year": 2020,
"id": "8",
"name": "先进制造",
"count": 22
},
{
"year": 2020,
"id": "9",
"name": "新材料",
"count": 0
},
{
"year": 2020,
"id": "10",
"name": "航空航天",
"count": 18
},
{
"year": 2020,
"id": "99",
"name": "其他",
"count": 0
},
{
"year": 2020,
"id": "13",
"name": "太空",
"count": 0
},
{
"year": 2020,
"id": "11",
"name": "深海",
"count": 0
},
{
"year": 2020,
"id": "12",
"name": "极地",
"count": 0
},
{
"year": 2020,
"id": "14",
"name": "核",
"count": 10
}
]
},
{
"sanTypeName": null,
"year": 2021,
"domainCountInfo": [
{
"year": 2021,
"id": "1",
"name": "人工智能",
"count": 15
},
{
"year": 2021,
"id": "2",
"name": "生物科技",
"count": 12
},
{
"year": 2021,
"id": "3",
"name": "新一代信息技术",
"count": 5
},
{
"year": 2021,
"id": "4",
"name": "量子科技",
"count": 7
},
{
"year": 2021,
"id": "5",
"name": "新能源",
"count": 3
},
{
"year": 2021,
"id": "6",
"name": "集成电路",
"count": 22
},
{
"year": 2021,
"id": "7",
"name": "海洋",
"count": 5
},
{
"year": 2021,
"id": "8",
"name": "先进制造",
"count": 27
},
{
"year": 2021,
"id": "9",
"name": "新材料",
"count": 7
},
{
"year": 2021,
"id": "10",
"name": "航空航天",
"count": 4
},
{
"year": 2021,
"id": "99",
"name": "其他",
"count": 0
},
{
"year": 2021,
"id": "13",
"name": "太空",
"count": 0
},
{
"year": 2021,
"id": "11",
"name": "深海",
"count": 3
},
{
"year": 2021,
"id": "12",
"name": "极地",
"count": 0
},
{
"year": 2021,
"id": "14",
"name": "核",
"count": 3
}
]
},
{
"sanTypeName": null,
"year": 2022,
"domainCountInfo": [
{
"year": 2022,
"id": "1",
"name": "人工智能",
"count": 22
},
{
"year": 2022,
"id": "2",
"name": "生物科技",
"count": 0
},
{
"year": 2022,
"id": "3",
"name": "新一代信息技术",
"count": 2
},
{
"year": 2022,
"id": "4",
"name": "量子科技",
"count": 0
},
{
"year": 2022,
"id": "5",
"name": "新能源",
"count": 0
},
{
"year": 2022,
"id": "6",
"name": "集成电路",
"count": 33
},
{
"year": 2022,
"id": "7",
"name": "海洋",
"count": 7
},
{
"year": 2022,
"id": "8",
"name": "先进制造",
"count": 27
},
{
"year": 2022,
"id": "9",
"name": "新材料",
"count": 1
},
{
"year": 2022,
"id": "10",
"name": "航空航天",
"count": 12
},
{
"year": 2022,
"id": "99",
"name": "其他",
"count": 0
},
{
"year": 2022,
"id": "13",
"name": "太空",
"count": 0
},
{
"year": 2022,
"id": "11",
"name": "深海",
"count": 2
},
{
"year": 2022,
"id": "12",
"name": "极地",
"count": 0
},
{
"year": 2022,
"id": "14",
"name": "核",
"count": 0
}
]
},
{
"sanTypeName": null,
"year": 2023,
"domainCountInfo": [
{
"year": 2023,
"id": "1",
"name": "人工智能",
"count": 31
},
{
"year": 2023,
"id": "2",
"name": "生物科技",
"count": 5
},
{
"year": 2023,
"id": "3",
"name": "新一代信息技术",
"count": 3
},
{
"year": 2023,
"id": "4",
"name": "量子科技",
"count": 1
},
{
"year": 2023,
"id": "5",
"name": "新能源",
"count": 0
},
{
"year": 2023,
"id": "6",
"name": "集成电路",
"count": 82
},
{
"year": 2023,
"id": "7",
"name": "海洋",
"count": 7
},
{
"year": 2023,
"id": "8",
"name": "先进制造",
"count": 36
},
{
"year": 2023,
"id": "9",
"name": "新材料",
"count": 1
},
{
"year": 2023,
"id": "10",
"name": "航空航天",
"count": 55
},
{
"year": 2023,
"id": "99",
"name": "其他",
"count": 0
},
{
"year": 2023,
"id": "13",
"name": "太空",
"count": 1
},
{
"year": 2023,
"id": "11",
"name": "深海",
"count": 0
},
{
"year": 2023,
"id": "12",
"name": "极地",
"count": 0
},
{
"year": 2023,
"id": "14",
"name": "核",
"count": 7
}
]
},
{
"sanTypeName": null,
"year": 2024,
"domainCountInfo": [
{
"year": 2024,
"id": "1",
"name": "人工智能",
"count": 33
},
{
"year": 2024,
"id": "2",
"name": "生物科技",
"count": 0
},
{
"year": 2024,
"id": "3",
"name": "新一代信息技术",
"count": 10
},
{
"year": 2024,
"id": "4",
"name": "量子科技",
"count": 22
},
{
"year": 2024,
"id": "5",
"name": "新能源",
"count": 0
},
{
"year": 2024,
"id": "6",
"name": "集成电路",
"count": 190
},
{
"year": 2024,
"id": "7",
"name": "海洋",
"count": 0
},
{
"year": 2024,
"id": "8",
"name": "先进制造",
"count": 27
},
{
"year": 2024,
"id": "9",
"name": "新材料",
"count": 13
},
{
"year": 2024,
"id": "10",
"name": "航空航天",
"count": 29
},
{
"year": 2024,
"id": "99",
"name": "其他",
"count": 0
},
{
"year": 2024,
"id": "13",
"name": "太空",
"count": 0
},
{
"year": 2024,
"id": "11",
"name": "深海",
"count": 0
},
{
"year": 2024,
"id": "12",
"name": "极地",
"count": 0
},
{
"year": 2024,
"id": "14",
"name": "核",
"count": 2
}
]
},
{
"sanTypeName": null,
"year": 2025,
"domainCountInfo": [
{
"year": 2025,
"id": "1",
"name": "人工智能",
"count": 12
},
{
"year": 2025,
"id": "2",
"name": "生物科技",
"count": 0
},
{
"year": 2025,
"id": "3",
"name": "新一代信息技术",
"count": 11
},
{
"year": 2025,
"id": "4",
"name": "量子科技",
"count": 9
},
{
"year": 2025,
"id": "5",
"name": "新能源",
"count": 0
},
{
"year": 2025,
"id": "6",
"name": "集成电路",
"count": 35
},
{
"year": 2025,
"id": "7",
"name": "海洋",
"count": 0
},
{
"year": 2025,
"id": "8",
"name": "先进制造",
"count": 42
},
{
"year": 2025,
"id": "9",
"name": "新材料",
"count": 11
},
{
"year": 2025,
"id": "10",
"name": "航空航天",
"count": 26
},
{
"year": 2025,
"id": "99",
"name": "其他",
"count": 0
},
{
"year": 2025,
"id": "13",
"name": "太空",
"count": 1
},
{
"year": 2025,
"id": "11",
"name": "深海",
"count": 0
},
{
"year": 2025,
"id": "12",
"name": "极地",
"count": 0
},
{
"year": 2025,
"id": "14",
"name": "核",
"count": 1
}
]
}
],
"domians": [
{
"year": null,
"id": "1",
"name": "人工智能",
"count": null
},
{
"year": null,
"id": "2",
"name": "生物科技",
"count": null
},
{
"year": null,
"id": "3",
"name": "新一代信息技术",
"count": null
},
{
"year": null,
"id": "4",
"name": "量子科技",
"count": null
},
{
"year": null,
"id": "5",
"name": "新能源",
"count": null
},
{
"year": null,
"id": "6",
"name": "集成电路",
"count": null
},
{
"year": null,
"id": "7",
"name": "海洋",
"count": null
},
{
"year": null,
"id": "8",
"name": "先进制造",
"count": null
},
{
"year": null,
"id": "9",
"name": "新材料",
"count": null
},
{
"year": null,
"id": "10",
"name": "航空航天",
"count": null
},
{
"year": null,
"id": "99",
"name": "其他",
"count": null
},
{
"year": null,
"id": "13",
"name": "太空",
"count": null
},
{
"year": null,
"id": "11",
"name": "深海",
"count": null
},
{
"year": null,
"id": "12",
"name": "极地",
"count": null
},
{
"year": null,
"id": "14",
"name": "核",
"count": null
}
]
} }
] ]
\ No newline at end of file
<template> <template>
<div class="fishbone"> <div class="fishbone-wrapper">
<div class="main-line"></div> <div class="fishbone-scroll-container" ref="scrollContainerRef">
<div v-for="(causeGroup, groupIndex) in fishboneData.causes" :key="groupIndex" :class="getBoneClass(groupIndex)"> <div class="fishbone" ref="fishboneRef" v-if="fishboneData.length > 0">
<div class="left-bone"> <div class="main-line" :style="{ width: (fishboneData.length / 2) * 340 - 275 + 'px' }"></div>
<div class="left-bone-item" v-for="(item, index) in getLeftItems(causeGroup.causes)" :key="index"> <!-- 奇数索引的数据组放在上方 -->
<!-- <div class="icon"> <div
<img :src="item.picture" alt="" /> v-for="(causeGroup, groupIndex) in getOddGroups(fishboneData)"
</div> --> :key="'top-' + groupIndex"
<div class="text">{{ item.name }}</div> :class="getTopBoneClass(groupIndex)"
<div class="line"></div> :style="{ left: groupIndex * 300 + 400 + 'px' }"
>
<div class="left-bone">
<div
class="left-bone-item"
v-for="(item, index) in getLeftItems(causeGroup.causes)"
:key="'left-' + index"
>
<div class="text">{{ 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>
<div class="text">{{ item.name }}</div>
</div>
</div>
</div> </div>
</div>
<div class="right-bone"> <!-- 偶数索引的数据组放在下方 -->
<div class="right-bone-item" v-for="(item, index) in getRightItems(causeGroup.causes)" :key="index"> <div
<div class="line"></div> v-for="(causeGroup, groupIndex) in getEvenGroups(fishboneData)"
<div class="text">{{ item.name }}</div> :key="'bottom-' + groupIndex"
<!-- <div class="icon"> :class="getBottomBoneClass(groupIndex)"
<img :src="item.picture" :alt="item.name" /> :style="{ left: groupIndex * 300 + 200 + 'px' }"
</div> --> >
<div class="left-bone">
<div
class="left-bone-item"
v-for="(item, index) in getLeftItems(causeGroup.causes)"
:key="'left-bottom-' + index"
>
<div class="text">{{ 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>
<div class="text">{{ item.name }}</div>
</div>
</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="scroll-indicators" v-if="showScrollIndicator">
<div class="scroll-btn left" :class="{ disabled: !canScrollLeft }" @click="scrollLeft">‹</div>
<div class="scroll-btn right" :class="{ disabled: !canScrollRight }" @click="scrollRight">›</div>
</div> -->
</div> </div>
</template> </template>
<script setup> <script setup>
import { getChainFishbone } from "@/api/exportControl"; import { getChainFishbone } from "@/api/exportControl";
import { onMounted, ref } from "vue"; import { onMounted, ref, nextTick, watch } from "vue";
// 这儿需要接收父组件传递来的产业链ID
const chainId = ref(1); const props = defineProps({
const fishboneData = ref({ chainId: {
text: "", type: Number,
causes: [] default: 1
}
}); });
// 根据索引确定鱼骨图位置类名 // const chainId = ref(1);
const getBoneClass = index => { const fishboneData = ref([]);
const positions = ["top-bone", "top-bone1", "top-bone2", "bottom-bone", "bottom-bone1", "bottom-bone2"]; const scrollContainerRef = ref(null);
return positions[index] || "top-bone"; const fishboneRef = ref(null);
const showScrollIndicator = ref(false);
const canScrollLeft = ref(false);
const canScrollRight = ref(true);
// 获取奇数索引的数据组(放在上方)
const getOddGroups = data => {
console.log(
"getOddGroups:",
data.filter((_, index) => index % 2 === 1)
);
return data.filter((_, index) => index % 2 === 1);
};
// 获取偶数索引的数据组(放在下方)
const getEvenGroups = data => {
console.log(
"getEvenGroups:",
data.filter((_, index) => index % 2 === 0)
);
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";
}; };
// 获取左侧显示的项目(前半部分) // 获取左侧显示的项目(前半部分)
...@@ -52,32 +135,109 @@ const getRightItems = items => { ...@@ -52,32 +135,109 @@ const getRightItems = items => {
return items.slice(midpoint); return items.slice(midpoint);
}; };
// 检查滚动状态
const updateScrollState = () => {
if (!scrollContainerRef.value) return;
const container = scrollContainerRef.value;
canScrollLeft.value = container.scrollLeft > 0;
canScrollRight.value = container.scrollLeft < container.scrollWidth - container.clientWidth;
};
// 滚动处理
const scrollLeft = () => {
if (scrollContainerRef.value) {
scrollContainerRef.value.scrollBy({ left: -200, behavior: "smooth" });
}
};
const scrollRight = () => {
if (scrollContainerRef.value) {
scrollContainerRef.value.scrollBy({ left: 200, behavior: "smooth" });
}
};
// 处理滚动事件
const handleScroll = () => {
updateScrollState();
};
onMounted(async () => { onMounted(async () => {
try { try {
const chainFishboneData = await getChainFishbone(chainId.value); const chainFishboneData = await getChainFishbone(props.chainId);
fishboneData.value = chainFishboneData ?? { fishboneData.value = chainFishboneData?.causes ?? [];
text: "",
causes: [] // 等待DOM更新后检查是否需要滚动
}; nextTick(() => {
if (scrollContainerRef.value && fishboneRef.value) {
showScrollIndicator.value = fishboneRef.value.scrollWidth > scrollContainerRef.value.clientWidth;
updateScrollState();
}
});
console.log("鱼骨图数据:", fishboneData.value);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
}); });
// 监听props中的chainId变化
watch(
() => props.chainId,
async () => {
try {
const chainFishboneData = await getChainFishbone(props.chainId);
fishboneData.value = chainFishboneData?.causes ?? [];
} catch (error) {
console.log(error);
}
}
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.fishbone-wrapper {
position: relative;
width: 100%;
height: 100%;
}
.fishbone-scroll-container {
display: flex;
align-items: center;
width: 100%;
height: 100%;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: thin;
scrollbar-color: rgba(144, 202, 249, 0.5) transparent;
&::-webkit-scrollbar {
height: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(144, 202, 249, 0.5);
border-radius: 3px;
}
}
/* ... 原有的样式保持不变 ... */ /* ... 原有的样式保持不变 ... */
.fishbone { .fishbone {
position: relative; position: relative;
width: 100%; width: fit-content;
height: 100%; height: 100%;
margin-top: 40px; margin-top: 40px;
min-width: 100%;
padding-left: 275px;
.main-line { .main-line {
position: absolute; margin-top: 280px;
top: 280px; width: 1888px;
right: 0;
width: 888px;
height: 3px; height: 3px;
background: rgba(174, 208, 255, 1); background: rgba(174, 208, 255, 1);
} }
...@@ -106,15 +266,6 @@ onMounted(async () => { ...@@ -106,15 +266,6 @@ onMounted(async () => {
margin-top: 15px; margin-top: 15px;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.text { .text {
margin-left: 4px; margin-left: 4px;
height: 70px; height: 70px;
...@@ -154,7 +305,7 @@ onMounted(async () => { ...@@ -154,7 +305,7 @@ onMounted(async () => {
background: rgba(174, 208, 255, 1); background: rgba(174, 208, 255, 1);
} }
.text { .text {
width: 100px; width: 100px;
margin-right: 4px; margin-right: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
...@@ -162,15 +313,6 @@ onMounted(async () => { ...@@ -162,15 +313,6 @@ onMounted(async () => {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
} }
} }
} }
...@@ -198,17 +340,8 @@ onMounted(async () => { ...@@ -198,17 +340,8 @@ onMounted(async () => {
margin-top: 15px; margin-top: 15px;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.text { .text {
width: 100px; width: 100px;
margin-left: 4px; margin-left: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
...@@ -247,7 +380,7 @@ onMounted(async () => { ...@@ -247,7 +380,7 @@ onMounted(async () => {
background: rgba(174, 208, 255, 1); background: rgba(174, 208, 255, 1);
} }
.text { .text {
width: 100px; width: 100px;
margin-right: 4px; margin-right: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
...@@ -255,15 +388,6 @@ onMounted(async () => { ...@@ -255,15 +388,6 @@ onMounted(async () => {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
} }
} }
} }
...@@ -291,17 +415,8 @@ onMounted(async () => { ...@@ -291,17 +415,8 @@ onMounted(async () => {
margin-top: 15px; margin-top: 15px;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.text { .text {
width: 100px; width: 100px;
margin-left: 4px; margin-left: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
...@@ -340,7 +455,7 @@ onMounted(async () => { ...@@ -340,7 +455,7 @@ onMounted(async () => {
background: rgba(174, 208, 255, 1); background: rgba(174, 208, 255, 1);
} }
.text { .text {
width: 100px; width: 100px;
margin-right: 4px; margin-right: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
...@@ -348,15 +463,6 @@ onMounted(async () => { ...@@ -348,15 +463,6 @@ onMounted(async () => {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
} }
} }
} }
...@@ -384,17 +490,8 @@ onMounted(async () => { ...@@ -384,17 +490,8 @@ onMounted(async () => {
margin-top: 15px; margin-top: 15px;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.text { .text {
width: 100px; width: 100px;
margin-left: 4px; margin-left: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
...@@ -433,7 +530,7 @@ onMounted(async () => { ...@@ -433,7 +530,7 @@ onMounted(async () => {
background: rgba(174, 208, 255, 1); background: rgba(174, 208, 255, 1);
} }
.text { .text {
width: 100px; width: 100px;
margin-right: 4px; margin-right: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
...@@ -441,15 +538,6 @@ onMounted(async () => { ...@@ -441,15 +538,6 @@ onMounted(async () => {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
} }
} }
} }
...@@ -477,17 +565,8 @@ onMounted(async () => { ...@@ -477,17 +565,8 @@ onMounted(async () => {
margin-top: 15px; margin-top: 15px;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.text { .text {
width: 100px; width: 100px;
margin-left: 4px; margin-left: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
...@@ -526,7 +605,7 @@ onMounted(async () => { ...@@ -526,7 +605,7 @@ onMounted(async () => {
background: rgba(174, 208, 255, 1); background: rgba(174, 208, 255, 1);
} }
.text { .text {
width: 100px; width: 100px;
margin-right: 4px; margin-right: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
...@@ -534,15 +613,6 @@ onMounted(async () => { ...@@ -534,15 +613,6 @@ onMounted(async () => {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
} }
} }
} }
...@@ -570,17 +640,8 @@ onMounted(async () => { ...@@ -570,17 +640,8 @@ onMounted(async () => {
margin-top: 15px; margin-top: 15px;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.text { .text {
width: 100px; width: 100px;
margin-left: 4px; margin-left: 4px;
height: 35px; height: 35px;
line-height: 35px; line-height: 35px;
...@@ -627,16 +688,49 @@ onMounted(async () => { ...@@ -627,16 +688,49 @@ onMounted(async () => {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
} }
} }
} }
.scroll-indicators {
position: absolute;
top: 50%;
left: 0;
right: 0;
transform: translateY(-50%);
display: flex;
justify-content: space-between;
pointer-events: none;
padding: 0 10px;
z-index: 10;
}
.scroll-btn {
width: 30px;
height: 30px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.8);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: bold;
color: #90caf9;
cursor: pointer;
pointer-events: auto;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
transition: all 0.2s ease;
&:hover:not(.disabled) {
background: #90caf9;
color: white;
transform: scale(1.1);
}
&.disabled {
color: #c0c4cc;
cursor: not-allowed;
background: rgba(255, 255, 255, 0.5);
}
}
</style> </style>
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<div class="chartsWrap"> <div class="chartsWrap">
<div class="right-main-content"> <div class="right-main-content">
<div class="right-main-content-main"> <div class="right-main-content-main">
<Fishbone /> <Fishbone :chainId="activeButtonId" />
</div> </div>
<div class="right-main-content-footer"> <div class="right-main-content-footer">
<div class="footer-item1"> <div class="footer-item1">
...@@ -76,30 +76,12 @@ import Echarts from "@/components/Chart/index.vue"; ...@@ -76,30 +76,12 @@ import Echarts from "@/components/Chart/index.vue";
import Hint from "./hint.vue"; import Hint from "./hint.vue";
import ButtonList from "@/components/buttonList/buttonList.vue"; import ButtonList from "@/components/buttonList/buttonList.vue";
import Fishbone from "./fishbone.vue"; import Fishbone from "./fishbone.vue";
import college1 from "../../assets/images/college1.png";
import college2 from "../../assets/images/college2.png";
import college3 from "../../assets/images/college3.png";
import college4 from "../../assets/images/college4.png";
import college5 from "../../assets/images/college5.png";
import college6 from "../../assets/images/college6.png";
import college7 from "../../assets/images/college7.png";
import college8 from "../../assets/images/college8.png";
import college9 from "../../assets/images/college9.png";
import college10 from "../../assets/images/college10.png";
import college11 from "../../assets/images/college11.png";
import { getHorizontalBarChart2 } from "../../utils/charts"; import { getHorizontalBarChart2 } from "../../utils/charts";
import { getDomainDistribution, getChainEntities } from "@/api/exportControl"; import { getDomainDistribution, getChainEntities } from "@/api/exportControl";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
const route = useRoute(); const route = useRoute();
const buttonList = ref([ const buttonList = ref([]);
{ id: 1, text: "新能源" }, const activeButtonId = ref(buttonList.value[0]?.id || 1);
{ id: 2, text: "半导体" },
{ id: 3, text: "跨境电商" },
{ id: 4, text: "金融业" },
{ id: 5, text: "军工" },
{ id: 6, text: "贸易" }
]);
const activeButtonId = ref(buttonList.value[0].id);
const setActiveButtonId = id => { const setActiveButtonId = id => {
activeButtonId.value = id; activeButtonId.value = id;
}; };
...@@ -178,10 +160,12 @@ const fetchDomainDistribution = async () => { ...@@ -178,10 +160,12 @@ const fetchDomainDistribution = async () => {
horizontalBarOptions.value = getHorizontalBarChart2(yAxisData, seriesData, false); horizontalBarOptions.value = getHorizontalBarChart2(yAxisData, seriesData, false);
// 更新buttonList // 更新buttonList
buttonList.value = sortedData.map(item => ({ buttonList.value = sortedData
id: item.id, .map(item => ({
text: item.name id: item.id,
})); text: item.name
}))
.sort((a, b) => a.id - b.id);
console.log("buttonList.value", buttonList.value); console.log("buttonList.value", buttonList.value);
setActiveButtonId(buttonList.value[0].id); setActiveButtonId(buttonList.value[0].id);
} }
......
...@@ -8,9 +8,23 @@ ...@@ -8,9 +8,23 @@
import Echarts from "@/components/Chart/index.vue"; import Echarts from "@/components/Chart/index.vue";
import { getMapOption } from "../../utils/charts"; import { getMapOption } from "../../utils/charts";
import { ref, onMounted, shallowRef } from "vue"; import { ref, onMounted, shallowRef } from "vue";
import { getAreaDistribution } from "@/api/exportControl";
// 这儿接收父组件传递过来的date参数
const props = defineProps({
date: {
type: String,
default: ""
}
});
const mapOption = shallowRef({}); const mapOption = shallowRef({});
onMounted(() => { onMounted(() => {
mapOption.value = getMapOption(); mapOption.value = getMapOption();
// 区域分布查询
getAreaDistribution(props.date).then(res => {
console.log("res", res);
});
}); });
</script> </script>
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
size="mini" size="mini"
v-model="domainValue" v-model="domainValue"
placeholder="领域选择" placeholder="领域选择"
@change="handleDomainChange"
> >
<el-option v-for="item in domainOptions" :key="item.value" :label="item.label" :value="item.value"> <el-option v-for="item in domainOptions" :key="item.value" :label="item.label" :value="item.value">
</el-option> </el-option>
...@@ -24,7 +25,8 @@ ...@@ -24,7 +25,8 @@
</template> </template>
<div class="subPanel1"> <div class="subPanel1">
<div class="chartsWrap" :style="{ paddingBottom: '10px' }"> <div class="chartsWrap" :style="{ paddingBottom: '10px' }">
<Echarts :option="bar1Option" height="100%"></Echarts> <Echarts v-if="!bar1DataIsEmpty" :option="bar1Option" height="100%"></Echarts>
<el-empty v-else description="暂无数据" />
</div> </div>
<Hint text="近几次新增受制裁实体中,中国实体占比提高,美方针对中国的出口管制风险显著增加。"></Hint> <Hint text="近几次新增受制裁实体中,中国实体占比提高,美方针对中国的出口管制风险显著增加。"></Hint>
</div> </div>
...@@ -123,7 +125,7 @@ onMounted(async () => { ...@@ -123,7 +125,7 @@ onMounted(async () => {
try { try {
const [entitiesGrowthTrendData, entitiesUpdateCountData] = await Promise.all([ const [entitiesGrowthTrendData, entitiesUpdateCountData] = await Promise.all([
getEntitiesGrowthTrend(), getEntitiesGrowthTrend(),
getEntitiesUpdateCount() getEntitiesUpdateCount(1)
]); ]);
const list = _.reverse(entitiesGrowthTrendData); const list = _.reverse(entitiesGrowthTrendData);
...@@ -133,12 +135,25 @@ onMounted(async () => { ...@@ -133,12 +135,25 @@ onMounted(async () => {
}); });
line1Option.value = getLineChart({ xAxisData, seriesData, name: "增长趋势", color: "rgba(146, 84, 222, 1)" }, true); line1Option.value = getLineChart({ xAxisData, seriesData, name: "增长趋势", color: "rgba(146, 84, 222, 1)" }, true);
bar2Option.value = getBarChart( if (entitiesUpdateCountData && Array.isArray(entitiesUpdateCountData)) {
_.reverse(entitiesUpdateCountData.xAxis), const sortedData = _.sortBy(entitiesUpdateCountData, "year");
_.reverse(entitiesUpdateCountData.series),
["rgba(22, 119, 255, 1)", "rgba(22, 119, 255, 0)"], // 提取 x 轴数据(年份)
"更新频率" const xAxisData = sortedData.map(item => item.year.toString());
);
// 提取 y 轴数据(数量)
const seriesData = sortedData.map(item => item.count);
bar2Option.value = getBarChart(xAxisData, seriesData, ["rgba(22, 119, 255, 1)", "rgba(22, 119, 255, 0)"], "更新频率");
} else {
// 保持原有逻辑以防数据格式不符合预期
bar2Option.value = getBarChart(
_.reverse(entitiesUpdateCountData.xAxis),
_.reverse(entitiesUpdateCountData.series),
["rgba(22, 119, 255, 1)", "rgba(22, 119, 255, 0)"],
"更新频率"
);
}
// 获取重点实体列表数据 // 获取重点实体列表数据
await fetchKeyEntityList(route.query.startTime); await fetchKeyEntityList(route.query.startTime);
} catch (err) { } catch (err) {
...@@ -248,11 +263,13 @@ const typeOptions = [ ...@@ -248,11 +263,13 @@ const typeOptions = [
const domainValue = ref(domainOptions[0].value); const domainValue = ref(domainOptions[0].value);
const typeValue = ref(typeOptions[0].value); const typeValue = ref(typeOptions[0].value);
const bar1Option = shallowRef({}); const bar1Option = shallowRef({});
const bar1DataIsEmpty = ref(false);
watch( watch(
[domainValue, typeValue], [domainValue, typeValue],
async ([domain, type]) => { async ([domain, type]) => {
let EntitiesChangeCount = await getEntitiesChangeCount(domain, type); let EntitiesChangeCount = await getEntitiesChangeCount(domain, type);
EntitiesChangeCount = _.reverse(EntitiesChangeCount); EntitiesChangeCount = _.reverse(EntitiesChangeCount);
bar1DataIsEmpty.value = EntitiesChangeCount.length === 0;
bar1Option.value = getBarChart( bar1Option.value = getBarChart(
_.map(EntitiesChangeCount, "year"), _.map(EntitiesChangeCount, "year"),
_.map(EntitiesChangeCount, "count"), _.map(EntitiesChangeCount, "count"),
...@@ -317,6 +334,10 @@ watch( ...@@ -317,6 +334,10 @@ watch(
await fetchKeyEntityList(route.query.startTime, newVal); await fetchKeyEntityList(route.query.startTime, newVal);
}, 300) }, 300)
); );
const handleDomainChange = async domain => {
await fetchKeyEntityList(route.query.startTime, value3.value, domain);
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
</CardCustom> </CardCustom>
</div> </div>
<div class="row"> <div class="row">
<CardCustom title="历制裁涉及领域数" :style="{ width: '798px', height: '422px' }"> <CardCustom title="历制裁涉及领域数" :style="{ width: '798px', height: '422px' }">
<div class="subPanel3"> <div class="subPanel3">
<div class="chartsWrap" :style="{ paddingBottom: '10px' }"> <div class="chartsWrap" :style="{ paddingBottom: '10px' }">
<Echarts :option="bar2Option" height="100%"></Echarts> <Echarts :option="bar2Option" height="100%"></Echarts>
...@@ -46,7 +46,12 @@ import { Search } from "@element-plus/icons-vue"; ...@@ -46,7 +46,12 @@ import { Search } from "@element-plus/icons-vue";
import Echarts from "@/components/Chart/index.vue"; import Echarts from "@/components/Chart/index.vue";
import { getBarChart, getLineChart, getPieOption1 } from "../../utils/charts"; import { getBarChart, getLineChart, getPieOption1 } from "../../utils/charts";
import Hint from "./hint.vue"; import Hint from "./hint.vue";
import { getEntitiesAreaCountByYear, getEntitiesDomainCount, getCountThisDomain } from "@/api/exportControl"; import {
getEntitiesAreaCountByYear,
getEntitiesDomainCount,
getCountThisDomain,
getDomainDistribution
} from "@/api/exportControl";
import _ from "lodash"; import _ from "lodash";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
const route = useRoute(); const route = useRoute();
...@@ -85,13 +90,18 @@ const fetchEntitiesDomainCount = async () => { ...@@ -85,13 +90,18 @@ const fetchEntitiesDomainCount = async () => {
onMounted(async () => { onMounted(async () => {
try { try {
const [entitiesAreaCountByYearData, countThisDomainData4, countThisDomainData10] = await Promise.all([ const [entitiesAreaCountByYearData, countThisDomainData4, countThisDomainData10] = await Promise.all([
getEntitiesAreaCountByYear(route.query.startTime), getDomainDistribution(route.query.startTime),
getCountThisDomain("4"), getCountThisDomain("4"),
// getEntitiesDomainCount(), // getEntitiesDomainCount(),
getCountThisDomain("10") getCountThisDomain("10")
]); ]);
pie1Option.value = getPieOption1(entitiesAreaCountByYearData ?? []); pie1Option.value = getPieOption1(
entitiesAreaCountByYearData.map(item => ({
name: item.name,
value: item.count
})) ?? []
);
const list4 = _.reverse(countThisDomainData4 ?? []); const list4 = _.reverse(countThisDomainData4 ?? []);
line1Option.value = getLineChart({ line1Option.value = getLineChart({
xAxisData: _.map(list4, "year"), xAxisData: _.map(list4, "year"),
......
...@@ -42,7 +42,7 @@ onMounted(async () => { ...@@ -42,7 +42,7 @@ onMounted(async () => {
.filter(item => item.count > 0) .filter(item => item.count > 0)
.map(item => { .map(item => {
return { return {
name: item?.type, name: item?.name,
value: item?.count value: item?.count
}; };
}) })
......
...@@ -57,7 +57,7 @@ ...@@ -57,7 +57,7 @@
<div class="panel3"> <div class="panel3">
<div class="chartWrap"> <div class="chartWrap">
<PieCharts v-if="panel3ActiveIndex === 1"></PieCharts> <PieCharts v-if="panel3ActiveIndex === 1"></PieCharts>
<MapCharts v-if="panel3ActiveIndex === 2"></MapCharts> <MapCharts v-if="panel3ActiveIndex === 2" :date="route.query.startTime"></MapCharts>
</div> </div>
<Hint text="本次制裁共新增83个实体,其中53个中国大陆实体、1个中国台湾实体。"></Hint> <Hint text="本次制裁共新增83个实体,其中53个中国大陆实体、1个中国台湾实体。"></Hint>
</div> </div>
...@@ -94,7 +94,7 @@ ...@@ -94,7 +94,7 @@
</div> </div>
</div> </div>
<div class="tableWrap"> <div class="tableWrap" ref="tableWrapRef" @scroll="handleScroll">
<el-table <el-table
:data="selectEntitiesList" :data="selectEntitiesList"
class="sanction-table" class="sanction-table"
...@@ -115,7 +115,7 @@ ...@@ -115,7 +115,7 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="domains" label="涉及领域" width="100"> <el-table-column prop="domains" label="涉及领域" width="180">
<template #default="{ row }"> <template #default="{ row }">
<div class="domain-tags"> <div class="domain-tags">
<el-tag v-for="tag in row.domains" :key="tag" :type="panel5TypeMap[tag]">{{ <el-tag v-for="tag in row.domains" :key="tag" :type="panel5TypeMap[tag]">{{
...@@ -125,11 +125,11 @@ ...@@ -125,11 +125,11 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="address" label="上市地点" width="90" align="center"> <!-- <el-table-column prop="address" label="上市地点" width="90" align="center">
<template #default="{ row }"> <template #default="{ row }">
{{ row.address }} {{ row.address }}
</template> </template>
</el-table-column> </el-table-column> -->
<el-table-column prop="time" label="制裁时间" width="120" align="center"> <el-table-column prop="time" label="制裁时间" width="120" align="center">
<template #default="{ row }"> <template #default="{ row }">
...@@ -137,11 +137,11 @@ ...@@ -137,11 +137,11 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="revenue" label="营收(亿元)" width="100" align="center"> <!-- <el-table-column prop="revenue" label="营收(亿元)" width="100" align="center">
<template #default="{ row }"> <template #default="{ row }">
{{ row.revenue }} {{ row.revenue }}
</template> </template>
</el-table-column> </el-table-column> -->
<el-table-column prop="subCompany" label="50%规则子企业" min-width="140" align="left"> <el-table-column prop="subCompany" label="50%规则子企业" min-width="140" align="left">
<template #default="{ row }"> <template #default="{ row }">
...@@ -183,7 +183,7 @@ import PieCharts from "../components/pieCharts.vue"; ...@@ -183,7 +183,7 @@ import PieCharts from "../components/pieCharts.vue";
import MapCharts from "../components/mapCharts.vue"; import MapCharts from "../components/mapCharts.vue";
import ButtonList from "@/components/buttonList/buttonList.vue"; import ButtonList from "@/components/buttonList/buttonList.vue";
import Hint from "../components/hint.vue"; import Hint from "../components/hint.vue";
import { onMounted, reactive, ref, shallowRef } from "vue"; import { onMounted, reactive, ref, shallowRef, watch } from "vue";
import panel1_1 from "../../assets/images/panel1_1.png"; import panel1_1 from "../../assets/images/panel1_1.png";
import panel2_1 from "../../assets/images/panel2_1.png"; import panel2_1 from "../../assets/images/panel2_1.png";
import panel2_2 from "../../assets/images/panel2_2.png"; import panel2_2 from "../../assets/images/panel2_2.png";
...@@ -197,7 +197,14 @@ import panel5_5 from "../../assets/images/panel5_5.png"; ...@@ -197,7 +197,14 @@ import panel5_5 from "../../assets/images/panel5_5.png";
import panel5_6 from "../../assets/images/panel5_6.png"; import panel5_6 from "../../assets/images/panel5_6.png";
import panel5_7 from "../../assets/images/panel5_7.png"; import panel5_7 from "../../assets/images/panel5_7.png";
import panel5_8 from "../../assets/images/panel5_8.png"; import panel5_8 from "../../assets/images/panel5_8.png";
import { getOrganizationInfo, getPersonList, getSanReasonSelect, getSelectEntitiesList } from "@/api/exportControl"; import {
getOrganizationInfo,
getPersonList,
getSanReasonSelect,
getSelectEntitiesList,
getEntitiesList
} from "@/api/exportControl";
import _ from "lodash"; import _ from "lodash";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { formatAnyDateToChinese } from "../../utils"; import { formatAnyDateToChinese } from "../../utils";
...@@ -246,15 +253,26 @@ const sanReasonSelect = shallowRef([ ...@@ -246,15 +253,26 @@ const sanReasonSelect = shallowRef([
text: "将中国定位为“通过强制技术转让获取先进制程的威胁”" text: "将中国定位为“通过强制技术转让获取先进制程的威胁”"
} }
]); ]);
const panel5IsChecked = ref(true);
const selectEntitiesList = shallowRef([]); const selectEntitiesList = shallowRef([]);
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(0);
const loading = ref(false);
const tableWrapRef = ref(null);
const noMoreData = ref(false);
onMounted(async () => { onMounted(async () => {
try { try {
const [organizationInfoData, personListData, sanReasonSelectData, selectEntitiesListData] = await Promise.all([ const [organizationInfoData, sanReasonSelectData] = await Promise.all([
getOrganizationInfo(), getOrganizationInfo(),
getPersonList(), // getPersonList(),
getSanReasonSelect(route.query.startTime), getSanReasonSelect(route.query.startTime)
getSelectEntitiesList(route.query.startTime) // getSelectEntitiesList(route.query.startTime)
]); ]);
console.log("organizationInfoData", organizationInfoData);
organizationInfo.value = { organizationInfo.value = {
img: panel1_1, img: panel1_1,
mingcheng: organizationInfoData?.orgNameZh, mingcheng: organizationInfoData?.orgNameZh,
...@@ -274,22 +292,103 @@ onMounted(async () => { ...@@ -274,22 +292,103 @@ onMounted(async () => {
return { text: item }; return { text: item };
}); });
selectEntitiesList.value = _.map(selectEntitiesListData, item => { // selectEntitiesList.value = _.map(selectEntitiesListData, item => {
return { // return {
name: item?.entityNameZh, // name: item?.entityNameZh,
domains: item.techDomainList, // domains: item.techDomainList,
// address: "--",
// time: formatAnyDateToChinese(item?.startTime),
// isUp: true,
// revenue: "--",
// subCompany: "--",
// img: ""
// };
// });
// 初始化加载第一页数据
await fetchEntitiesList(currentPage.value, pageSize.value);
} catch (err) {
console.log(err);
}
});
// 获取实体清单数据
const fetchEntitiesList = async (page = 1, size = 10) => {
if (loading.value || noMoreData.value) return;
loading.value = true;
try {
const res = await getEntitiesList("实体清单", page, size, route.query.startTime, panel5IsChecked.value);
if (res) {
const newData = res.content.map(item => ({
...item,
name: item.entityNameZh,
enName: item.entityName,
domains: item.techDomains,
time: formatAnyDateToChinese(item.startTime),
address: "--", address: "--",
time: formatAnyDateToChinese(item?.startTime),
isUp: true,
revenue: "--",
subCompany: "--", subCompany: "--",
img: "" img: ""
}; }));
});
// 如果是第一页,替换数据;否则追加数据
if (page === 1) {
selectEntitiesList.value = newData;
} else {
selectEntitiesList.value = [...selectEntitiesList.value, ...newData];
}
total.value = res.totalElements;
currentPage.value = res.number + 1;
// 检查是否还有更多数据
if (selectEntitiesList.value.length >= total.value) {
noMoreData.value = true;
}
}
} catch (err) { } catch (err) {
console.log(err); console.error(err);
if (currentPage.value > 1) {
currentPage.value--;
}
} finally {
loading.value = false;
} }
}); };
watch(
() => panel5IsChecked.value,
newVal => {
fetchEntitiesList(1, 10);
}
);
// 处理滚动事件
const debounceFlag = ref(false);
const handleScroll = () => {
if (!tableWrapRef.value || debounceFlag.value) return;
const { scrollTop, scrollHeight, clientHeight } = tableWrapRef.value;
// 当距离底部小于50px时加载更多
if (scrollHeight - scrollTop - clientHeight < 50) {
// 设置防抖标志
debounceFlag.value = true;
loadMore();
// 延迟重置防抖标志,防止连续触发
setTimeout(() => {
debounceFlag.value = false;
}, 300);
}
};
// 加载更多数据
const loadMore = () => {
if (!loading.value && !noMoreData.value) {
currentPage.value++;
fetchEntitiesList(currentPage.value, pageSize.value);
}
};
const panel3ActiveIndex = ref(1); const panel3ActiveIndex = ref(1);
const setPanel3ActiveIndex = index => { const setPanel3ActiveIndex = index => {
...@@ -304,7 +403,7 @@ const panel5ButtonAcitveID = ref(panel5ButtonList[0].id); ...@@ -304,7 +403,7 @@ const panel5ButtonAcitveID = ref(panel5ButtonList[0].id);
const panel5SetButtonAcitveID = id => { const panel5SetButtonAcitveID = id => {
panel5ButtonAcitveID.value = id; panel5ButtonAcitveID.value = id;
}; };
const panel5IsChecked = ref(true);
const panel5TypeMap = { const panel5TypeMap = {
人工智能: "danger", 人工智能: "danger",
通信网络: "warning", 通信网络: "warning",
...@@ -662,6 +761,10 @@ const panel6 = ref([ ...@@ -662,6 +761,10 @@ const panel6 = ref([
margin-top: 14px; margin-top: 14px;
min-height: 0; min-height: 0;
overflow: auto; overflow: auto;
.domain-tags {
display: flex;
gap: 4px;
}
} }
.name { .name {
display: flex; display: flex;
......
...@@ -35,14 +35,19 @@ ...@@ -35,14 +35,19 @@
</div> </div>
<div class="layout-main-header-right-box"> <div class="layout-main-header-right-box">
<div class="right-box-top"> <div class="right-box-top">
<div class="time">{{ "2025年7月" }}</div> <div class="time">{{ route.query.startTime }}</div>
<div class="name">{{ "美国商务部工业与安全局" }}</div> <div class="name">{{ "美国商务部工业与安全局" }}</div>
</div> </div>
<div class="right-box-bottom"> <div class="right-box-bottom">
<el-button type="plain" size="large" icon="Search" @click="handleSwitchActiveName('实体清单原文')" <el-button
type="plain"
size="large"
disabled
icon="Search"
@click="handleSwitchActiveName('实体清单原文')"
>实体清单原文</el-button >实体清单原文</el-button
> >
<el-button type="primary" size="large" icon="EditPen">分析报告</el-button> <el-button type="primary" size="large" disabled icon="EditPen">分析报告</el-button>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<div class="sub-title">{{ subtitle }}</div> <div class="sub-title">{{ subtitle }}</div>
</div> </div>
<div class="description">{{ description }}</div> <div class="description">{{ description }}</div>
<div v-if="quantity > 0" class="quantity" :style="{ color: color }">{{ quantity }}</div> <div v-if="quantity > 0" class="quantity" :style="{ color: color }">{{ quantity }}{{ unit || "个" }}</div>
</div> </div>
</div> </div>
</template> </template>
...@@ -30,6 +30,10 @@ defineProps({ ...@@ -30,6 +30,10 @@ defineProps({
type: [Number, String], type: [Number, String],
default: 0 default: 0
}, },
unit: {
type: String,
default: ""
},
color: { color: {
type: String, type: String,
default: "#409EFF" default: "#409EFF"
......
...@@ -67,13 +67,14 @@ ...@@ -67,13 +67,14 @@
</div> </div>
<div class="home-main-header-footer-info"> <div class="home-main-header-footer-info">
<InfoCard <InfoCard
v-for="item in infoList" v-for="(item, index) in infoList"
:key="item.title" :key="item.id"
:title="item.title" :title="item.nameZh"
:subtitle="item.subTitle" :subtitle="item.nameAbbr"
:description="item.des" :description="item.description"
:quantity="item.num" :quantity="item.postCount"
:color="item.color" unit="次"
:color="infoListColor[index]"
/> />
</div> </div>
</div> </div>
...@@ -90,95 +91,80 @@ ...@@ -90,95 +91,80 @@
<template #default> <template #default>
<div class="box1"> <div class="box1">
<!-- <el-image <el-carousel trigger="click" height="350px" :autoplay="true">
:src="box1Image" <el-carousel-item v-for="(item, index) in entitiesDataInfoList" :key="item.id + index">
alt="" <div>
style="width: 458px; height: 353px; object-fit: cover; flex-shrink: 0" <div class="box1-top">
></el-image> --> <div class="box1-top-title">
<!-- <div class="box1-right"> {{ item.postDate }}——BIS《实体清单增列与修订条目》
<div class="box1-right-title">关于进一步延长TikTok执法宽限期的行政令</div> </div>
<div class="box1-right-tags"> <div class="box1-top-content">
<el-tag type="primary">互联网</el-tag> <div class="box1-top-content-item">
<el-tag type="danger">人工智能</el-tag> <span class="box1-top-content-item-title">· 发布机构:</span>
</div> <span class="box1-top-content-item-content">{{ item.postOrgName }}</span>
<div class="box1-right-content"> </div>
9月16日,美国白宫官方网站发布总统政令,再次推迟(第四次)对TikTok禁令的执法,新的宽限期截止日为2025年12月16日​。在宽限期内及对于宽限期前的行为,司法部不得强制执行​《保护美国人免受外国对手控制应用程序法》或因此处罚相关实体​(如TikTok及其分发平台)。司法部还需向提供商发出无违规和无责任的信函,并强调执行该法的权力专属联邦司法部长,意在阻止各州或私人提起诉讼。 <div class="box1-top-content-item">
</div> <span class="box1-top-content-item-title">· 生效日期:</span>
<div class="box1-right-footer"> <span class="box1-top-content-item-content">{{ item.postDate }}</span>
<span class="box1-right-footer-time"> 2025年9月16日 </span> </div>
<el-button type="primary" link> <div class="box1-top-content-item">
美国白宫官方网站 <span class="box1-top-content-item-title">· 涉及领域:</span>
<el-image <div
src="./assets/images/icon-open.png" class="box1-top-content-item-tags"
alt="" v-for="domainItem in item.domains"
style="width: 16px; height: 16px; margin-left: 4px" :key="domainItem"
></el-image> >
</el-button> <el-tag
</div> :type="
</div> --> domainItem === '航空航天'
? 'primary'
<div class="box1-top"> : item === '人工智能'
<div class="box1-top-title"> ? 'danger'
{{ entitiesDataInfoReactive.startTime }}——BIS《实体清单增列与修订条目》 : 'info'
</div> "
<div class="box1-top-content"> >{{ domainItem }}</el-tag
<div class="box1-top-content-item"> >
<span class="box1-top-content-item-title">· 发布机构:</span> </div>
<span class="box1-top-content-item-content">{{ </div>
entitiesDataInfoReactive.orgName </div>
}}</span>
</div>
<div class="box1-top-content-item">
<span class="box1-top-content-item-title">· 生效日期:</span>
<span class="box1-top-content-item-content">{{
entitiesDataInfoReactive.startTime
}}</span>
</div>
<div class="box1-top-content-item">
<span class="box1-top-content-item-title">· 涉及领域:</span>
<div
class="box1-top-content-item-tags"
v-for="item in entitiesDataInfoReactive.domains"
:key="item"
>
<el-tag
:type="
item === '航空航天' ? 'primary' : item === '人工智能' ? 'danger' : 'info'
"
>{{ item }}</el-tag
>
</div> </div>
</div> <div class="box1-bottom">
</div> <div class="box1-bottom-title">· 涉及主要实体:</div>
</div> <div class="box1-bottom-content">
<div class="box1-bottom"> <div
<div class="box1-bottom-title">· 涉及主要实体:</div> class="box1-bottom-content-item"
<div class="box1-bottom-content"> v-for="(ett, index) in item.sanEntities"
<div :key="index"
class="box1-bottom-content-item" >
v-for="(item, index) in entitiesDataInfoReactive.entityList" <el-image
:key="index" v-if="ett.img"
> class="box1-bottom-content-item-img"
<el-image :src="ett.img"
v-if="item.img" alt=""
class="box1-bottom-content-item-img" ></el-image>
:src="item.img" <div v-else class="box1-bottom-content-item-imgUndefined">
alt="" {{
></el-image> (ett.entityNameZh || ett.enName)?.match(
<div v-else class="box1-bottom-content-item-imgUndefined"> /[\u4e00-\u9fa5a-zA-Z0-9]/
{{ (item.name || item.enName)?.match(/[\u4e00-\u9fa5a-zA-Z0-9]/)?.[0] }} )?.[0]
}}
</div>
<div class="box1-bottom-content-item-txt">
{{ ett.name || ett.entityNameZh }}
</div>
</div>
</div>
</div>
<div class="box1-absolute">
<div class="box1-absolute-des">
<el-icon><Warning color="rgba(206, 79, 81, 1)" /></el-icon>
<span>新增中国实体</span>
</div>
<div class="box1-absolute-num">{{ item.cnEntityCount }}</div>
</div> </div>
<div class="box1-bottom-content-item-txt">{{ item.name || item.enName }}</div>
</div> </div>
</div> </el-carousel-item>
</div> </el-carousel>
<div class="box1-absolute">
<div class="box1-absolute-des">
<el-icon><Warning color="rgba(206, 79, 81, 1)" /></el-icon>
<span>新增中国实体</span>
</div>
<div class="box1-absolute-num">{{ entitiesDataInfoReactive.chNum }}</div>
</div>
</div> </div>
</template> </template>
</custom-container> </custom-container>
...@@ -300,9 +286,11 @@ ...@@ -300,9 +286,11 @@
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="重点领域" width="180"> <el-table-column label="重点领域" width="180" align="center">
<template #default="scope"> <template #default="scope">
<div style="display: flex; align-items: center; gap: 5px"> <div
style="display: flex; justify-content: center; align-items: center; gap: 5px"
>
<el-tag <el-tag
v-for="tag in scope.row.tags" v-for="tag in scope.row.tags"
:key="tag" :key="tag"
...@@ -334,9 +322,11 @@ ...@@ -334,9 +322,11 @@
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="重点领域" width="180"> <el-table-column label="重点领域" width="180" align="center">
<template #default="scope"> <template #default="scope">
<div style="display: flex; align-items: center; gap: 5px"> <div
style="display: flex; justify-content: center; align-items: center; gap: 5px"
>
<el-tag <el-tag
v-for="tag in scope.row.tags" v-for="tag in scope.row.tags"
:key="tag" :key="tag"
...@@ -352,7 +342,7 @@ ...@@ -352,7 +342,7 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
</div> </div>
<div class="box3-content"> <div class="box3-content" style="display: none">
<div class="box3-content-title">关键与新兴技术清单(CETs)</div> <div class="box3-content-title">关键与新兴技术清单(CETs)</div>
<el-table :data="tableData1" stripe style="width: 100%"> <el-table :data="tableData1" stripe style="width: 100%">
<el-table-column prop="year" label="年份" width="100" /> <el-table-column prop="year" label="年份" width="100" />
...@@ -394,19 +384,19 @@ ...@@ -394,19 +384,19 @@
<el-row :gutter="20" style="width: 1600px; margin: 0 auto"> <el-row :gutter="20" style="width: 1600px; margin: 0 auto">
<el-col :span="8"> <el-col :span="8">
<custom-container title="制裁领域分析" :titleIcon="radarIcon" height="450px"> <custom-container title="制裁领域分析" :titleIcon="radarIcon" height="480px">
<template #header-right> <template #header-right>
<el-checkbox v-model="checked" label="50%规则" size="large" /> <el-checkbox v-model="domainChecked" label="50%规则" size="large" />
</template> </template>
<template #default> <template #default>
<EChart :option="radarOption" autoresize :style="{ height: '380px' }" /> <EChart :option="radarOption" autoresize :style="{ height: '450px' }" />
</template> </template>
</custom-container> </custom-container>
</el-col> </el-col>
<el-col :span="16"> <el-col :span="16">
<custom-container title="制裁清单数量增长趋势" :titleIcon="qushiIcon" height="450px"> <custom-container title="制裁清单数量增长趋势" :titleIcon="qushiIcon" height="480px">
<template #header-right> <template #header-right>
<el-checkbox v-model="checked" label="50%规则" size="large" /> <el-checkbox v-model="trendChecked" label="50%规则" size="large" />
</template> </template>
<template #default> <template #default>
<EChart :option="trendOption" autoresize :style="{ height: '400px' }" /> <EChart :option="trendOption" autoresize :style="{ height: '400px' }" />
...@@ -421,23 +411,25 @@ ...@@ -421,23 +411,25 @@
<custom-container title="历次制裁过程" :titleIcon="listIcon" height="845px"> <custom-container title="历次制裁过程" :titleIcon="listIcon" height="845px">
<template #default> <template #default>
<div class="box4"> <div class="box4">
<div class="box4-item" v-for="(item, idx) in sanctionProcessList" :key="item.title"> <div style="height: 90%; overflow-y: auto; padding-top: 10px">
<div class="box4-item-left"> <div class="box4-item" v-for="(item, idx) in sanctionProcessList" :key="item.title">
<el-image :src="dotIcon" alt="图片" class="box4-item-left-icon" /> <div class="box4-item-left">
<div class="box4-item-left-line" v-if="idx + 1 != sanctionProcessList.length"></div> <el-image :src="dotIcon" alt="图片" class="box4-item-left-icon" />
</div> <div class="box4-item-left-line" v-if="idx + 1 != sanctionProcessList.length"></div>
<div class="box4-item-right">
<div class="box4-item-right-header">
<span class="box4-item-right-header-title">{{ item.title }}</span>
<span class="box4-item-right-header-desc">{{ item.desc }}</span>
</div> </div>
<div class="box4-item-right-content"> <div class="box4-item-right">
{{ item.content }} <div class="box4-item-right-header" @click="handleSanc(item)">
<span class="box4-item-right-header-title">{{ item.title }}</span>
<span class="box4-item-right-header-desc">{{ item.desc }}</span>
</div>
<div class="box4-item-right-content">
{{ item.content }}
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="box4-footer" :style="{ marginTop: sanctionProcessList.length > 0 ? '0px' : 'auto' }"> <div class="box4-footer" :style="{ marginTop: sanctionProcessList.length > 0 ? '0px' : 'auto' }">
<el-button type="primary" link :icon="DownRight" <el-button type="primary" link :icon="DownRight" @click="handleGetMore"
>查看更多 >查看更多
<el-icon><DArrowRight /></el-icon> <el-icon><DArrowRight /></el-icon>
</el-button> </el-button>
...@@ -470,7 +462,7 @@ ...@@ -470,7 +462,7 @@
<el-table-column prop="name" label="实体名称" min-width="200"> <el-table-column prop="name" label="实体名称" min-width="200">
<template #default="scope"> <template #default="scope">
<div class="tableName"> <div class="tableName" @click="handleCompClick(scope.row)">
<el-image <el-image
v-if="scope.row.img" v-if="scope.row.img"
class="box1-bottom-content-item-img" class="box1-bottom-content-item-img"
...@@ -571,7 +563,7 @@ ...@@ -571,7 +563,7 @@
</template> </template>
<script setup> <script setup>
import { onMounted, ref, computed, reactive, shallowRef } from "vue"; import { onMounted, ref, computed, reactive, shallowRef, watch } from "vue";
import scrollToTop from "@/utils/scrollToTop"; import scrollToTop from "@/utils/scrollToTop";
import * as echarts from "echarts"; import * as echarts from "echarts";
import setChart from "@/utils/setChart"; import setChart from "@/utils/setChart";
...@@ -645,50 +637,45 @@ import { getMultipleBarChart_m } from "./utils/charts"; ...@@ -645,50 +637,45 @@ import { getMultipleBarChart_m } from "./utils/charts";
import { formatAnyDateToChinese } from "./utils"; import { formatAnyDateToChinese } from "./utils";
import _ from "lodash"; import _ from "lodash";
const handleCompClick = item => {
console.log("item", item);
const route = router.resolve({
path: "/companyPages",
query: {
id: item.id
}
});
window.open(route.href, "_blank");
};
const tagsType = ["primary", "success", "info", "warning", "danger"]; const tagsType = ["primary", "success", "info", "warning", "danger"];
//数据定义 //数据定义
const entitiesDataInfoReactive = shallowRef({ const entitiesDataInfoList = shallowRef([]);
chNum: undefined,
entityList: [],
domains: [],
startTime: "",
rawStartTime: "",
orgName: ""
});
// 趋势图 // 趋势图
const trendOption = ref({}); const trendOption = ref({});
const trendChecked = ref(false);
// 发布频度 // 发布频度
const tableData1 = ref([]); const tableData1 = ref([]);
// 历次制裁过程 // 历次制裁过程
const sanctionProcessList = ref([]); const sanctionProcessList = ref([]);
const sanctionPage = ref(1);
// 制裁实体清单 // 制裁实体清单
const entitiesList = ref([]); const entitiesList = ref([]);
onMounted(async () => { onMounted(async () => {
try { try {
const [dataCount, entitiesDataInfo, industryCountByYear, countDomainByYear, sanctionsInfoCount, entityBody] = const [dataCount, entitiesDataInfo, industryCountByYear, countDomainByYear] = await Promise.all([
await Promise.all([ getEntitiesDataCount(),
getEntitiesDataCount(), getEntitiesDataInfo(),
getEntitiesDataInfo(), getIndustryCountByYear(1),
getIndustryCountByYear(), getCountDomainByYear(trendChecked.value)
getCountDomainByYear() ]);
// getSanctionsInfoCount() infoList.value = dataCount;
// getEntitiesList("实体清单")
]);
infoList.value[0].num = dataCount;
const entityList = _.map(entitiesDataInfo?.sanEntities ?? [], ({ entityNameZh, entityName }) => { const entityList = _.map(entitiesDataInfo?.sanEntities ?? [], ({ entityNameZh, entityName }) => {
return { name: entityNameZh, enName: entityName }; return { name: entityNameZh, enName: entityName };
}); });
entitiesDataInfoReactive.value = { entitiesDataInfoList.value = entitiesDataInfo || [];
entityList,
chNum: entitiesDataInfo?.cnEntityCount,
domains: entitiesDataInfo?.domains ?? [],
// startTime: formatAnyDateToChinese(entitiesDataInfo?.startTime ?? ""),
startTime: entitiesDataInfo.postDate,
rawStartTime: entitiesDataInfo?.startTime ?? "",
orgName: entitiesDataInfo?.postOrgName
};
const list = _.chain(industryCountByYear).filter("year").orderBy("year", "desc").value(); const list = _.chain(industryCountByYear).filter("year").orderBy("year", "desc").value();
const total = _.sumBy(list, "count"); const total = _.sumBy(list, "count");
tableData1.value = _.map(list, item => { tableData1.value = _.map(list, item => {
...@@ -699,8 +686,12 @@ onMounted(async () => { ...@@ -699,8 +686,12 @@ onMounted(async () => {
tags: item.domain tags: item.domain
}; };
}).slice(0, 5); }).slice(0, 5);
console.log("tableData1", tableData1.value); console.log("countDomainByYear", countDomainByYear);
trendOption.value = getMultipleBarChart_m(countDomainByYear); // 整理柱状图数据并应用到趋势图
if (countDomainByYear && countDomainByYear[0].yearDomainCount) {
trendOption.value = processYearDomainCountData(countDomainByYear[0].yearDomainCount);
}
// trendOption.value = getMultipleBarChart_m(countDomainByYear);
// sanctionProcessList.value = _.map(_.slice(sanctionsInfoCount, 0, 5), item => { // sanctionProcessList.value = _.map(_.slice(sanctionsInfoCount, 0, 5), item => {
// return { // return {
// title: item.tittle, // title: item.tittle,
...@@ -711,9 +702,9 @@ onMounted(async () => { ...@@ -711,9 +702,9 @@ onMounted(async () => {
// }; // };
// }); // });
await fetchEntitiesList(currentPage.value, pageSize.value); await fetchEntitiesList(currentPage.value, pageSize.value);
await fetchSanctionProcess(1, pageSize.value); await fetchSanctionProcess(sanctionPage.value, 10);
// 获取雷达图数据 // 获取雷达图数据
await fetchRadarData(); await fetchRadarData(domainChecked.value);
// console.log("entitiesList entitiesList", entityBody); // console.log("entitiesList entitiesList", entityBody);
// entitiesList.value = _.map(entityBody.content, item => { // entitiesList.value = _.map(entityBody.content, item => {
// return { // return {
...@@ -729,6 +720,59 @@ onMounted(async () => { ...@@ -729,6 +720,59 @@ onMounted(async () => {
console.log(err); console.log(err);
} }
}); });
// 新增函数:处理 yearDomainCount 数据并使用 getMultipleBarChart_m 方法生成图表配置
const processYearDomainCountData = yearDomainCountData => {
// 提取所有年份并排序
const years = [...new Set(yearDomainCountData.map(item => item.year))].sort();
// 提取所有领域名称
const allDomains = [...new Set(yearDomainCountData.flatMap(item => item.domainCountInfo.map(domain => domain.name)))];
// 构造 getMultipleBarChart_m 所需的数据结构
const chartData = {
domains: allDomains,
data: years.map(year => {
const yearData = yearDomainCountData.find(item => item.year === year);
const domainCounts = {};
// 初始化所有领域的计数为0
allDomains.forEach(domain => {
domainCounts[domain] = 0;
});
// 填充实际数据
if (yearData && yearData.domainCountInfo) {
yearData.domainCountInfo.forEach(domain => {
domainCounts[domain.name] = domain.count;
});
}
return {
year: year,
domainNum: domainCounts
};
})
};
// 使用 getMultipleBarChart_m 生成图表配置
return getMultipleBarChart_m(chartData);
};
watch(
() => trendChecked.value,
async checked => {
const res = await getCountDomainByYear(checked);
// if (res && Array.isArray(res) && res.length > 0) {
// trendOption.value = getMultipleBarChart_m(res);
// }
// 整理数据并更新趋势图
if (res && res.yearDomainCount) {
trendOption.value = processYearDomainCountData(res[0].yearDomainCount);
}
}
);
// 返回首页 // 返回首页
const handleBackHome = () => { const handleBackHome = () => {
router.push({ router.push({
...@@ -747,7 +791,7 @@ const handleToDetail = () => { ...@@ -747,7 +791,7 @@ const handleToDetail = () => {
const route = router.resolve({ const route = router.resolve({
path: "/exportControlAnalysis", path: "/exportControlAnalysis",
query: { query: {
startTime: entitiesDataInfoReactive.value.startTime startTime: entitiesDataInfoList.value.startTime
} }
}); });
window.open(route.href, "_blank"); window.open(route.href, "_blank");
...@@ -757,39 +801,8 @@ const billList = ref([]); ...@@ -757,39 +801,8 @@ const billList = ref([]);
const curBillListIndex = ref(0); const curBillListIndex = ref(0);
const searchKey = ref(""); const searchKey = ref("");
const infoListColor = ref(["rgba(206, 79, 81, 1)", "rgba(132, 136, 142, 1)", "rgba(132, 136, 142, 1)", "rgba(132, 136, 142, 1)"]);
const infoList = ref([ const infoList = ref([]);
{
title: "实体清单",
subTitle: "Entity List",
des: "美国商务部工业与安全局依据《出口管理条例》建立的出口管制机制",
num: null,
color: "rgba(206, 79, 81, 1)"
},
{
title: "商业管制清单 ",
subTitle: "CCL",
des: "美国《出口管制条例》中列明受管制军民两用物项的清单",
num: 253,
// color: "rgba(114, 46, 209, 1)"
color: "rgba(132, 136, 142, 1)"
},
{
title: "关键与新兴技术清单",
subTitle: "CETs",
des: "美国为维护其技术领导地位与国家安全而制定的18项优先发展技术清单",
num: 52,
// color: "rgba(250, 140, 22, 1)"
color: "rgba(132, 136, 142, 1)"
},
{
title: "军事最终用户清单 ",
subTitle: "MEU",
des: "美国商务部制定的限制特定外国实体获取可能用于军事用途的美国技术的清单",
num: 0,
color: "rgba(132, 136, 142, 1)"
}
]);
const entityList = ref([ const entityList = ref([
{ {
...@@ -865,15 +878,20 @@ const customNewsData = ref([ ...@@ -865,15 +878,20 @@ const customNewsData = ref([
]); ]);
// 雷达图 // 雷达图
const domainChecked = ref(false);
const radarOption = ref({ const radarOption = ref({
title: { title: {
text: "" text: ""
}, },
legend: { legend: {
top: 0, top: "0%",
icon: "circle", icon: "circle",
data: ["实体清单", "商业管制清单", "关键和新型技术清单"] data: ["实体清单", "商业管制清单", "关键和新型技术清单"]
}, },
grid: {
top: "15%",
containLabel: true
},
radar: { radar: {
// shape: 'circle', // shape: 'circle',
indicator: [ indicator: [
...@@ -923,9 +941,9 @@ const radarOption = ref({ ...@@ -923,9 +941,9 @@ const radarOption = ref({
}); });
// 获取雷达图数据 // 获取雷达图数据
const fetchRadarData = async () => { const fetchRadarData = async checked => {
try { try {
const data = await getSanDomainCount(); const data = await getSanDomainCount(checked);
if (data && Array.isArray(data) && data.length > 0) { if (data && Array.isArray(data) && data.length > 0) {
// 收集所有可能的领域名称 // 收集所有可能的领域名称
const allDomains = new Set(); const allDomains = new Set();
...@@ -984,6 +1002,11 @@ const fetchRadarData = async () => { ...@@ -984,6 +1002,11 @@ const fetchRadarData = async () => {
} }
}; };
watch(
() => domainChecked.value,
() => fetchRadarData(domainChecked.value)
);
// 进度条状态 // 进度条状态
const getStatus = _percent => { const getStatus = _percent => {
const percent = _percent * 100; const percent = _percent * 100;
...@@ -1020,12 +1043,37 @@ const fetchEntitiesList = async (page = 1, size = 10) => { ...@@ -1020,12 +1043,37 @@ const fetchEntitiesList = async (page = 1, size = 10) => {
} }
}; };
const handleGetMore = async () => {
sanctionPage.value++;
try {
const res = await getSanctionProcess("实体清单", sanctionPage.value, 10);
if (res && res.content) {
// 将新数据合并到现有列表中
const newData = res.content.map(item => ({
...item,
title: item.name,
desc: `${item.cnEntityCount} 家中国实体`,
content:
item.summary ||
"2025年3月25日,美国商务部工业与安全局以从事有悖于美国国家安全和外交政策利益的活动为由,宣布将来自中国的54家实体新增至“实体清单”。"
}));
// 合并新数据到现有列表
sanctionProcessList.value = [...sanctionProcessList.value, ...newData];
}
} catch (err) {
console.error(err);
// 如果请求失败,回退页码
sanctionPage.value--;
}
};
// 获取历次制裁过程数据 // 获取历次制裁过程数据
const fetchSanctionProcess = async (page = 1, size = 10) => { const fetchSanctionProcess = async (page = 1, size = 10) => {
try { try {
const res = await getSanctionProcess("实体清单", page, size); const res = await getSanctionProcess("实体清单", page, size);
if (res) { if (res) {
sanctionProcessList.value = res.content.slice(0, 5).map(item => ({ sanctionProcessList.value = res.content.map(item => ({
...item, ...item,
title: item.name, title: item.name,
desc: `${item.cnEntityCount} 家中国实体`, desc: `${item.cnEntityCount} 家中国实体`,
...@@ -1416,6 +1464,17 @@ const chart1Data = ref({ ...@@ -1416,6 +1464,17 @@ const chart1Data = ref({
] ]
}); });
const handleSanc = item => {
console.log(item);
const route = router.resolve({
path: "/exportControlAnalysis",
query: {
startTime: item.postDate
}
});
window.open(route.href, "_blank");
};
// 获取热门法案 // 获取热门法案
const handleGetHotBills = async () => { const handleGetHotBills = async () => {
try { try {
...@@ -1742,8 +1801,9 @@ onMounted(async () => { ...@@ -1742,8 +1801,9 @@ onMounted(async () => {
.box3 { .box3 {
display: flex; display: flex;
justify-content: space-between; // justify-content: space-between;
align-items: flex-start; align-items: flex-start;
gap: 60px;
.box3-content-title { .box3-content-title {
font-size: 18px; font-size: 18px;
font-weight: 700; font-weight: 700;
...@@ -1769,7 +1829,10 @@ onMounted(async () => { ...@@ -1769,7 +1829,10 @@ onMounted(async () => {
overflow: auto; overflow: auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between;
padding-top: 16px; padding-top: 16px;
// padding-bottom: 50px;
position: relative;
.box4-item { .box4-item {
display: flex; display: flex;
gap: 10px; gap: 10px;
...@@ -1802,6 +1865,7 @@ onMounted(async () => { ...@@ -1802,6 +1865,7 @@ onMounted(async () => {
position: relative; position: relative;
top: -7.5px; top: -7.5px;
padding-bottom: 8px; padding-bottom: 8px;
cursor: pointer;
&-title { &-title {
font-size: 18px; font-size: 18px;
color: $base-color; color: $base-color;
...@@ -1827,14 +1891,15 @@ onMounted(async () => { ...@@ -1827,14 +1891,15 @@ onMounted(async () => {
} }
} }
.box4-footer { .box4-footer {
margin-top: auto; position: absolute;
// margin-top: auto;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
bottom: 30px; bottom: 30px;
left: 50%; left: 50%;
margin-left: -20px; margin-left: -30px;
margin-bottom: 30px; // margin-bottom: 30px;
} }
} }
...@@ -2550,6 +2615,7 @@ onMounted(async () => { ...@@ -2550,6 +2615,7 @@ onMounted(async () => {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
cursor: pointer;
.box1-bottom-content-item-imgUndefined { .box1-bottom-content-item-imgUndefined {
width: 24px; width: 24px;
height: 24px; height: 24px;
......
import * as echarts from 'echarts'; import * as echarts from "echarts";
import chinaJson from './China.json' import chinaJson from "./China.json";
import _ from 'lodash'; import _ from "lodash";
//饼图 //饼图
export function getPieOption(data, title) { export function getPieOption(data, title) {
let option = { let option = {
color: ["#4096ff", "#b37feb", "#ff7875", "#85a5ff", "#69b1ff", "#ffc069", "#87e8de"], color: ["#4096ff", "#b37feb", "#ff7875", "#85a5ff", "#69b1ff", "#ffc069", "#87e8de"],
title: { title: {
text: title, text: title,
top: 10, top: 10,
left: 10, left: 10,
textStyle: { textStyle: {
color: "rgba(59, 65, 75, 1)", color: "rgba(59, 65, 75, 1)",
fontSize: 16, fontSize: 16,
fontWeight: 700 fontWeight: 700
} }
}, },
series: [ series: [
{ {
type: 'pie', type: "pie",
radius: [75, 132], radius: [75, 132],
height: '100%', height: "100%",
center: ['50%', '50%'], center: ["50%", "50%"],
width: '100%', width: "100%",
itemStyle: { itemStyle: {
borderColor: '#fff', borderColor: "#fff",
borderWidth: 1 borderWidth: 1
}, },
label: { label: {
alignTo: 'edge', alignTo: "edge",
formatter: '{b} {d}%', formatter: "{b} {d}%",
minMargin: 5, minMargin: 5,
edgeDistance: 10, edgeDistance: 10,
lineHeight: 15, lineHeight: 15,
rich: { rich: {
time: { time: {
fontSize: 10, fontSize: 10,
color: '#999' color: "#999"
} }
} }
}, },
labelLine: { labelLine: {
length: 15, length: 15,
length2: 0, length2: 0,
maxSurfaceAngle: 80 maxSurfaceAngle: 80
}, },
labelLayout: function (params) { labelLayout: function (params) {
const isLeft = params.labelRect.x < 556 / 2; const isLeft = params.labelRect.x < 556 / 2;
const points = params.labelLinePoints; const points = params.labelLinePoints;
// Update the end point. // Update the end point.
points[2][0] = isLeft points[2][0] = isLeft ? params.labelRect.x : params.labelRect.x + params.labelRect.width;
? params.labelRect.x return {
: params.labelRect.x + params.labelRect.width; labelLinePoints: points
return { };
labelLinePoints: points },
}; data: data
}, }
data: data ]
}] };
} return option;
return option
} }
export function getPieOption1(data, title) { export function getPieOption1(data, title) {
let option = { let option = {
color: ["#4096ff", "#b37feb", "#ff7875", "#85a5ff", "#69b1ff", "#ffc069", "#87e8de"], color: ["#4096ff", "#b37feb", "#ff7875", "#85a5ff", "#69b1ff", "#ffc069", "#87e8de"],
title: { title: {
text: title, text: title,
top: 10, top: 10,
left: 10, left: 10,
textStyle: { textStyle: {
color: "rgba(59, 65, 75, 1)", color: "rgba(59, 65, 75, 1)",
fontSize: 16, fontSize: 16,
fontWeight: 700 fontWeight: 700
} }
}, },
series: [ series: [
{ {
type: 'pie', type: "pie",
radius: [70, 110], radius: [70, 110],
height: '100%', height: "100%",
center: ['50%', '50%'], center: ["50%", "50%"],
width: '100%', width: "100%",
itemStyle: { itemStyle: {
borderColor: '#fff', borderColor: "#fff",
borderWidth: 1 borderWidth: 1
}, },
label: { label: {
alignTo: 'edge', alignTo: "edge",
formatter: `{b}${data.length < 10 ? '\n' : ''} {two|{c}家 {d}%}`, formatter: `{b}${data.length < 10 ? "\n" : ""} {two|{c}家 {d}%}`,
fontSize: 17.6, fontSize: 17.6,
fontWeight: 700, fontWeight: 700,
minMargin: 5, minMargin: 5,
edgeDistance: 10, edgeDistance: 10,
lineHeight: 23, lineHeight: 23,
rich: { rich: {
two: { two: {
fontSize: 15, fontSize: 15,
color: ' rgba(95, 101, 108, 1)', color: " rgba(95, 101, 108, 1)"
} }
} }
}, },
labelLine: { labelLine: {
length: 15, length: 15,
length2: 0, length2: 0,
maxSurfaceAngle: 80 maxSurfaceAngle: 80
}, },
labelLayout: function (params) { labelLayout: function (params) {
console.log('labelLayoutparams', params) console.log("labelLayoutparams", params);
const isLeft = params.labelRect.x < 556 / 2; const isLeft = params.labelRect.x < 556 / 2;
const points = params.labelLinePoints; const points = params.labelLinePoints;
// Update the end point. // Update the end point.
points[2][0] = isLeft points[2][0] = isLeft
? data.length < 10 ? params.labelRect.x : (params.labelRect.x + params.labelRect.width) ? data.length < 10
: data.length < 10 ? (params.labelRect.x + params.labelRect.width) : params.labelRect.x; ? params.labelRect.x
return { : params.labelRect.x + params.labelRect.width
labelLinePoints: points : data.length < 10
}; ? params.labelRect.x + params.labelRect.width
}, : params.labelRect.x;
data: data return {
}] labelLinePoints: points
} };
return option },
data: data
}
]
};
return option;
} }
export function getPieOption2(data, title) { export function getPieOption2(data, title) {
let option = { let option = {
color: ["#4096ff", "#b37feb", "#ff7875", "#85a5ff", "#69b1ff", "#ffc069", "#87e8de"], color: ["#4096ff", "#b37feb", "#ff7875", "#85a5ff", "#69b1ff", "#ffc069", "#87e8de"],
title: { title: {
text: title, text: title,
top: 10, top: 10,
left: 10, left: 10,
textStyle: { textStyle: {
color: "rgba(59, 65, 75, 1)", color: "rgba(59, 65, 75, 1)",
fontSize: 16, fontSize: 16,
fontWeight: 700 fontWeight: 700
} }
}, },
legend: { legend: {
icon: 'rect', icon: "rect",
top: 'center', top: "center",
right: '40', right: "40",
orient: 'vertical', orient: "vertical",
itemWidth: 30, itemWidth: 30,
itemHeight: 20, itemHeight: 20,
borderRadius: 2, borderRadius: 2,
formatter: function (name) { formatter: function (name) {
// 获取系列数据,假设第一个系列为饼图 // 获取系列数据,假设第一个系列为饼图
let seriesData = option.series[0].data; // [citation:8] let seriesData = option.series[0].data; // [citation:8]
// 也可以考虑使用 this.getSeries()[0].data [citation:8] // 也可以考虑使用 this.getSeries()[0].data [citation:8]
let currentValue; let currentValue;
// 计算数据总和并查找当前图例名对应的数值 // 计算数据总和并查找当前图例名对应的数值
seriesData.forEach(item => { seriesData.forEach(item => {
if (item.name === name) { if (item.name === name) {
currentValue = item.value; currentValue = item.value;
} }
}); });
return ` ${name} ${currentValue} %` return ` ${name} ${currentValue} %`;
}, },
itemGap: 10, itemGap: 10,
textStyle: { textStyle: {
color: 'rgba(59, 65, 75 ,0.8)', color: "rgba(59, 65, 75 ,0.8)",
fontSize: 18, fontSize: 18,
fontWeight: 500 fontWeight: 500
}, }
}, },
series: [ series: [
{ {
type: 'pie', type: "pie",
radius: [70, 120], radius: [70, 120],
height: '100%', height: "100%",
center: ['33%', '50%'], center: ["33%", "50%"],
width: '100%', width: "100%",
itemStyle: { itemStyle: {
borderColor: '#fff', borderColor: "#fff",
borderWidth: 1 borderWidth: 1
}, },
emphasis: { emphasis: {
scale: false, scale: false
}, },
label: { label: {
show: false, show: false
}, },
data: data data: data
}] }
} ]
return option };
return option;
} }
export function getMapOption() { export function getMapOption() {
echarts.registerMap('china', chinaJson); echarts.registerMap("china", chinaJson);
let data = [{ let data = [
name: '2256', {
value: 2256 name: "2256",
}, { value: 2256
name: '578', },
value: 578 {
}, { name: "578",
name: '744', value: 578
value: 744 },
}, { {
name: '806', name: "744",
value: 806 value: 744
}, { },
name: '336', {
value: 336 name: "806",
}, { value: 806
name: '325', },
value: 325 {
}, { name: "336",
name: '487', value: 336
value: 487 },
}, { {
name: '343', name: "325",
value: 343 value: 325
}, { },
name: '432', {
value: 432 name: "487",
}, { value: 487
name: '273', },
value: 273 {
}, { name: "343",
name: '1055', value: 343
value: 1055 },
}, { {
name: '590', name: "432",
value: 590 value: 432
}, { },
name: '319', {
value: 319 name: "273",
}, { value: 273
name: '349', },
value: 349 {
}, { name: "1055",
name: '126', value: 1055
value: 126 },
}, { {
name: '97', name: "590",
value: 97 value: 590
}, { },
name: '201', {
value: 201 name: "319",
}, { value: 319
name: '398', },
value: 398 {
}, { name: "349",
name: '795', value: 349
value: 795 },
}, { {
name: '655', name: "126",
value: 655 value: 126
}, { },
name: '295', {
value: 295 name: "97",
}, { value: 97
name: '311', },
value: 311 {
}, { name: "201",
name: '993', value: 201
value: 993 },
}, { {
name: '601', name: "398",
value: 601 value: 398
}, { },
name: '275', {
value: 275 name: "795",
}, { value: 795
name: '317', },
value: 317 {
}, { name: "655",
name: '1000', value: 655
value: 1000 },
}, { {
name: '186', name: "295",
value: 186 value: 295
}, { },
name: '261', {
value: 261 name: "311",
}, { value: 311
name: '132', },
value: 132 {
}, { name: "993",
name: '18', value: 993
value: 18 },
}, { {
name: '11', name: "601",
value: 11 value: 601
},]; },
{
name: "275",
value: 275
},
{
name: "317",
value: 317
},
{
name: "1000",
value: 1000
},
{
name: "186",
value: 186
},
{
name: "261",
value: 261
},
{
name: "132",
value: 132
},
{
name: "18",
value: 18
},
{
name: "11",
value: 11
}
];
let geoCoordMap = { let geoCoordMap = {
'2256': [116.46, 39.92], 2256: [116.46, 39.92],
'578': [121.29, 31.14], 578: [121.29, 31.14],
'744': [117.2, 39.13], 744: [117.2, 39.13],
'806': [106.32, 29.32], 806: [106.32, 29.32],
'336': [126.41, 45.45], 336: [126.41, 45.45],
'325': [125.19, 43.52], 325: [125.19, 43.52],
'487': [123.24, 41.50], 487: [123.24, 41.5],
'343': [111.48, 40.49], 343: [111.48, 40.49],
'432': [114.28, 38.02], 432: [114.28, 38.02],
'273': [112.34, 37.52], 273: [112.34, 37.52],
'1055': [117, 36.38], 1055: [117, 36.38],
'590': [113.42, 34.48], 590: [113.42, 34.48],
'319': [108.54, 34.16], 319: [108.54, 34.16],
'349': [103.49, 36.03], 349: [103.49, 36.03],
'126': [106.16, 38.20], 126: [106.16, 38.2],
'97': [101.45, 36.38], 97: [101.45, 36.38],
'201': [87.36, 43.48], 201: [87.36, 43.48],
'398': [117.18, 31.51], 398: [117.18, 31.51],
'795': [118.50, 32.02], 795: [118.5, 32.02],
'655': [120.09, 30.14], 655: [120.09, 30.14],
'295': [113, 28.11], 295: [113, 28.11],
'311': [115.52, 28.41], 311: [115.52, 28.41],
'993': [114.21, 30.37], 993: [114.21, 30.37],
'601': [104.05, 30.39], 601: [104.05, 30.39],
'275': [106.42, 26.35], 275: [106.42, 26.35],
'317': [119.18, 26.05], 317: [119.18, 26.05],
'1000': [113.15, 23.08], 1000: [113.15, 23.08],
'186': [110.20, 20.02], 186: [110.2, 20.02],
'261': [108.20, 22.48], 261: [108.2, 22.48],
'132': [102.41, 25], 132: [102.41, 25],
'18': [91.10, 29.40], 18: [91.1, 29.4],
'11': [114.10, 22.18], 11: [114.1, 22.18]
}; };
function convertData(data) { function convertData(data) {
var res = []; var res = [];
for (var i = 0; i < data.length; i++) { for (var i = 0; i < data.length; i++) {
var geoCoord = geoCoordMap[data[i].name]; var geoCoord = geoCoordMap[data[i].name];
if (geoCoord) { if (geoCoord) {
res.push({ res.push({
name: data[i].name, name: data[i].name,
value: geoCoord.concat(data[i].value) value: geoCoord.concat(data[i].value)
}); });
} }
} }
console.log(res) console.log(res);
return res; return res;
} }
let option = { let option = {
tooltip: { tooltip: {
show: false show: false
}, },
geo: { geo: {
map: 'china', map: "china",
roam: true, roam: true,
center: [104.95, 35.27], center: [104.95, 35.27],
// 地图尺寸为容器宽高较小值的80% // 地图尺寸为容器宽高较小值的80%
zoom: 1.7, zoom: 1.7,
// scaleLimit:{ // scaleLimit:{
// max:'1.2', // max:'1.2',
// min:'0.7' // min:'0.7'
// }, // },
label: { label: {
normal: { normal: {
show: false, show: false,
textStyle: { textStyle: {
color: 'rgba(0,0,0,0.6)' color: "rgba(0,0,0,0.6)"
} }
} }
}, },
itemStyle: { itemStyle: {
areaColor: 'rgba(231, 243, 255, 1)', // 设置所有区域的默认填充色[citation:8] areaColor: "rgba(231, 243, 255, 1)", // 设置所有区域的默认填充色[citation:8]
borderColor: 'rgb(5, 95, 194)', // 设置边界线颜色[citation:8] borderColor: "rgb(5, 95, 194)", // 设置边界线颜色[citation:8]
borderWidth: 1 // 设置边界线宽度[citation:8] borderWidth: 1 // 设置边界线宽度[citation:8]
}, }
}, },
// backgroundColor: 'rgba(0,51,102, 1)', // backgroundColor: 'rgba(0,51,102, 1)',
series: [{ series: [
type: 'scatter', {
coordinateSystem: 'geo', type: "scatter",
data: convertData(data), coordinateSystem: "geo",
symbolSize: 10, data: convertData(data),
symbolSize: 10,
symbolRotate: 0,
symbolOffset: ['50%', '-100%'],
tooltip: {
show: true
},
label: {
normal: {
formatter: '{a}',
position: 'top',
show: false,
textStyle: {
color: '#000000',
fontSize: 16
}
},
emphasis: {
show: false
}
},
itemStyle: {
normal: {
borderWidth: 3,
borderColor: 'rgba(255, 163, 158, 1)',
color: 'rgba(255, 77, 79, 1)'
}
}
}]
};
return option
symbolRotate: 0,
symbolOffset: ["50%", "-100%"],
tooltip: {
show: true
},
label: {
normal: {
formatter: "{a}",
position: "top",
show: false,
textStyle: {
color: "#000000",
fontSize: 16
}
},
emphasis: {
show: false
}
},
itemStyle: {
normal: {
borderWidth: 3,
borderColor: "rgba(255, 163, 158, 1)",
color: "rgba(255, 77, 79, 1)"
}
}
}
]
};
return option;
} }
export const getBarChart = (nameList, valueList, color = ['rgba(255, 159, 22, 1)', 'rgba(255, 159, 22, 0)'], name) => { export const getBarChart = (nameList, valueList, color = ["rgba(255, 159, 22, 1)", "rgba(255, 159, 22, 0)"], name) => {
const option = { const option = {
tooltip: { tooltip: {
trigger: "axis", trigger: "axis",
formatter: function (params) { formatter: function (params) {
let result = params[0].name + '<br/>'; let result = params[0].name + "<br/>";
params.forEach(function (item, index) { params.forEach(function (item, index) {
// 自定义颜色数组 // 自定义颜色数组
const customColors = [color[0]]; const customColors = [color[0]];
const dotColor = customColors[index % customColors.length]; // 循环取色 const dotColor = customColors[index % customColors.length]; // 循环取色
// 创建彩色圆点图标 // 创建彩色圆点图标
const dot = `<span style="display:inline-block;margin-right:5px;border-radius:50%;width:10px;height:10px;background-color:${dotColor};"></span>`; const dot = `<span style="display:inline-block;margin-right:5px;border-radius:50%;width:10px;height:10px;background-color:${dotColor};"></span>`;
result += dot + `${item.seriesName}: ${item.value}<br/>`; result += dot + `${item.seriesName}: ${item.value}<br/>`;
}); });
return result; return result;
} }
}, },
grid: { grid: {
top: '3%', top: "3%",
right: '3%', right: "3%",
bottom: '1%', bottom: "1%",
left: '1%', left: "1%",
containLabel: true containLabel: true
}, },
xAxis: { xAxis: {
axisLine: { axisLine: {
lineStyle: { lineStyle: {
width: 1, width: 1,
color: "rgba(231, 243, 255, 1)" color: "rgba(231, 243, 255, 1)"
} }
}, },
axisTick: axisTick: { show: false },
{ show: false }, type: "category",
type: "category", boundaryGap: [100, 100],
boundaryGap: [100, 100], axisLabel: {
axisLabel: { color: "rgba(95, 101, 108, 1)"
color: "rgba(95, 101, 108, 1)", // fontSize: 22,
// fontSize: 22, // fontWeight: 400
// fontWeight: 400 },
}, data: nameList
data: nameList },
}, yAxis: {
yAxis: { type: "value",
type: "value", axisLine: {
axisLine: { lineStyle: {
lineStyle: { type: "dashed"
type: "dashed" }
} },
},
axisLabel: { axisLabel: {
color: "rgba(95, 101, 108, 1)", color: "rgba(95, 101, 108, 1)"
// fontSize: 22, // fontSize: 22,
// fontWeight: 400 // fontWeight: 400
}, },
splitNumber: 8, splitNumber: 8,
splitLine: { splitLine: {
lineStyle: { lineStyle: {
width: 1, width: 1,
type: "dashed", type: "dashed",
color: "rgba(231, 243, 255, 1)" color: "rgba(231, 243, 255, 1)"
}, }
}
},
series: [
{
name: name,
type: "bar",
data: valueList,
} barWidth: 12,
}, itemStyle: {
series: [{ color: function (params) {
name: name, return new echarts.graphic.LinearGradient(0, 1, 0, 0, [
type: 'bar', {
data: valueList, offset: 0,
color: color[1]
barWidth: 12, },
itemStyle: { {
color: function (params) { offset: 1,
return new echarts.graphic.LinearGradient(0, 1, 0, 0, color: color[0]
[{ }
offset: 0, ]);
color: color[1] },
}, barBorderRadius: 10
{ }
offset: 1, }
color: color[0] ]
} };
]); return option;
}, };
barBorderRadius: 10,
}
}]
}
return option
}
export const getLineChart = (object, isPercent) => { export const getLineChart = (object, isPercent) => {
const option = { const option = {
title: { title: {
text: "" text: ""
}, },
tooltip: { tooltip: {
trigger: "axis", trigger: "axis",
formatter: function (params) { formatter: function (params) {
let result = params[0].name + '<br/>'; let result = params[0].name + "<br/>";
params.forEach(function (item, index) { params.forEach(function (item, index) {
// 自定义颜色数组 // 自定义颜色数组
const customColors = [object.color]; const customColors = [object.color];
const dotColor = customColors[index % customColors.length]; // 循环取色 const dotColor = customColors[index % customColors.length]; // 循环取色
// 创建彩色圆点图标 // 创建彩色圆点图标
const dot = `<span style="display:inline-block;margin-right:5px;border-radius:50%;width:10px;height:10px;background-color:${dotColor};"></span>`; const dot = `<span style="display:inline-block;margin-right:5px;border-radius:50%;width:10px;height:10px;background-color:${dotColor};"></span>`;
result += dot + `${item.seriesName}: ${item.value}${isPercent ? '%' : ''}<br/>`; result += dot + `${item.seriesName}: ${item.value}${isPercent ? "%" : ""}<br/>`;
}); });
return result; return result;
} }
}, },
grid: { grid: {
top: '3%', top: "3%",
right: '3%', right: "3%",
bottom: '1%', bottom: "1%",
left: '1%', left: "1%",
containLabel: true containLabel: true
}, },
// toolbox: { // toolbox: {
// feature: { // feature: {
// saveAsImage: {} // saveAsImage: {}
// } // }
// }, // },
xAxis: { xAxis: {
axisLine: { axisLine: {
lineStyle: { lineStyle: {
width: 1, width: 1,
color: "rgba(231, 243, 255, 1)" color: "rgba(231, 243, 255, 1)"
} }
}, },
axisTick: axisTick: { show: false },
{ show: false }, type: "category",
type: "category", boundaryGap: [100, 100],
boundaryGap: [100, 100], axisLabel: {
axisLabel: { color: "rgba(95, 101, 108, 1)"
color: "rgba(95, 101, 108, 1)", // fontSize: 22,
// fontSize: 22, // fontWeight: 400
// fontWeight: 400 },
}, data: object.xAxisData
data: object.xAxisData },
}, yAxis: {
yAxis: { type: "value",
type: "value", axisLine: {
axisLine: { lineStyle: {
lineStyle: { type: "dashed"
type: "dashed" }
} },
},
axisLabel: { axisLabel: {
color: "rgba(95, 101, 108, 1)", color: "rgba(95, 101, 108, 1)",
// fontSize: 22, // fontSize: 22,
// fontWeight: 400 // fontWeight: 400
formatter: `{value} ${isPercent ? '%' : ''}` formatter: `{value} ${isPercent ? "%" : ""}`
}, },
splitNumber: 8, splitNumber: 8,
splitLine: { splitLine: {
lineStyle: { lineStyle: {
width: 1, width: 1,
type: "dashed", type: "dashed",
color: "rgba(231, 243, 255, 1)" color: "rgba(231, 243, 255, 1)"
}, }
}
} },
}, series: [
series: [ {
{ name: object.name,
name: object.name, type: "line",
type: "line", symbolSize: 8,
symbolSize: 8, symbol: "circle",
symbol: 'circle', itemStyle: {
itemStyle: { color: "#ffffff",
color: "#ffffff", borderColor: object.color,
borderColor: object.color, borderWidth: 3
borderWidth: 3 },
}, lineStyle: {
lineStyle: { color: object.color
color: object.color, },
}, data: object.seriesData
data: object.seriesData }
} ]
] };
}; return option;
return option; };
}
export const getHorizontalBarChart1 = (nameList, valueList, isPer) => { export const getHorizontalBarChart1 = (nameList, valueList, isPer) => {
const colorList = ['#ce4f51', '#1778ff'] const colorList = ["#ce4f51", "#1778ff"];
const option = { const option = {
tooltip: {}, tooltip: {},
grid: { grid: {
top: '3%', top: "3%",
right: '3%', right: "3%",
bottom: '1%', bottom: "1%",
left: '1%', left: "1%",
containLabel: true containLabel: true
}, },
color: ['#ce4f51', '#1778ff'], color: ["#ce4f51", "#1778ff"],
xAxis: { xAxis: {
type: 'value', type: "value",
splitLine: { splitLine: {
show: false show: false
}, },
show: false show: false
}, },
yAxis: { yAxis: {
type: 'category', type: "category",
data: nameList, data: nameList,
splitLine: { splitLine: {
show: false show: false
}, },
axisTick: { axisTick: {
show: false show: false
}, },
axisLine: { axisLine: {
show: false show: false
}, },
axisLabel: { axisLabel: {
show: true show: true
} }
}, },
series: [{ series: [
type: 'bar', {
data: valueList.map((item, index) => { type: "bar",
return { data: valueList.map((item, index) => {
value: item, return {
label: { value: item,
textStyle: { label: {
color: index < 4 ? '#1778ff' : '#ce4f51' textStyle: {
} color: index < 4 ? "#1778ff" : "#ce4f51"
} }
}; }
}), };
label: { }),
show: true, label: {
position: [450, -2], show: true,
formatter: function (params) { position: [450, -2],
return isPer ? params.value + '%' : params.value formatter: function (params) {
} return isPer ? params.value + "%" : params.value;
}, }
barWidth: 8, },
itemStyle: { barWidth: 8,
color: function (params) { itemStyle: {
if (params.dataIndex < 4) { color: function (params) {
return new echarts.graphic.LinearGradient(0, 0, 1, 0, if (params.dataIndex < 4) {
[{ return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
offset: 0, {
color: 'rgba(22, 119, 255, 0)' offset: 0,
}, color: "rgba(22, 119, 255, 0)"
{ },
offset: 1, {
color: colorList[1] offset: 1,
} color: colorList[1]
]); }
} else { ]);
return new echarts.graphic.LinearGradient(0, 0, 1, 0, } else {
[{ return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
offset: 0, {
color: 'rgba(206, 79, 81, 0)' offset: 0,
}, color: "rgba(206, 79, 81, 0)"
{ },
offset: 1, {
color: colorList[0] offset: 1,
} color: colorList[0]
]); }
} ]);
}
}, },
barBorderRadius: 4, barBorderRadius: 4
} }
}] }
} ]
return option };
} return option;
};
export const getHorizontalBarChart2 = (nameList, valueList, isPer) => { export const getHorizontalBarChart2 = (nameList, valueList, isPer) => {
const colorList = [ const colorList = [
['rgba(64, 150, 255, 1)', 'rgba(64, 150, 255, 0)'], ["rgba(64, 150, 255, 1)", "rgba(64, 150, 255, 0)"],
['rgba(255, 120, 117, 1)', 'rgba(255, 120, 117, 0)'], ["rgba(255, 120, 117, 1)", "rgba(255, 120, 117, 0)"],
['rgba(89, 126, 247, 1)', 'rgba(89, 126, 247, 0)'], ["rgba(89, 126, 247, 1)", "rgba(89, 126, 247, 0)"],
['rgba(54, 207, 201, 1)', 'rgba(54, 207, 201, 0)'], ["rgba(54, 207, 201, 1)", "rgba(54, 207, 201, 0)"],
['rgba(255, 197, 61, 1)', 'rgba(255, 197, 61, 0)'], ["rgba(255, 197, 61, 1)", "rgba(255, 197, 61, 0)"],
['rgba(179, 127, 235, 1)', 'rgba(179, 127, 235, 0)'] ["rgba(179, 127, 235, 1)", "rgba(179, 127, 235, 0)"]
] ];
console.log(colorList) console.log(colorList);
const option = { const option = {
tooltip: {}, tooltip: {},
grid: { grid: {
top: '6%', top: "6%",
right: '6%', right: "6%",
bottom: '0', bottom: "0",
left: '1%', left: "1%",
containLabel: true containLabel: true
}, },
xAxis: { xAxis: {
type: 'value', type: "value",
splitLine: { splitLine: {
show: false show: false
}, },
show: false show: false
}, },
yAxis: { yAxis: {
type: 'category', type: "category",
data: nameList, data: nameList,
splitLine: { splitLine: {
show: false show: false
}, },
axisTick: { axisTick: {
show: false show: false
}, },
axisLine: { axisLine: {
show: false show: false
}, },
axisLabel: { axisLabel: {
show: true show: true
} }
}, },
series: [{ series: [
type: 'bar', {
data: valueList.map((item, index) => { type: "bar",
return { data: valueList.map((item, index) => {
value: item, return {
label: { value: item,
textStyle: { label: {
color: colorList[index][0] textStyle: {
} color: colorList[index % 6][0]
} }
}; }
}), };
label: { }),
show: true, label: {
position: [340, -2], show: true,
formatter: function (params) { position: [340, -2],
return isPer ? params.value + '%' : params.value formatter: function (params) {
} return isPer ? params.value + "%" : params.value;
}, }
barWidth: 8, },
itemStyle: { barWidth: 8,
color: function (params) { itemStyle: {
console.log('params', params) color: function (params) {
return new echarts.graphic.LinearGradient(0, 0, 1, 0, console.log("params", params);
[{ return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
offset: 0, {
color: colorList[params.dataIndex][1] offset: 0,
}, color: colorList[params.dataIndex % 6][1]
{ },
offset: 1, {
color: colorList[params.dataIndex][0] offset: 1,
} color: colorList[params.dataIndex % 6][0]
]); }
]);
},
barBorderRadius: 4
}
}
]
};
return option;
};
export const getMultipleLineChart = obj => {
const color = ["rgba(19, 168, 168, 1)", "rgba(146, 84, 222, 1)", "rgba(250, 140, 22, 1)", "rgba(206, 79, 81, 1)"];
const option = {
title: {
text: ""
},
tooltip: {
trigger: "axis",
formatter: function (params) {
let result = params[0].name + "<br/>";
params.forEach(function (item, index) {
// 自定义颜色数组
const customColors = color;
const dotColor = customColors[index % customColors.length]; // 循环取色
// 创建彩色圆点图标
const dot = `<span style="display:inline-block;margin-right:5px;border-radius:50%;width:10px;height:10px;background-color:${dotColor};"></span>`;
result += dot + `${item.seriesName}: ${item.value}<br/>`;
});
return result;
}
},
grid: {
top: "12%",
right: "3%",
bottom: "3%",
left: "1%",
containLabel: true
},
legend: {
right: "5%",
icon: "circle",
itemWidth: 15,
textStyle: {
color: "rgba(0, 0, 0, 0.8)",
fontSize: 14,
fontWeight: 400
},
itemGap: 17,
data: obj.data.map((item, index) => {
return { name: item.name, itemStyle: { color: color[index] } };
})
},
xAxis: {
axisLine: {
lineStyle: {
width: 1,
color: "rgba(231, 243, 255, 1)"
}
},
axisTick: { show: false },
type: "category",
boundaryGap: [100, 100],
axisLabel: {
color: "rgba(95, 101, 108, 1)"
// fontSize: 22,
// fontWeight: 400
},
data: obj.dates
},
yAxis: {
type: "value",
axisLine: {
lineStyle: {
type: "dashed"
}
},
}, axisLabel: {
barBorderRadius: 4, color: "rgba(95, 101, 108, 1)"
} // fontSize: 22,
}] // fontWeight: 400
} },
return option splitNumber: 8,
} splitLine: {
export const getMultipleLineChart = (obj) => { lineStyle: {
const color = ['rgba(19, 168, 168, 1)', 'rgba(146, 84, 222, 1)', 'rgba(250, 140, 22, 1)', 'rgba(206, 79, 81, 1)'] width: 1,
const option = { type: "dashed",
title: { color: "rgba(231, 243, 255, 1)"
text: "" }
}, }
tooltip: { },
trigger: "axis", series: obj.data.map((item, index) => {
formatter: function (params) { return {
let result = params[0].name + '<br/>'; name: item.name,
params.forEach(function (item, index) { type: "line",
// 自定义颜色数组 symbolSize: 6,
const customColors = color; symbol: "circle",
const dotColor = customColors[index % customColors.length]; // 循环取色 itemStyle: {
// 创建彩色圆点图标 color: "#ffffff",
const dot = `<span style="display:inline-block;margin-right:5px;border-radius:50%;width:10px;height:10px;background-color:${dotColor};"></span>`; borderColor: color[index],
result += dot + `${item.seriesName}: ${item.value}<br/>`; borderWidth: 2
}); },
return result; lineStyle: {
} width: 1,
}, color: color[index]
grid: { },
top: '12%', data: item.value
right: '3%', };
bottom: '3%', })
left: '1%', };
containLabel: true return option;
}, };
legend: {
right: "5%",
icon: "circle",
itemWidth: 15,
textStyle: {
color: "rgba(0, 0, 0, 0.8)",
fontSize: 14,
fontWeight: 400
},
itemGap: 17,
data: obj.data.map((item, index) => {
return { name: item.name, itemStyle: { color: color[index] } }
})
},
xAxis: {
axisLine: {
lineStyle: {
width: 1,
color: "rgba(231, 243, 255, 1)"
}
},
axisTick:
{ show: false },
type: "category",
boundaryGap: [100, 100],
axisLabel: {
color: "rgba(95, 101, 108, 1)",
// fontSize: 22,
// fontWeight: 400
},
data: obj.dates
},
yAxis: {
type: "value",
axisLine: {
lineStyle: {
type: "dashed"
}
},
axisLabel: {
color: "rgba(95, 101, 108, 1)",
// fontSize: 22,
// fontWeight: 400
},
splitNumber: 8,
splitLine: {
lineStyle: {
width: 1,
type: "dashed",
color: "rgba(231, 243, 255, 1)"
},
}
},
series: obj.data.map((item, index) => {
return {
name: item.name,
type: "line",
symbolSize: 6,
symbol: 'circle',
itemStyle: {
color: "#ffffff",
borderColor: color[index],
borderWidth: 2
},
lineStyle: {
width: 1,
color: color[index],
},
data: item.value
}
})
};
return option;
}
//出口管制主页接口 //出口管制主页接口
export const getMultipleBarChart_m = (object) => { export const getMultipleBarChart_m = object => {
const list = _.chain(object.data).filter('year').orderBy('year', 'asc').value(); const list = _.chain(object.data).filter("year").orderBy("year", "asc").value();
const colors = [ const colors = [
['rgba(22, 119, 255, 1)', 'rgba(22, 119, 255, 0)'], ["rgba(22, 119, 255, 1)", "rgba(22, 119, 255, 0)"],
['rgba(206, 79, 81, 1)', 'rgba(206, 79, 81, 0)'], ["rgba(206, 79, 81, 1)", "rgba(206, 79, 81, 0)"],
['rgba(255, 197, 61, 1)', 'rgba(255, 197, 61, 0)'], ["rgba(255, 197, 61, 1)", "rgba(255, 197, 61, 0)"],
['rgba(255, 204, 199, 1)', 'rgba(255, 204, 199, 0)'], ["rgba(255, 204, 199, 1)", "rgba(255, 204, 199, 0)"],
['rgba(179, 127, 235, 1)', 'rgba(179, 127, 235, 0)'], ["rgba(179, 127, 235, 1)", "rgba(179, 127, 235, 0)"],
['rgba(127, 218, 235, 1)', 'rgba(127, 214, 235, 0)'], ["rgba(127, 218, 235, 1)", "rgba(127, 214, 235, 0)"]
]; ];
const names = _.map(list, 'year'); const names = _.map(list, "year");
const datas = _.chain(object.domains).splice(0, 6).map((name, index) => { const datas = _.chain(object.domains)
console.log(_.map(list, name)) .splice(0, 6)
return { .map((name, index) => {
name, console.log(_.map(list, name));
data: _.map(list, `domainNum.${name}`), return {
type: "bar", name,
barWidth: 12, data: _.map(list, `domainNum.${name}`),
itemStyle: { type: "bar",
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ barWidth: 12,
{ offset: 0, color: colors[index % colors.length][0] }, itemStyle: {
// { offset: 0.5, color: '#188df0' }, color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 1, color: colors[index % colors.length][1] } { offset: 0, color: colors[index % colors.length][0] },
]), // { offset: 0.5, color: '#188df0' },
borderRadius: [6, 6, 0, 0] { offset: 1, color: colors[index % colors.length][1] }
} ]),
} borderRadius: [6, 6, 0, 0]
}).value(); }
console.log('names', names) };
const option = { })
tooltip: { .value();
trigger: "axis", console.log("names", names);
axisPointer: { const option = {
type: "shadow" tooltip: {
} trigger: "axis",
}, axisPointer: {
grid: { type: "shadow"
top: 50 }
}, },
legend: { grid: {
// type: "scroll", top: 50
// show: true, },
// orient: "horizontal", legend: {
icon: "circle", // type: "scroll",
}, // show: true,
xAxis: { // orient: "horizontal",
type: "category", icon: "circle"
data: names },
}, xAxis: {
yAxis: { type: "category",
type: "value" data: names
}, },
series: datas yAxis: {
} type: "value"
return option; },
} series: datas
};
return option;
};
# 交互消息类
## ApiResult
```java
public class ApiResult<T> {
@ApiModelProperty("响应码")
private int code;
@ApiModelProperty("响应消息")
private String message;
@ApiModelProperty("是否成功")
private boolean success;
@ApiModelProperty("响应数据")
private T data;
}
```
## LatestExportControlInfo
```java
public class LatestExportControlInfo {
// 出口管制事件ID
private String id;
// 管制信息标题
private String name;
// 管制内容简述
private String summary;
// 发布机构名称
private String postOrgName;
// 发布时间
private Date postDate;
// 涉及领域
private List<String> domains;
// 涉及中国实体数
private Integer cnEntityCount;
// 涉及主要实体
private List<SanctionListBean> sanEntities;
}
```
## AnnualCount
```java
public class AnnualCount {
// 年份
private Integer year;
// 数量
private Integer count;
// 领域列表
private List<String> domain;
}
```
## DomainCount
```java
public class DomainCount {
// 制裁类型名称
private String sanTypeName;
// 领域统计信息
private List<BaseCount> domainCountInfo;
}
```
## BaseCount
```java
public class BaseCount {
// 统计名称
private String name;
// 数量
private Integer count;
}
```
## ExportPageQuery
```java
public class ExportPageQuery extends BasePageQuery {
// 类型名称(实体清单)
private String typeName;
// 制裁时间
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date sanctionDate;
// 是否只看中国实体
private Boolean isCn;
}
```
## BasePageQuery
```java
public class BasePageQuery {
// 查询页
private Integer pageNum = 1;
// 每页数量
private Integer pageSize = 10;
// 排序字段
private String sortField;
// 排序方式
private String sortOrder;
}
```
## SanctionProcess
```java
public class SanctionProcess {
// 制裁事件ID
private String id;
// 制裁时间
private Date postDate;
// 制裁标题
private String name;
// 制裁内容简述
private String summary;
// 涉及中国实体数
private Integer cnEntityCount;
}
```
## SanctionListBean
```java
public class SanctionListBean extends BaseBean {
@Id
@Column(name = "ID", nullable = false)
private String id;
@Column(name = "ENTITY_NAME")
private String entityName;
@Column(name = "SAN_TYPE_ID")
private Integer sanTypeId;
@Column(name = "ENTITY_TYPE")
private Integer entityType;
@Column(name = "ENTITY_ID")
private String entityId;
@Column(name = "ENTITY_NAME_ZH")
private String entityNameZh;
@Column(name = "COUNTRY_ID")
private String countryId;
@Column(name = "SAN_REASON")
private String sanReason;
@Column(name = "SAN_INTENSITY")
private char sanIntensity;
@Column(name = "START_TIME")
private Date startTime;
@Column(name = "END_TIME")
private Date endTime;
@Column(name = "IS_KEY")
private char isKey;
@Transient
private List<TechDomainBean> techDomainList;
/**
* 领域列表
*/
@Transient
private List<String> techDomains;
/**
* 50%规则子企业数
*/
@Transient
private Integer ruleOrgCount;
}
```
## OrgInfo
```java
public class OrgInfo {
// 机构id
private String id;
// 机构名称
private String orgName;
// 相关制裁措施列表
private List<String> sanTypeList;
// 机构职责
private String orgDuty;
// 机构图片
private String imageUrl;
// 人员列表
private List<PersonInfo> personList;
}
```
## PersonInfo
```java
public class PersonInfo {
// id
private String id;
// 姓名
private String name;
// 党派
private String party;
// 职位
private String position;
// 头像链接
private String imageUrl;
}
```
## SanCountInfo
```java
public class SanCountInfo {
// 实体数
private Integer entityNum;
// 实体变动数
private Integer entityChange;
// 上市公司数
private Integer listedCompanyNum;
// 上市公司变动数
private Integer listedCompanyChange;
// 涉及领域名数
private Integer domainNum;
// 涉及领域变动数
private Integer domainChange;
// 实体类型数
private Integer typeNum;
// 实体类型变动数
private Integer typeChange;
}
```
## Chain
```java
public class Chain {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// 名称
@Column(name = "name")
private String name;
// 中文名称
@Column(name = "name_zh")
private String nameZh;
// 值
@Column(name = "description")
private String description;
// 父级id
@Column(name = "parent_id")
private Integer parentId;
// 是否为产业链主分支
@Column(name = "is_main_branch")
private String isMainBranch;
// 下属产业链分支
@Transient
private List<Chain> children;
}
```
## FishboneResp
```java
public class FishboneResp<T> {
private String text;
private List<T> causes;
}
```
## AreasStreamResp
```java
public class AreasStreamResp {
// 上游国内企业数量
private int upstreamInternalCount;
// 上游国内占比
private double upstreamInternalRate;
// 上游受制裁企业数量
private int upstreamEntityCount;
// 上游受制裁占比
private double upstreamEntityRate;
// 中游国内企业数量
private int midstreamInternalCount;
// 中游国内占比
private double midstreamInternalRate;
// 中游受制裁企业数量
private int midstreamEntityCount;
// 中游受制裁占比
private double midstreamEntityRate;
// 下游国内企业数量
private int downstreamInternalCount;
// 下游国内占比
private double downstreamInternalRate;
// 下游受制裁企业数量
private int downstreamEntityCount;
// 下游受制裁占比
private double downstreamEntityRate;
}
```
## AnnualDomainQuery
```java
public class AnnualDomainQuery {
// 开始年份
private Integer startYear;
// 结束年份
private Integer endYear;
// 是否考虑50%规则
private Boolean isRule;
}
```
## AnnualDomainCount
```java
public class AnnualDomainCount {
// 年度领域统计
private List<DomainCount> yearDomainCount;
// 所有领域
private List<BaseCount> domians;
}
```
## SanctionTypeBean
```java
public class SanctionTypeBean extends BaseBean {
@Id
@Column(name = "ID", nullable = false)
private Integer id;
@Column(name = "NAME")
private String name;
@Column(name = "NAME_ZH")
private String nameZh;
@Column(name = "NAME_ABBR")
private String nameAbbr;
@Column(name = "DESCRIPTION")
private String description;
// 发布国家
@Column(name = "POST_COUNTRY_ID")
private String postCountryId;
// 发布机构
@Column(name = "POST_ORG_ID")
private String postOrgId;
// 是否出口管制 1:是 0:否
@Column(name = "IS_EXPORT_CONTROL")
private String isExportControl;
// 总发布次数
@Transient
private Integer postCount;
}
```
# 字典
## 领域类别(id:name)
1:人工智能、2:生物科技、3:新一代信息技术、4:量子科技、5:新能源、6:集成电路、7:海洋、8:先进制造、9:新材料、10:航空航天、11:深海、12:极地、13:太空、14:核
## 实体类别(id:name)
1:人物、2:机构
# 出口管制
## **获取出口管制类清单统计信息**
请求地址:/sanctionList/export/getTotalInfo
请求类型:GET
输入参数:
​ 参数:无输入
​ 请求头:携带token,内容为:
```
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiIsImlzcyI6ImRhdGEtY2VudGVyIiwiZXhwIjozODI1ODM1NTkxLCJpYXQiOjE2NzgzNTE5NTMsImp0aSI6IjI4YmY1NTZjMTc0MDQ3YjJiNTExNWM3NzVhYjhlNWRmIiwidXNlcm5hbWUiOiJzdXBlcl91c2VyIn0.zHyVzsleX2lEqjDBYRpwluu_wy2nZKGl0dw3IUGnKNw
```
输出结果:ApiResult<List<SanctionTypeBean>>
## 最新出口管制政策(4条)
请求地址:http://8.140.26.4:9085/entitiesDataInfo/getLatestInfo
请求类型:GET
输入参数:
​ 参数:无输入
​ 请求头:携带token,内容为:
```
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiIsImlzcyI6ImRhdGEtY2VudGVyIiwiZXhwIjozODI1ODM1NTkxLCJpYXQiOjE2NzgzNTE5NTMsImp0aSI6IjI4YmY1NTZjMTc0MDQ3YjJiNTExNWM3NzVhYjhlNWRmIiwidXNlcm5hbWUiOiJzdXBlcl91c2VyIn0.zHyVzsleX2lEqjDBYRpwluu_wy2nZKGl0dw3IUGnKNw
```
输出结果:ApiResult<LatestExportControlInfo>
## 发布(更新)频度
请求地址:http://8.140.26.4:9085/entitiesDataCount/getAnnualCount
请求类型:GET
输入参数:
​ 参数:Integer sanTypeId(制裁类别)
​ 实体清单发布频度:1;CCL发布频度:X(待定,暂无数据)
​ 请求头:携带token,内容为:
```
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiIsImlzcyI6ImRhdGEtY2VudGVyIiwiZXhwIjozODI1ODM1NTkxLCJpYXQiOjE2NzgzNTE5NTMsImp0aSI6IjI4YmY1NTZjMTc0MDQ3YjJiNTExNWM3NzVhYjhlNWRmIiwidXNlcm5hbWUiOiJzdXBlcl91c2VyIn0.zHyVzsleX2lEqjDBYRpwluu_wy2nZKGl0dw3IUGnKNw
```
输出结果:ApiResult<List<AnnualCount>>
## **制裁领域分析**(20251215)
请求地址:http://8.140.26.4:9085/entitiesDataCount/getSanDomainCount
请求类型:GET
输入参数:
​ 参数:Boolean rule(是否勾选50%规则)
​ 请求头:携带token,内容为:
```
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiIsImlzcyI6ImRhdGEtY2VudGVyIiwiZXhwIjozODI1ODM1NTkxLCJpYXQiOjE2NzgzNTE5NTMsImp0aSI6IjI4YmY1NTZjMTc0MDQ3YjJiNTExNWM3NzVhYjhlNWRmIiwidXNlcm5hbWUiOiJzdXBlcl91c2VyIn0.zHyVzsleX2lEqjDBYRpwluu_wy2nZKGl0dw3IUGnKNw
```
输出结果:ApiResult<List<DomainCount>>
## **历次制裁过程**
请求地址:http://8.140.26.4:9085/entitiesDataCount/getSanctionProcess
请求类型:POST
输入参数:
​ 参数:ExportPageQuery exportPageQuery
​ 请求头:携带token,内容为:
```
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiIsImlzcyI6ImRhdGEtY2VudGVyIiwiZXhwIjozODI1ODM1NTkxLCJpYXQiOjE2NzgzNTE5NTMsImp0aSI6IjI4YmY1NTZjMTc0MDQ3YjJiNTExNWM3NzVhYjhlNWRmIiwidXNlcm5hbWUiOiJzdXBlcl91c2VyIn0.zHyVzsleX2lEqjDBYRpwluu_wy2nZKGl0dw3IUGnKNw
```
输出结果:ApiResult<Page<SanctionProcess>>
## **制裁实体清单**列表(20251215)
请求地址:http://8.140.26.4:9085/sanctionList/pageQuery
请求类型:POST
输入参数:
​ 参数:ExportPageQuery exportPageQuery
​ 出口管制-概览页请求时:typeName=实体清单
​ 实体清单-制裁概览页请求时:typeName=实体清单,sanctionDate=该次制裁的具体时间
​ 请求头:携带token,内容为:
```
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkYXRhLWNlbnRlciIsImF1ZCI6IndlYiIsImlzcyI6ImRhdGEtY2VudGVyIiwiZXhwIjozODI1ODM1NTkxLCJpYXQiOjE2NzgzNTE5NTMsImp0aSI6IjI4YmY1NTZjMTc0MDQ3YjJiNTExNWM3NzVhYjhlNWRmIiwidXNlcm5hbWUiOiJzdXBlcl91c2VyIn0.zHyVzsleX2lEqjDBYRpwluu_wy2nZKGl0dw3IUGnKNw
```
输出结果:ApiResult<Page<SanctionListBean>>
## **发布机构与重点人物**
请求地址:/sanctionList/getPublishedOrg
请求类型:GET
输入参数:
​ 参数:Integer sanTypeId
​ 暂时固定输入:1;对应实体清单发布机构
​ 请求头:携带token
输出结果:ApiResult<OrgInfo>
## **领域分布查询**
请求地址:/entitiesDataInfo/getDomianDistribution
请求类型:GET
输入参数:
​ 参数:String sanctionDate (制裁时间)
​ 请求头:携带token
输出结果:ApiResult<List<BaseCount>>
## **类型分布查询**
请求地址:/entitiesDataInfo/getTypeDistribution
请求类型:GET
输入参数:
​ 参数:String sanctionDate (制裁时间)
​ 请求头:携带token
输出结果:ApiResult<List<BaseCount>>
## **区域分布查询**
请求地址:/entitiesDataInfo/getRegionDistribution
请求类型:GET
输入参数:
​ 参数:String sanctionDate (制裁时间)
​ 请求头:携带token
输出结果:ApiResult<List<BaseCount>>
## **制裁理由查询**
请求地址:/entitiesDataInfo/getSanReason
请求类型:GET
输入参数:
​ 参数:String sanctionDate (制裁时间)
​ 请求头:携带token
输出结果:ApiResult<List<String>>
## **深度挖掘-制裁信息变化统计**
请求地址:/entitiesDataInfo/getSanCountInfo
请求类型:GET
输入参数:
​ 参数:String sanctionDate (制裁时间)
​ 请求头:携带token
输出结果:ApiResult<SanCountInfo>
## **年度实体数统计**
请求地址:/entitiesDataInfo/getCountByDomianAndType
请求类型:GET
输入参数:
​ 参数:String domianId (非必需,领域类别ID),Integer typeId (非必需,实体类别ID)
​ 参考字典
​ 请求头:携带token
输出结果:ApiResult<SanCountInfo>
## **重点实体列表查询**
请求地址:/entitiesDataInfo/getKeyEntities
请求类型:GET
输入参数:
​ 参数:String sanctionDate(必需),String searchText(非必需,检索文本)
​ 请求头:携带token
输出结果:ApiResult<List<OrgInfo>>
## **上市企业制裁强度**
请求地址:/entitiesDataInfo/listedEntity/sanInfo
请求类型:GET
输入参数:
​ 参数:无
​ 请求头:携带token
输出结果:ApiResult<List<BaseCount>>
## **上市企业融资变化情况**
请求地址:/entitiesDataInfo/listedEntity/financing
请求类型:GET
输入参数:
​ 参数:无
​ 请求头:携带token
输出结果:ApiResult<List<BaseCount>>
## **上市企业市值变化情况**
请求地址:/entitiesDataInfo/listedEntity/market
请求类型:GET
输入参数:
​ 参数:无
​ 请求头:携带token
输出结果:ApiResult<List<BaseCount>>
## **重点上市企业列表**
请求地址:/entitiesDataInfo/listedEntity/keyEntity
请求类型:GET
输入参数:
​ 参数:String sanctionDate(必需),String searchText(非必需,检索文本)
​ 请求头:携带token
输出结果:ApiResult<List<OrgInfo>>
## **历次制裁涉及领域数查询**
请求地址:/entitiesDataInfo/getPreviousDomian
请求类型:GET
输入参数:
​ 参数:无
​ 请求头:携带token
输出结果:ApiResult<List<BaseCount>>
## **具体领域的制裁实体数统计**
请求地址:/entitiesDataInfo/getDomianAnnual
请求类型:GET
输入参数:
​ 参数:String domainId
​ 请求头:携带token
输出结果:ApiResult<List<AnnualCount>>
## **具体实体类型的制裁实体数统计**
请求地址:/entitiesDataInfo/getEntityTypeAnnual
请求类型:GET
输入参数:
​ 参数:Integer entityTypeId
​ 请求头:携带token
输出结果:ApiResult<List<AnnualCount>>
## **产业链结构查询**
请求地址:/chain/getChainTree
请求类型:GET
输入参数:
​ 参数:Integer chainId,非必需
​ 请求头:携带token
输出结果:ApiResult<List<Chain>>
## **产业链鱼骨图信息查询**
请求地址:/chain/getChainFishbone
请求类型:GET
输入参数:
​ 参数:Integer chainId
​ 请求头:携带token
输出结果:ApiResult<FishboneResp>
## **产业链中国企业实体信息查询**
请求地址:/chain/getChainEntityStat
请求类型:GET
输入参数:
​ 参数:Integer chainId
​ 请求头:携带token
输出结果:ApiResult<AreasStreamResp>
## **实体列表查询**
请求地址:/entitiesDataInfo/getEntityList
请求类型:GET
输入参数:
​ 参数:String sanctionDate(制裁时间),String domainId(领域ID)
​ 请求头:携带token
输出结果:ApiResult<List<OrgInfo>>
## **历年制裁领域统计**
请求地址:/entitiesDataCount/getAnnualSanDomain
请求类型:POST
输入参数:
​ 参数:AnnualDomainQuery annualDomainQuery
​ 请求头:携带token
输出结果:ApiResult<List<AnnualDomainCount>>
## **新增实体数量增长趋势**
请求地址:/entitiesDataInfo/yoyComparison
请求类型:GET
输入参数:
​ 参数:无输入
​ 请求头:携带token
输出结果:ApiResult<List<AnnualCount>>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论