提交 3c267bc1 authored 作者: 朱亚刚's avatar 朱亚刚
......@@ -20,7 +20,10 @@ export function getAllDomainCount(params) {
/**
* @header token
* @param {Object} params
* @param {String} params.byYOrM - 按月统计或按年统计
* @param {String} params.startDate - 开始日期
* @param {String} params.endDate - 结束日期
* @param {String} params.org - 机构
* @param {String} params.sanMeasures - 制裁手段
*/
export function getDomainContainmentTrend(params) {
return request({
......
......@@ -2,7 +2,7 @@
import WrittingAsstaint from "@/views/writtingAsstaint/index.vue";
const writtingRoutes = [
// 法案系统路由
// 智能写报路由
{
path: "/writtingAsstaint",
name: "writtingAsstaint",
......
......@@ -39,13 +39,26 @@
</div>
<div class="title-right">
<el-select
v-model="select1"
placeholder="按月统计"
v-model="deptValue"
placeholder="全部部门"
class="custom-select"
@change="handleGetDomainContainmentTrend"
>
<el-option label="按月统计" value="按月统计" />
<el-option label="按年统计" value="按年统计" />
<el-option label="全部部门" value="" />
<el-option
v-for="item in departmentList"
:key="item.departId"
:label="item.departName"
:value="item.departId"
/>
</el-select>
<el-select
v-model="methodValue"
placeholder="全部制裁手段"
class="custom-select"
@change="handleGetDomainContainmentTrend"
>
<el-option v-for="item in methodOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</div>
......@@ -114,7 +127,7 @@
<el-option v-for="item in fieldOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="main-box">
<div class="main-box" v-loading="rankLoading" element-loading-background="rgba(255, 255, 255, 0.5)">
<!-- 机构排行的原有样式 -->
<template v-if="rankType === 'institution'">
<div v-for="(item, index) in rankList" :key="index" class="rank-item">
......@@ -264,13 +277,45 @@ import {
getDomainContainmentRanking,
getDomainContainmentTimeline
} from "@/api/zmOverview/allDomains";
import { getUSGovernmentLatestDynamic } from "@/api/allGovernment.js";
import { getUSGovernmentLatestDynamic, getDepartmentList } from "@/api/allGovernment.js";
const router = useRouter();
const activeDate = inject("activeDate");
const select1 = ref("按月统计");
const deptValue = ref("");
const methodValue = ref("");
const departmentList = ref([]);
const methodOptions = ref([
{ label: "全部制裁手段", value: "" },
{ label: "法案", value: "-1" },
{ label: "政令", value: "-2" },
{ label: "实体清单", value: "1" },
{ label: "特别国民指定清单", value: "2" },
{ label: "涉军企业", value: "3" },
{ label: "行业制裁识别清单", value: "4" },
{ label: "无法核实清单", value: "5" },
{ label: "军事最终用户清单", value: "6" },
{ label: "非SDN中国军工企业名单", value: "7" },
{ label: "拒绝往来人员清单", value: "8" },
{ label: "军事最终用途与最终用户规则", value: "9" },
{ label: "欧盟合并制裁清单", value: "10" },
{ label: "英国制裁清单", value: "11" },
{ label: "加拿大合并自主制裁清单", value: "12" },
{ label: "商业管制清单", value: "13" }
]);
const getDepartmentListData = async () => {
try {
const res = await getDepartmentList();
if (res.code === 200 && res.data) {
departmentList.value = res.data;
}
} catch (error) {
console.error("获取部门数据失败:", error);
}
};
const rankType = ref("institution");
const rankLoading = ref(false);
const selectedField = ref("");
const selectedFieldTimeline = ref("");
const timelineContainerWidth = 1700;
......@@ -722,9 +767,18 @@ const box5Data = ref({});
const handleGetDomainContainmentTrend = async () => {
try {
const res = await getDomainContainmentTrend({
byYOrM: select1.value
});
const { startDate, endDate } = getCalculatedDate(activeDate.value);
const params = {
startDate,
endDate
};
if (deptValue.value) {
params.org = deptValue.value;
}
if (methodValue.value) {
params.sanMeasures = methodValue.value;
}
const res = await getDomainContainmentTrend(params);
console.log("美对华领域打压遏制数量趋势", res);
if (res.code === 200 && res.data) {
......@@ -847,6 +901,7 @@ const rankTypeMap = {
// 获取领域遏制排名数据
const handleGetDomainContainmentRanking = async () => {
rankLoading.value = true;
rankList.value = [];
try {
const res = await getDomainContainmentRanking(
......@@ -863,6 +918,8 @@ const handleGetDomainContainmentRanking = async () => {
console.error("获取美对华领域打压遏制排行失败:", error);
// 设置默认空数组
rankList.value = [];
} finally {
rankLoading.value = false;
}
};
......@@ -918,6 +975,7 @@ onMounted(() => {
// let Chart = getMultiLineChart(box5Data.value);
// setChart(Chart, "chartRef");
handleGetDomainContainmentTrend();
getDepartmentListData();
handleGetAllDomainCount();
handleGetDomainContainmentRanking();
......@@ -932,6 +990,7 @@ onUnmounted(() => {
watch(activeDate, () => {
handleGetAllDomainCount();
handleGetDomainContainmentTrend();
});
</script>
......
......@@ -310,6 +310,189 @@ const countryNameMap = {
Jamaica: "Jamaica"
};
const nameMap = {
Afghanistan: "阿富汗",
Angola: "安哥拉",
Albania: "阿尔巴尼亚",
"United Arab Emirates": "阿联酋",
Argentina: "阿根廷",
Armenia: "亚美尼亚",
"French Southern and Antarctic Lands": "法属南半球和南极领地",
Australia: "澳大利亚",
Austria: "奥地利",
Azerbaijan: "阿塞拜疆",
Burundi: "布隆迪",
Belgium: "比利时",
Benin: "贝宁",
"Burkina Faso": "布基纳法索",
Bangladesh: "孟加拉国",
Bulgaria: "保加利亚",
"The Bahamas": "巴哈马",
"Bosnia and Herzegovina": "波斯尼亚和黑塞哥维那",
Belarus: "白俄罗斯",
Belize: "伯利兹",
Bermuda: "百慕大",
Bolivia: "玻利维亚",
Brazil: "巴西",
Brunei: "文莱",
Bhutan: "不丹",
Botswana: "博茨瓦纳",
"Central African Republic": "中非共和国",
Canada: "加拿大",
Switzerland: "瑞士",
Chile: "智利",
China: "中国",
"Ivory Coast": "科特迪瓦",
Cameroon: "喀麦隆",
"Democratic Republic of the Congo": "刚果民主共和国",
"Republic of the Congo": "刚果共和国",
Colombia: "哥伦比亚",
"Costa Rica": "哥斯达黎加",
Cuba: "古巴",
"Northern Cyprus": "北塞浦路斯",
Cyprus: "塞浦路斯",
"Czech Republic": "捷克",
Germany: "德国",
Djibouti: "吉布提",
Denmark: "丹麦",
"Dominican Republic": "多米尼加共和国",
Algeria: "阿尔及利亚",
Ecuador: "厄瓜多尔",
Egypt: "埃及",
Eritrea: "厄立特里亚",
Spain: "西班牙",
Estonia: "爱沙尼亚",
Ethiopia: "埃塞俄比亚",
Finland: "芬兰",
Fiji: "斐济",
"Falkland Islands": "福克兰群岛",
France: "法国",
Gabon: "加蓬",
"United Kingdom": "英国",
Georgia: "格鲁吉亚",
Ghana: "加纳",
Guinea: "几内亚",
Gambia: "冈比亚",
"Guinea Bissau": "几内亚比绍",
"Equatorial Guinea": "赤道几内亚",
Greece: "希腊",
Greenland: "格陵兰",
Guatemala: "危地马拉",
"French Guiana": "法属圭亚那",
Guyana: "圭亚那",
Honduras: "洪都拉斯",
Croatia: "克罗地亚",
Haiti: "海地",
Hungary: "匈牙利",
Indonesia: "印度尼西亚",
India: "印度",
Ireland: "爱尔兰",
Iran: "伊朗",
Iraq: "伊拉克",
Iceland: "冰岛",
Israel: "以色列",
Italy: "意大利",
Jamaica: "牙买加",
Jordan: "约旦",
Japan: "日本",
Kazakhstan: "哈萨克斯坦",
Kenya: "肯尼亚",
Kyrgyzstan: "吉尔吉斯斯坦",
Cambodia: "柬埔寨",
"South Korea": "韩国",
Korea: "韩国",
Kosovo: "科索沃",
Kuwait: "科威特",
Laos: "老挝",
Lebanon: "黎巴嫩",
Liberia: "利比里亚",
Libya: "利比亚",
"Sri Lanka": "斯里兰卡",
Lesotho: "莱索托",
Lithuania: "立陶宛",
Luxembourg: "卢森堡",
Latvia: "拉脱维亚",
Morocco: "摩洛哥",
Moldova: "摩尔多瓦",
Madagascar: "马达加斯加",
Mexico: "墨西哥",
Macedonia: "马其顿",
Mali: "马里",
Myanmar: "缅甸",
Montenegro: "黑山",
Mongolia: "蒙古",
Mozambique: "莫桑比克",
Mauritania: "毛里塔尼亚",
Malawi: "马拉维",
Malaysia: "马来西亚",
Namibia: "纳米比亚",
"New Caledonia": "新喀里多尼亚",
Niger: "尼日尔",
Nigeria: "尼日利亚",
Nicaragua: "尼加拉瓜",
Netherlands: "荷兰",
Norway: "挪威",
Nepal: "尼泊尔",
"New Zealand": "新西兰",
Oman: "阿曼",
Pakistan: "巴基斯坦",
Panama: "巴拿马",
Peru: "秘鲁",
Philippines: "菲律宾",
"Papua New Guinea": "巴布亚新几内亚",
Poland: "波兰",
"Puerto Rico": "波多黎各",
"North Korea": "朝鲜",
Portugal: "葡萄牙",
Paraguay: "巴拉圭",
Qatar: "卡塔尔",
Romania: "罗马尼亚",
Russia: "俄罗斯",
Rwanda: "卢旺达",
"Western Sahara": "西撒哈拉",
"Saudi Arabia": "沙特阿拉伯",
Sudan: "苏丹",
"South Sudan": "南苏丹",
Senegal: "塞内加尔",
"Solomon Islands": "所罗门群岛",
"Sierra Leone": "塞拉利昂",
"El Salvador": "萨尔瓦多",
"Somaliland": "索马里兰",
Somalia: "索马里",
"Republic of Serbia": "塞尔维亚",
Suriname: "苏里南",
Slovakia: "斯洛伐克",
Slovenia: "斯洛文尼亚",
Sweden: "瑞典",
Swaziland: "斯威士兰",
Syria: "叙利亚",
Chad: "乍得",
Togo: "多哥",
Thailand: "泰国",
Tajikistan: "塔吉克斯坦",
Turkmenistan: "土库曼斯坦",
"East Timor": "东帝汶",
"Trinidad and Tobago": "特立尼达和多巴哥",
Tunisia: "突尼斯",
Turkey: "土耳其",
Tanzania: "坦桑尼亚",
"United Republic of Tanzania": "坦桑尼亚",
Uganda: "乌干达",
Ukraine: "乌克兰",
Uruguay: "乌拉圭",
"United States": "美国",
Uzbekistan: "乌兹别克斯坦",
Venezuela: "委内瑞拉",
Vietnam: "越南",
Vanuatu: "瓦努阿图",
"West Bank": "西岸",
Yemen: "也门",
"South Africa": "南非",
Zambia: "赞比亚",
Zimbabwe: "津巴布韦",
Singapore: "新加坡"
};
const next = () => {
let arr = [...carouselList.value];
if (startIndex.value < carouselList.value.length - 5) {
......@@ -392,6 +575,55 @@ const getColorByValue = (value, maxValue) => {
return `rgb(${r}, ${g}, ${b})`;
};
/**
* 根据 value 和 maxValue 生成高饱和度但不刺眼的 RGB 颜色
* @param {number} value - 当前值(0 到 maxValue)
* @param {number} maxValue - 最大值
* @returns {string} RGB 颜色字符串,如 "rgb(255, 0, 0)"
*/
const getColorByValueRandom = (value, maxValue) => {
const ratio = Math.min(Math.max(value / maxValue, 0), 1);
// HSL 参数:高饱和度,中等偏暗亮度,避免太亮或黑色
let h = ratio * 360; // 色相:0°(红) 到 360°(循环)
h = h % 360; // 修复:确保 h=360 映射回 0,避免黑色
const s = 100; // 饱和度 100%
const l = 40; // 亮度 40%(不亮不暗)
// HSL 到 RGB 转换
const hslToRgb = (h, s, l) => {
s /= 100;
l /= 100;
const c = (1 - Math.abs(2 * l - 1)) * s;
const x = c * (1 - Math.abs((h / 60) % 2 - 1));
const m = l - c / 2;
let r = 0, g = 0, b = 0;
if (0 <= h && h < 60) {
r = c; g = x; b = 0;
} else if (60 <= h && h < 120) {
r = x; g = c; b = 0;
} else if (120 <= h && h < 180) {
r = 0; g = c; b = x;
} else if (180 <= h && h < 240) {
r = 0; g = x; b = c;
} else if (240 <= h && h < 300) {
r = x; g = 0; b = c;
} else if (300 <= h && h < 360) {
r = c; g = 0; b = x;
}
// 移除 else 分支(修复后无需,h 已 %360)
r = Math.round((r + m) * 255);
g = Math.round((g + m) * 255);
b = Math.round((b + m) * 255);
return `rgb(${r}, ${g}, ${b})`;
};
return hslToRgb(h, s, l);
}
const chartDom = ref();
const myChart = ref();
function createChart() {
......@@ -400,12 +632,21 @@ function createChart() {
const maxValue = Math.max(...countList.value.map(item => item.value));
// 为每个数据项计算颜色
const processedData = countList.value.map(item => ({
const processedData = countList.value.map(item => {
const color = getColorByValueRandom(item.value, maxValue);
return {
...item,
name: nameMap[item.name] || item.name, // 将英文名转换为中文名以匹配地图区域
itemStyle: {
color: color
},
emphasis: {
itemStyle: {
color: getColorByValue(item.value, maxValue)
areaColor: color // 高亮时保持原色
}
}
}));
};
});
const option = {
// geo: {
......@@ -424,14 +665,9 @@ function createChart() {
trigger: "item",
formatter: function (params) {
if (params.data) {
// 从数据中查找对应的中文名称
const item = countList.value.find(item => item.name === params.name);
console.log(item);
if (item) {
return `${item.zhName}: ${params.data.value || 0}`;
} else {
return `${params.name}: ${params.data.value || 0}`;
}
// 既然 processedData 已经匹配上了,params.data 就是正确的数据项
// 包含 value 和 zhName
return `${params.data.zhName || params.name}: ${params.data.value || 0}`;
}
return params.name;
}
......@@ -454,16 +690,17 @@ function createChart() {
{
name: "数据值",
type: "map",
roam: true, // 允许缩放和平移
roam: "move", // 允许平移,禁止缩放
zoom: 1.2, // 初始缩放级别
map: "world",
nameMap: nameMap,
emphasis: {
label: {
show: true,
color: "#fff"
},
itemStyle: {
areaColor: null,
areaColor: "#aaa", // 无数据区域高亮颜色(灰色,避免白色看不清文字)
borderWidth: 1,
borderColor: "#fff"
}
......@@ -637,7 +874,11 @@ const initRightDonut = async () => {
let item = {
name: res.data[i].country,
x: Math.random() * 10,
y: Math.random() * 10
y: Math.random() * 10,
// 节点颜色
itemStyle: {
color: `rgba(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, 1)`
}
};
let item1 = {
name: res.data[i].relationCountry,
......@@ -694,7 +935,7 @@ const initRightDonut = async () => {
series: [
{
type: "graph",
layout: "none",
layout: "circular",
symbolSize: 50,
roam: true,
label: {
......
<!-- src/components/HuaweiSupplyChainChart.vue -->
<template>
<div class="chart-container" ref="chartRef"></div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import * as echarts from "echarts";
// import { graphData } from "./mockData";
import Img from "./assets/images/img.png";
import Img1 from "./assets/images/img1.png";
import Img2 from "./assets/images/img2.png";
import Img3 from "./assets/images/img3.png";
import Img4 from "./assets/images/img4.png";
import Img5 from "./assets/images/img5.png";
import Img6 from "./assets/images/img6.png";
import Img7 from "./assets/images/img7.png";
import Img8 from "./assets/images/img8.png";
const graphData = {
nodes: [
{ id: "huawei", name: "美国众议院", category: 0, symbolSize: 60, symbol: `image://${Img}` },
{ id: "foxconn", name: "唐纳德·特朗普", category: 1, symbolSize: 42, symbol: `image://${Img1}` },
{ id: "huawei-cloud", name: "约翰·斯奎尔斯", category: 2, symbolSize: 42, symbol: `image://${Img2}` },
{ id: "huawei-digital-energy", name: "马尔科·卢比奥", category: 2, symbolSize: 42, symbol: `image://${Img3}` },
{ id: "huawei-intelligent-car", name: "埃隆·马斯克", category: 2, symbolSize: 42, symbol: `image://${Img4}` },
{ id: "qualcomm", name: "道格·伯格姆", category: 2, symbolSize: 42, symbol: `image://${Img5}` },
{ id: "intel", name: "斯科特·贝森特", category: 2, symbolSize: 42, symbol: `image://${Img6}` },
{ id: "mediatek", name: "杰弗里·凯斯勒", category: 2, symbolSize: 42, symbol: `image://${Img7}` },
{ id: "shenghongda", name: "杰弗里·凯德勒", category: 2, symbolSize: 42, symbol: `image://${Img8}` }
// { id: "luxshare", name: "立讯精密", category: 1, symbolSize: 20 },
// { id: "tianma", name: "天马微电子", category: 1, symbolSize: 20 },
// { id: "desay", name: "德赛电池", category: 1, symbolSize: 20 },
// { id: "auspicious-sound", name: "瑞声科技", category: 1, symbolSize: 20 },
// { id: "goertek", name: "歌尔股份", category: 1, symbolSize: 20 },
// { id: "sony-semiconductor", name: "索尼半导体", category: 1, symbolSize: 20 },
// { id: "ibm", name: "IBM", category: 1, symbolSize: 20 },
// { id: "lg-innotek", name: "LG伊诺特", category: 1, symbolSize: 20 },
// { id: "micron", name: "美光科技", category: 1, symbolSize: 20 }
// { id: "nokia", name: "诺基亚", category: 1, symbolSize: 20 },
// { id: "google", name: "谷歌", category: 1, symbolSize: 20 },
// { id: "vodafone", name: "沃达丰", category: 1, symbolSize: 20 },
// { id: "microsoft", name: "微软", category: 1, symbolSize: 20 },
// { id: "china-unicom", name: "中国联通", category: 1, symbolSize: 20 },
// { id: "china-mobile", name: "中国移动", category: 1, symbolSize: 20 },
// { id: "tsmc", name: "台积电", category: 1, symbolSize: 20 }
// { id: "orange", name: "Orange", category: 1, symbolSize: 20 },
// { id: "germany-telecom", name: "德国电信", category: 1, symbolSize: 20 },
// { id: "byd-electronics", name: "比亚迪电子", category: 1, symbolSize: 20 },
// { id: "boe", name: "京东方", category: 1, symbolSize: 20 },
// { id: "dali-optical", name: "大力光", category: 1, symbolSize: 20 },
// { id: "lg-display", name: "LG显示", category: 1, symbolSize: 20 },
// { id: "sunny-optical", name: "舜宇光学", category: 1, symbolSize: 20 }
],
links: [
{ source: "huawei", target: "huawei-cloud" },
{ source: "huawei", target: "huawei-digital-energy" },
{ source: "huawei", target: "huawei-intelligent-car" },
{ source: "huawei", target: "qualcomm" },
{ source: "huawei", target: "intel" },
{ source: "huawei", target: "mediatek" },
{ source: "huawei", target: "shenghongda" },
{ source: "huawei", target: "foxconn" }
// { source: "huawei", target: "luxshare" },
// { source: "huawei", target: "tianma" },
// { source: "huawei", target: "desay" },
// { source: "huawei", target: "auspicious-sound" },
// { source: "huawei", target: "goertek" },
// { source: "huawei", target: "sony-semiconductor" },
// { source: "huawei", target: "ibm" },
// { source: "huawei", target: "lg-innotek" },
// { source: "huawei", target: "micron" }
// { source: "huawei", target: "nokia" },
// { source: "huawei", target: "google" },
// { source: "huawei", target: "vodafone" },
// { source: "huawei", target: "microsoft" },
// { source: "huawei", target: "china-unicom" },
// { source: "huawei", target: "china-mobile" },
// { source: "huawei", target: "tsmc" }
// { source: "huawei", target: "orange" },
// { source: "huawei", target: "germany-telecom" },
// { source: "huawei", target: "byd-electronics" },
// { source: "huawei", target: "boe" },
// { source: "huawei", target: "dali-optical" },
// { source: "huawei", target: "lg-display" },
// { source: "huawei", target: "sunny-optical" }
],
categories: [{ name: "子业务" }, { name: "合作商" }]
};
const chartRef = ref(null);
onMounted(() => {
const chart = echarts.init(chartRef.value);
const option = {
title: { text: "", left: "center" },
tooltip: {},
// legend: {
// data: graphData.categories.map(item => item.name),
// left: "left"
// },
series: [
{
type: "graph",
layout: "force", // 力导向布局
force: {
repulsion: 1000, // 节点排斥力
edgeLength: [50, 200] // 边长度范围
},
data: graphData.nodes,
links: graphData.links,
categories: graphData.categories,
roam: true, // 支持缩放、平移
label: {
show: true,
fontSize: 14,
position: "bottom",
formatter: function (params) {
// 根据条件返回不同样式的文本
if (params.data.category === 0) {
return `{a|${params.name}}`; // 使用富文本样式
} else if (params.data.category === 1) {
return `{b|${params.name}}`;
} else {
return params.name; // 默认样式
}
},
rich: {
a: {
// 重要节点的样式
color: "#000",
fontSize: 18,
fontWeight: "bold",
padding: [2, 4],
borderRadius: 2
},
b: {
// 普通节点的样式
color: "rgba(5, 95, 194, 1)",
fontSize: 14,
}
}
},
edgeSymbol: ["arrow", "none"], // 边的箭头
edgeSymbolSize: [8, 50],
itemStyle: {
color: "rgba(5, 95, 194, 1)"
// borderColor: "red",
// borderWidth: 1
},
lineStyle: {
color: "rgba(174, 214, 255, 1)",
width: 2,
opacity: 0.5
},
emphasis: {
focus: "adjacency",
lineStyle: {
width: 2
}
}
}
]
};
chart.setOption(option);
// 窗口resize时自适应
window.addEventListener("resize", () => chart.resize());
});
</script>
<style scoped>
.chart-container {
width: 100%;
height: 430px;
margin: 0 auto;
background-color: #f7f8f9; /* 深色背景,模拟原图风格 */
}
</style>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论