提交 273f345b authored 作者: coderBryanFu's avatar coderBryanFu

update

......@@ -14,13 +14,13 @@ export function getBillInfo(params) {
// 提出人-根据动议ID获取对应的提出人信息
/**
* @param {id}
* @param {billId}
* @header token
*/
export function getBillPerson(params) {
return request({
method: 'GET',
url: `/api/billInfoBean/person/${params.id}`,
url: `/api/billInfoBean/person/${params.billId}`,
params,
})
}
......@@ -63,6 +63,18 @@ export function getBillBackground(params) {
params,
})
}
// 相关事件-根据法案ID获取相关事件信息
/**
* @param {id}
* @header token
*/
export function getBillInfoEvent(params) {
return request({
method: 'GET',
url: `/api/billInfoBean/event/${params.id}`,
params,
})
}
// 议员相关性-根据法案ID获取议员分析信息(现在只包括名称 支持 反对,没有标签和事件动态)
/**
......@@ -92,20 +104,20 @@ export function getBillContentId(params) {
// 主要条款-根据原文ID获取条款内容
/**
* @param {id,cRelated,currentPage,pageSize}
* @param {billid,id,cRelated,currentPage,pageSize}
* @header token
*/
export function getBillContentTk(params) {
return request({
method: 'GET',
url: `/api/billInfoBean/content/tk/${params.id}`,
url: `/api/billInfoBean/content/tk/${params.billid}/${params.id}`,
params,
})
}
// 限制方式-根据法案原文ID获取限制方式列表
/**
* @param {id}
* @param {billId}
* @header token
*/
export function getBillContentXzfs(params) {
......
......@@ -13,11 +13,11 @@ export function getBillIndustry(params) {
}
// 涉华法案统计
export function getBillCount() {
export function getBillCount(params) {
return request({
method: 'GET',
url: `/api/BillOverview/billCount`,
params
})
}
......@@ -64,4 +64,35 @@ export function getHylyList() {
method: 'GET',
url: `/api/billImpactAnalysis/industry/hylyList`,
})
}
// 获取法案提出部门
/**
* @param {year}
*/
export function getBillPostOrg(params) {
return request({
method: 'GET',
url: `/api/BillOverview/billPostOrg/${params.year}`,
})
}
// 获取关键议员提案
/**
* @param {year}
*/
export function getMemberProposal(params) {
return request({
method: 'GET',
url: `/api/BillOverview/memberProposal/${params.year}`,
})
}
// 获取资源库
export function getBills(params) {
return request({
method: 'GET',
url: `/api/BillOverview/bills`,
params
})
}
\ No newline at end of file
......@@ -12,6 +12,18 @@ export function getBillTimeAnalyze(params) {
})
}
// 修正案次数分析
/**
* @param {id}
*/
export function getBillAmeAnalyzeCount(params) {
return request({
method: 'GET',
url: `/api/billDeepDive/ameAnalyze/count/${params.id}`,
params,
})
}
// 根据法案ID获取党派政治献金
/**
* @param {id, personCongress}
......
......@@ -23,7 +23,8 @@ export function getEntitiesDataCount() {
return request200(
request({
method: "GET",
url: "/api/entitiesDataCount/countData"
// url: "/api/entitiesDataCount/countData",
url: "/api/sanctionList/export/getTotalInfo"
})
);
}
......@@ -62,12 +63,15 @@ export function getEntitiesDataInfo() {
* maxCount: number
* }[]>}
*/
export function getIndustryCountByYear() {
export function getIndustryCountByYear(sanTypeId) {
return request200(
request({
method: "GET",
// url: "/api/entitiesDataCount/industryCountByYear"
url: "/api/entitiesDataCount/getAnnualCount"
url: "/api/entitiesDataCount/getAnnualCount",
params: {
sanTypeId
}
})
);
}
......@@ -84,11 +88,16 @@ export function getIndustryCountByYear() {
* domains: string[]
* }>}
*/
export function getCountDomainByYear() {
export function getCountDomainByYear(isRule, startYear = "2020", endYear = new Date().getFullYear()) {
return request200(
request({
method: "GET",
url: "/api/entitiesDataCount/countDomainByYear"
method: "POST",
url: "/api/entitiesDataCount/getAnnualSanDomain",
data: {
isRule,
startYear,
endYear
}
})
);
}
......@@ -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'
......@@ -292,7 +316,7 @@ export function getDomainDistribution(sanctionDate = "2025-11-11") {
* startTime: string
* }[]>}
*/
export function getEntitiesList(typeName = "实体清单", pageNum = 1, pageSize = 10) {
export function getEntitiesList(typeName = "实体清单", pageNum = 1, pageSize = 10, sanctionDate = "", rule = false) {
return request200(
request({
method: "POST",
......@@ -300,7 +324,9 @@ export function getEntitiesList(typeName = "实体清单", pageNum = 1, pageSize
data: {
typeName,
pageNum,
pageSize
pageSize,
sanctionDate,
rule
}
})
);
......@@ -340,15 +366,16 @@ export function getCompareCountSan(startTime) {
* count:number
* }[]>}
*/
export function getEntitiesChangeCount(domain, type) {
export function getEntitiesChangeCount(domianId, typeId) {
return request200(
request({
method: "GET",
// url: '/api/entitiesDataCount/entitiesChangeCount',
url: "/api/entitiesDataCount/sanCountByYear",
// url: "/api/entitiesDataCount/sanCountByYear",
url: "/api/entitiesDataInfo/getCountByDomianAndType",
params: {
domain,
type
domianId,
typeId
}
})
);
......@@ -377,11 +404,14 @@ export function getEntitiesGrowthTrend() {
* xAxis: string[]
* }>}
*/
export function getEntitiesUpdateCount() {
export function getEntitiesUpdateCount(sanTypeId = 1) {
return request200(
request({
method: "GET",
url: "/api/entitiesDataCount/entitiesUpdateCount"
url: "/api/entitiesDataCount/getAnnualCount",
params: {
sanTypeId
}
})
);
}
......@@ -389,11 +419,14 @@ export function getEntitiesUpdateCount() {
/**
* 制裁领域分析
*/
export function getSanDomainCount() {
export function getSanDomainCount(rule) {
return request200(
request({
method: "GET",
url: "/api/entitiesDataCount/getSanDomainCount"
url: "/api/entitiesDataCount/getSanDomainCount",
params: {
rule
}
})
);
}
......
......@@ -32,6 +32,7 @@ const emit = defineEmits(["click"]);
cursor: pointer;
padding-left: 8px;
padding-right: 8px;
flex-shrink: 0;
}
.activeButton {
border: 1px solid rgb(10, 87, 166);
......
......@@ -61,5 +61,6 @@ function setActiveIndex(item) {
.buttonList {
display: flex;
gap: 8px;
overflow-x: auto;
}
</style>
......@@ -35,8 +35,8 @@
v-for="(item, index) in backgroundList"
:key="item.id"
>
<div class="id">{{ index + 1 }}</div>
<div class="title">{{ item.bjnr }}</div>
<div class="id">{{ (currentPage - 1) * 10 + index + 1 }}</div>
<div class="title">{{ item.backgroundTitle }}</div>
<div class="share">
<img src="./assets/icons/open.png" alt="打开" />
</div>
......@@ -45,7 +45,7 @@
<div class="box1-main-footer">
<div class="info">
{{
`共有${backgroundList.length}条${
`共有${total}条${
box1BtnActive === 1 ? "涉华" : "全部"
}背景`
}}
......@@ -54,7 +54,9 @@
<el-pagination
background
layout="prev, pager, next"
:total="backgroundList.length"
:total="total"
v-model:current-page="currentPage"
@current-change="handleGetBillBackground"
/>
</div>
</div>
......@@ -80,7 +82,7 @@
:key="index"
>
<div class="left">
<img :src="item.image" alt="" />
<img :src="item.imageUrl || item.image" alt="" />
</div>
<div class="center">
<div class="title">{{ item.sjbt }}</div>
......@@ -157,7 +159,7 @@
/></el-icon>
</div>
<div class="right-box1-main-bottom">
<WordCloudMap :data="wordCloudData" :shape="circle" />
<WordCloudMap :data="wordCloudData" shape="circle" />
</div>
</div>
</div>
......@@ -214,25 +216,26 @@ import {
getBillBackground,
getBillEvent,
getBillPersonAnalyze,
getBillInfoEvent
} from "@/api/bill";
const box1BtnActive = ref(1);
const currentPage = ref(1);
const total = ref(0);
const handleClickBox1Btn = (index) => {
box1BtnActive.value = index;
if (index === 2) {
handleGetBillBackground(false);
} else {
handleGetBillBackground(true);
}
currentPage.value = 1;
handleGetBillBackground();
};
const box2BtnActive = ref(1);
const handleClickBox2Btn = (index) => {
box2BtnActive.value = index;
if (index === 1) {
handleGetBillPersonAnalyze(true);
} else {
handleGetBillPersonAnalyze(false);
} else {
handleGetBillPersonAnalyze(true);
}
};
......@@ -439,27 +442,29 @@ const wordCloudData = [
];
// 获取立法背景内容
const handleGetBillBackground = async (cRelated) => {
const handleGetBillBackground = async () => {
const cRelated = box1BtnActive.value === 1 ? 'Y' : 'N';
const params = {
cRelated: cRelated,
id: window.sessionStorage.getItem("billId"),
currentPage: 0,
currentPage: currentPage.value - 1,
pageSize: 10,
};
try {
const res = await getBillBackground(params);
console.log("立法背景", res);
backgroundList.value = res.data.content;
total.value = res.data.totalElements; // 假设API返回totalElements
} catch (error) {}
};
// 获取相关事件
const handleGetRelatedEvent = async () => {
const params = {
id: 1,
id: window.sessionStorage.getItem("billId"),
};
try {
const res = await getBillEvent(params);
const res = await getBillInfoEvent(params);
console.log("相关事件", res);
eventList.value = res.data;
eventList.value.forEach((item, index) => {
......@@ -488,8 +493,9 @@ const handleGetBillPersonAnalyze = async (isOppose) => {
const res = await getBillPersonAnalyze(params);
console.log("议员相关性分析", res);
personList.value = res.data;
personList.value.forEach((item) => {
item.image = user1;
personList.value.forEach((item, index) => {
const imgList = [user1, user2, user3, user4, user5];
item.image = imgList[index % imgList.length];
item.icon = userIcon;
item.icon1 = userIcon1;
});
......@@ -497,9 +503,9 @@ const handleGetBillPersonAnalyze = async (isOppose) => {
};
onMounted(() => {
handleGetBillBackground(true);
handleGetBillBackground();
handleGetRelatedEvent();
handleGetBillPersonAnalyze(true);
handleGetBillPersonAnalyze(false);
});
</script>
......@@ -572,7 +578,7 @@ onMounted(() => {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-content: flex-start;
.box1-main-item {
width: 544px;
height: 48px;
......@@ -598,11 +604,12 @@ onMounted(() => {
width: 440px;
height: 48px;
line-height: 48px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
color: rgb(59, 65, 75);
font-family: "Microsoft YaHei";
font-size: 16px;
font-weight: 400;
text-align: left;
overflow: hidden;
}
.share {
margin-left: 13px;
......@@ -626,8 +633,8 @@ onMounted(() => {
.info {
height: 22px;
line-height: 22px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
color: rgb(132, 136, 142);
font-family: "Microsoft YaHei";
font-size: 14px;
font-weight: 400;
text-align: left;
......@@ -645,6 +652,7 @@ onMounted(() => {
margin-top: 9px;
margin-left: 23px;
height: 300px;
overflow: auto;
.box2-main-item {
width: 1103px;
height: 60px;
......@@ -659,9 +667,9 @@ onMounted(() => {
width: 64px;
height: 48px;
border-radius: 2px;
image {
width: 100px;
height: 100%;
img {
width: 64px;
height: 48px;
}
}
.center {
......
......@@ -84,6 +84,7 @@
<div class="btn-icon">
<img src="@/assets/icons/arrow-right-icon.png" alt="" />
</div>
</div>
<div class="btn" @click="handleToPosi('position2')">
<div class="btn-text">{{ "资讯要闻" }}</div>
......@@ -140,15 +141,15 @@
<div
class="info-box"
:class="{
info1: item.status === 1,
info2: item.status === 2,
info3: item.status === 3,
info4: item.status === 4
info1: index === 0,
info2: index === 1,
info3: index === 2,
info4: index === 3
}"
v-for="(item, index) in curBill.hylyList"
:key="index"
>
{{ item }}
{{ item.industryName }}
</div>
</div>
<div class="box1-main-left-info1">
......@@ -307,6 +308,12 @@
<div class="header-right-text">{{ "数据来源:美国国会官方网站" }}</div>
</div>
</div>
<div class="box5-select">
<el-select v-model="box5Select" placeholder="选择领域" @change="handleBox5Change" style="width: 150px">
<el-option label="全部领域" value="全部领域" />
<el-option v-for="item in categoryList.slice(1)" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</div>
<div class="box5-main" id="box5Chart"></div>
</div>
<div class="box6">
......@@ -386,13 +393,13 @@
@click="handleClcikToCharacter(0)"
>
<div class="box8-main-item-left">
<img :src="item.img" alt="" />
<img :src="item.img" alt="" referrerpolicy="no-referrer" class="left-img" />
<div class="left-icon1">
<img :src="item.dangpai" alt="" />
</div>
<div class="left-icon2">
<img :src="item.yuan" alt="" />
</div>
</div>
</div>
<div class="box8-main-item-center">
<div class="box8-main-item-center-top">{{ item.name }}</div>
......@@ -448,16 +455,7 @@
{{ cate.name }}
</div>
</div>
<div class="select-box">
<el-select v-model="releaseTime" placeholder="选择发布时间" style="width: 120px">
<el-option
v-for="item in releaseTimeList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</div>
<div class="home-main-footer-main">
<div class="left">
......@@ -467,17 +465,16 @@
<div class="title">{{ "科技领域" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox-group class="checkbox-group" v-model="activeAreaList">
<el-checkbox
v-for="area in areaList"
v-for="(area, index) in areaList"
:key="area.id"
v-model="activeAreaList"
:label="area.id"
class="filter-checkbox"
>
{{ area.name }}
</el-checkbox>
</div>
</el-checkbox-group>
</div>
</div>
<div class="select-box">
......@@ -485,18 +482,17 @@
<div class="icon"></div>
<div class="title">{{ "党派" }}</div>
</div>
<div class="select-main select-main1">
<div class="checkbox-group">
<div class="select-main">
<el-checkbox-group class="checkbox-group" v-model="activeDpList">
<el-checkbox
v-for="dp in dpList"
v-for="(dp, index) in dpList"
:key="dp.id"
v-model="activeDpList"
:label="dp.id"
class="filter-checkbox"
>
{{ dp.name }}
</el-checkbox>
</div>
</el-checkbox-group>
</div>
</div>
<div class="select-box">
......@@ -505,17 +501,16 @@
<div class="title">{{ "议院" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox-group class="checkbox-group" v-model="activeYyList">
<el-checkbox
v-for="yy in yyList"
v-for="(yy, index) in yyList"
:key="yy.id"
v-model="activeYyList"
:label="yy.id"
class="filter-checkbox"
>
{{ yy.name }}
</el-checkbox>
</div>
</el-checkbox-group>
</div>
</div>
......@@ -525,17 +520,16 @@
<div class="title">{{ "发布时间" }}</div>
</div>
<div class="select-main">
<div class="checkbox-group">
<el-checkbox-group class="checkbox-group" v-model="activePubTime">
<el-checkbox
v-for="time in pubTime"
v-for="(time, index) in pubTime"
:key="time.id"
v-model="activePubTime"
:label="time.id"
class="filter-checkbox"
>
{{ time.name }}
</el-checkbox>
</div>
</el-checkbox-group>
</div>
</div>
</div>
......@@ -561,6 +555,16 @@
/>
</el-select>
</div>
<div class="right-header-box" style="margin-left: auto;">
<el-select v-model="releaseTime" placeholder="选择发布时间" style="width: 120px">
<el-option
v-for="item in releaseTimeList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
</div>
<div class="right-main">
<div class="right-main-box" v-for="(item, index) in footerBillList" :key="index">
......@@ -620,7 +624,7 @@
</template>
<script setup>
import { onMounted, ref, computed, onUnmounted, nextTick } from "vue";
import { onMounted, ref, computed, onUnmounted, nextTick, watch } from "vue";
import router from "@/router/index";
import setChart from "@/utils/setChart";
......@@ -631,7 +635,9 @@ import {
getBillsByType,
getHylyList,
getBillOverviewKeyTK,
getBillCount
getBillCount,
getBillPostOrg,
getMemberProposal
} from "@/api/bill/billHome";
import DivideHeader from "@/components/DivideHeader.vue";
......@@ -1138,6 +1144,7 @@ const handleGetBillsByType = async () => {
};
// 涉华法案数量
const box5Select = ref("全部领域");
const box5Data = ref({
title: [
"2024-09",
......@@ -1166,11 +1173,31 @@ const box5Data = ref({
});
const handleGetBillCount = async () => {
try {
const res = await getBillCount();
let params = {};
if (box5Select.value !== "全部领域") {
params.industryId = box5Select.value;
}
const res = await getBillCount(params);
console.log("涉华法案统计", res);
if (res.code === 200 && res.data) {
const sortedData = res.data.sort((a, b) => a.month.localeCompare(b.month));
box5Data.value = {
title: sortedData.map(item => item.month),
data: [
{
name: "提出法案",
value: sortedData.map(item => item.totalCount)
},
{
name: "通过法案",
value: sortedData.map(item => item.passCount)
}
],
percent: sortedData.map(item => item.percent)
};
} else {
box5Data.value = {};
// 保持默认数据或清空
// box5Data.value = {};
}
} catch (error) {
console.error("获取涉华法案统计error", error);
......@@ -1179,10 +1206,60 @@ const handleGetBillCount = async () => {
const handleBox5 = async () => {
await handleGetBillCount();
let box5Chart = getMultiLineChart(box5Data.value.title, box5Data.value.data[0].value, box5Data.value.data[1].value);
const proposed = box5Data.value.data[0].value;
const passed = box5Data.value.data[1].value;
const rate = box5Data.value.percent || proposed.map((p, i) => {
const pass = passed[i] || 0;
return p ? ((pass / p) * 100).toFixed(2) : 0;
});
let box5Chart = getMultiLineChart(box5Data.value.title, proposed, passed, rate);
setChart(box5Chart, "box5Chart");
};
const handleBox5Change = () => {
handleBox5();
};
// 法案提出部门
const handleBox7Data = async () => {
try {
const res = await getBillPostOrg({ year: box7selectetedTime.value });
console.log("法案提出部门", res);
if (res.code === 200 && res.data) {
const apiData = res.data;
const houseItems = apiData.filter(i => i.congressName === "House");
const senateItems = apiData.filter(i => i.congressName === "Senate");
const houseTotal = houseItems.reduce((sum, i) => sum + i.countBill, 0);
const senateTotal = senateItems.reduce((sum, i) => sum + i.countBill, 0);
const data1 = [];
if (houseItems.length > 0) {
data1.push({ name: "众议院", value: houseTotal });
}
if (senateItems.length > 0) {
data1.push({ name: "参议院", value: senateTotal });
}
const data2 = [...houseItems, ...senateItems].map(item => ({
name: item.originDepart,
value: item.countBill,
type: item.congressName === "House" ? "众议院" : "参议院"
}));
const box7Chart = getDoublePieChart(data1, data2);
setChart(box7Chart, "box7Chart");
}
} catch (error) {
console.error("获取法案提出部门数据失败", error);
}
};
watch(box7selectetedTime, () => {
handleBox7Data();
});
// 关键条款
const wordCloudData = ref([
{ name: "限制中国获取能源技术", value: 100 },
......@@ -1379,48 +1456,30 @@ const box7Data = ref([
]
]);
const box8Data = ref([
{
name: "汤姆·科顿",
zhiwei: "参议院情报委员会主席",
img: Message3,
dangpai: Cyy,
yuan: Ghd,
num: 8
},
{
name: "吉姆·里施",
zhiwei: "参议院外交关系委员会主席",
img: Message3,
dangpai: Cyy,
yuan: Ghd,
num: 4
},
{
name: "特德·克鲁兹",
zhiwei: "参议院商务、科学和交通委员会主席",
img: Message3,
dangpai: Cyy,
yuan: Ghd,
num: 2
},
{
name: "里克·克劳福德",
zhiwei: "众议院美中战略竞争特设委员会主席",
img: Message3,
dangpai: Cyy,
yuan: Ghd,
num: 2
},
{
name: "布莱恩·马斯特",
zhiwei: "众议院情报委员会主席",
img: Message3,
dangpai: Cyy,
yuan: Ghd,
num: 2
const box8Data = ref([]);
const handleBox8Data = async () => {
try {
const res = await getMemberProposal({ year: box8selectetedTime.value });
console.log("关键议员提案", res);
if (res.code === 200 && res.data) {
box8Data.value = res.data.map(item => ({
name: item.memberName,
zhiwei: item.position,
img: item.imageUrl,
num: item.countProposal,
dangpai: Cyy,
yuan: item.position === "Democratic" ? Mzd : Ghd
}));
}
} catch (error) {
console.error("获取关键议员提案失败", error);
}
]);
};
watch(box8selectetedTime, () => {
handleBox8Data();
});
const handleToPosi = id => {
// 0 618 1240 2350
......@@ -1464,26 +1523,36 @@ const handleClickTab = tab => {
};
const areaList = [
{ id: "全部领域", name: "全部领域" },
{ id: "人工智能", name: "人工智能" },
{ id: "集成电路", name: "集成电路" },
{ id: "通信网络", name: "通信网络" },
{ id: "量子科技", name: "量子科技" }
{ id: "先进制造", name: "先进制造" },
{ id: "量子科技", name: "量子科技" },
{ id: "生物科技", name: "生物科技" },
{ id: "能源", name: "能源" },
{ id: "航空航天", name: "航空航天" },
{ id: "新材料", name: "新材料" },
{ id: "海洋", name: "海洋" }
];
const activeAreaList = ["人工智能"];
const activeAreaList = ref(["全部领域"]);
const dpList = ref([
{ id: "全部党派", name: "全部党派" },
{ id: "民主党", name: "民主党" },
{ id: "共和党", name: "共和党" }
]);
const activeDpList = ["民主党"];
const activeDpList = ref(["全部党派"]);
const yyList = ref([
{ id: "全部议院", name: "全部议院" },
{ id: "参议院", name: "参议院" },
{ id: "众议院", name: "众议院" }
]);
const activeYyList = ["参议院"];
const activeYyList = ref(["全部议院"]);
const pubTime = ref([
{ id: "全部时间", name: "全部时间" },
{ id: "2025年", name: "2025年" },
{ id: "2024年", name: "2024年" },
{ id: "2023年", name: "2023年" },
......@@ -1491,7 +1560,7 @@ const pubTime = ref([
{ id: "2021年", name: "2021年" },
{ id: "更早时间", name: "更早时间" }
]);
const activePubTime = ref(["2025年"]);
const activePubTime = ref(["全部时间"]);
const footerSelectList1 = ref([
{
......@@ -1549,8 +1618,9 @@ onMounted(async () => {
handleBox5(); //涉华法案统计
handleBox6(); // 关键条款
const box7Chart = getDoublePieChart(box7Data.value[0], box7Data.value[1]);
setChart(box7Chart, "box7Chart");
handleBox7Data();
handleBox8Data();
await handleGetHotBills();
curBill.value = hotBillList.value[0];
......@@ -1929,15 +1999,14 @@ onUnmounted(() => {});
padding-left: 31px;
.box1-main-left {
margin-left: 39px;
// flex: 1;
width: 484px;
.box1-main-left-title {
height: 22px;
color: rgba(20, 89, 187, 1);
font-family: Microsoft YaHei;
font-family: "Microsoft YaHei";
font-size: 20px;
font-weight: 700;
line-height: 22px;
margin-bottom: 14px;
}
.box1-main-left-info {
margin-top: 17px;
......@@ -2549,6 +2618,7 @@ onUnmounted(() => {});
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
background: rgba(255, 255, 255, 1);
position: relative;
.box5-header {
height: 53px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
......@@ -2605,6 +2675,12 @@ onUnmounted(() => {});
.box5-main {
height: 397px;
}
.box5-select {
position: absolute;
top: 50px;
left: 100px;
z-index: 100;
}
}
.box6 {
margin-left: 20px;
......@@ -2615,7 +2691,7 @@ onUnmounted(() => {});
background: rgba(255, 255, 255, 1);
.box6-header {
width: 521px;
height: 48px;
height: 53px;
border-bottom: 1px solid rgba(240, 242, 244, 1);
display: flex;
box-sizing: border-box;
......@@ -2665,8 +2741,8 @@ onUnmounted(() => {});
}
}
.box6-main {
width: 452px;
height: 402px;
width: 100%;
height: calc(100% - 53px);
}
}
}
......@@ -2816,6 +2892,8 @@ onUnmounted(() => {});
}
.box8-main {
height: 340px;
overflow-y: auto;
overflow-x: hidden;
.box8-main-item {
margin: 0 auto;
width: 478px;
......@@ -2833,9 +2911,10 @@ onUnmounted(() => {});
position: relative;
width: 42px;
height: 42px;
img {
width: 100%;
height: 100%;
.left-img {
width: 42px;
height: 42px;
border-radius: 50%;
}
.left-icon1 {
position: absolute;
......@@ -2892,11 +2971,10 @@ onUnmounted(() => {});
position: absolute;
top: 0;
right: 10px;
width: 92px;
height: 51px;
text-align: right;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-family: "Microsoft YaHei";
font-size: 16px;
font-weight: 400;
line-height: 51px;
......@@ -3051,7 +3129,7 @@ onUnmounted(() => {});
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
.select-box {
margin-top: 21px;
margin-top: 20px;
.select-box-header {
display: flex;
gap: 17px;
......@@ -3063,18 +3141,35 @@ onUnmounted(() => {});
border-radius: 0 4px 4px 0;
}
.title {
height: 24px;
// height: 24px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 16px;
font-size: 20px;
font-weight: 700;
line-height: 24px;
line-height: 26px;
letter-spacing: 1px;
text-align: left;
}
}
.select-main {
margin-left: 25px;
margin-top: 16px;
.checkbox-group {
display: flex;
flex-wrap: wrap;
.filter-checkbox {
width: 50%;
margin-right: 0;
margin-bottom: 4px;
:deep(.el-checkbox__label) {
color: rgb(95, 101, 108);
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
}
}
}
}
.select-main1 {
width: 100px;
......
import * as echarts from 'echarts'
const getMultiLineChart = (dataX, dataY1, dataY2) => {
const getMultiLineChart = (dataX, dataY1, dataY2, dataY3) => {
return {
tooltip: {
trigger: 'axis',
......@@ -9,25 +9,34 @@ const getMultiLineChart = (dataX, dataY1, dataY2) => {
label: {
backgroundColor: '#6a7985'
}
},
formatter: function (params) {
let res = params[0].name + '<br/>';
params.forEach(item => {
res += item.marker + item.seriesName + ': ' + item.value + (item.seriesName === '通过率' ? '%' : '') + '<br/>';
});
return res;
}
},
grid: {
top: '8%',
top: '15%',
right: '5%',
bottom: '5%',
left: '5%',
containLabel: true
},
legend: {
data: ['提出法案', '通过法案'],
data: ['提出法案', '通过法案', '通过率'],
show: true,
top: 0,
icon: 'circle',
textStyle: {
color: 'rgba(95, 101, 108, 1)',
fontFamily: 'Microsoft YaHei',
fontSize: '16px',
fontSize: '14px',
}
},
color: ['#1459bb', '#fa8c16'],
color: ['#1677FF', '#FA8C16', '#D9001B'],
xAxis: [
{
type: 'category',
......@@ -37,47 +46,83 @@ const getMultiLineChart = (dataX, dataY1, dataY2) => {
],
yAxis: [
{
type: 'value'
type: 'value',
position: 'left',
axisLabel: {
color: '#666'
}
},
{
type: 'value',
position: 'right',
min: 0,
max: 100,
interval: 20,
axisLabel: {
formatter: '{value}%',
color: '#666'
},
splitLine: {
show: false
}
}
],
series: [
{
name: '提出法案',
type: 'line',
symbol: 'emptyCircle',
symbolSize: 6,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(22, 119, 255, 1)' // 起始颜色
color: 'rgba(22, 119, 255, 0.4)' // 起始颜色
}, {
offset: 1,
color: 'rgba(22, 119, 255, 0)' // 结束颜色
}])
},
emphasis: {
focus: 'series'
itemStyle: {
color: '#1677FF'
},
data: dataY1
},
{
name: '通过法案',
type: 'line',
symbol: 'emptyCircle',
symbolSize: 6,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(255, 172, 77, 1)' // 起始颜色
color: 'rgba(250, 140, 22, 0.4)' // 起始颜色
}, {
offset: 1,
color: 'rgba(255, 172, 77, 0)' // 结束颜色
color: 'rgba(250, 140, 22, 0)' // 结束颜色
}])
},
emphasis: {
focus: 'series'
itemStyle: {
color: '#FA8C16'
},
data: dataY2
},
{
name: '通过率',
type: 'line',
yAxisIndex: 1,
symbol: 'emptyCircle',
symbolSize: 4,
lineStyle: {
type: 'dashed',
width: 2
},
itemStyle: {
color: '#D9001B'
},
data: dataY3
}
]
}
}
export default getMultiLineChart
\ No newline at end of file
export default getMultiLineChart
......@@ -11,6 +11,8 @@ const getWordCloudChart = (data) => {
series: [
{
type: "wordCloud",
width: '80%',
height: '80%',
shape: "rect", //
// 其他形状你可以使用形状路径
// 或者自定义路径
......
......@@ -35,7 +35,7 @@
<div class="box2">
<div class="box-header">
<div class="header-left"></div>
<div class="title">辩论投票时长</div>
<div class="title">修正案次数分析</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" />
......@@ -320,7 +320,7 @@
<script setup>
import { ref, onMounted } from "vue";
import { getBillTimeAnalyze, getBillXj, getBillTotalXj, getBillTp } from "@/api/deepdig";
import { getBillTimeAnalyze, getBillAmeAnalyzeCount, getBillTp } from "@/api/deepdig";
import getBoxPlotChcart from "./utils/boxplot";
import * as echarts from "echarts";
......@@ -563,20 +563,69 @@ const handleGetBillTimeAnalyze = async () => {
return item.lcmc;
});
chartData1.value.dataY = res.data.map(item => {
return [item.zdhs, item.zshs, item.pjs, item.pjx, item.zws, item.dysc];
return [item.zshs, item.pjx, item.zws, item.pjs, item.zdhs, item.dysc];
});
} catch (error) {}
};
// 辩论投票时长
// 修正案次数分析
const handleGetBillAmeAnalyzeCount = async () => {
const params = {
id: window.sessionStorage.getItem("billId")
};
try {
const res = await getBillAmeAnalyzeCount(params);
console.log("修正案次数分析", res);
chartData2.value.dataX = res.data.map(item => {
return item.lcmc;
});
chartData2.value.dataY = res.data.map(item => {
// 兼容多种字段名,防止字段不存在导致图表消失
// 顺序:最小(min), Q1(小), 中位数(median), Q3(大), 最大(max), 该法案(current)
return [
item.zsCount || item.zscs || item.zshs || 0,
item.jsx || item.pjx || 0,
item.zws || item.zwcs || 0,
item.pjs || item.pjcs || 0,
item.zdCount || item.zdcs || item.zdhs || 0,
item.dysc || item.dycs || 0
];
});
} catch (error) {
console.error("修正案次数分析 error", error);
}
};
// 获取投票分析
const handleGetBillVoteAnalyze = async () => {
const params = {
id: window.sessionStorage.getItem("billId")
};
try {
const res = await getBillTp(params);
console.log("投票分析", res);
} catch (error) {
console.error("投票分析 error", error);
}
};
onMounted(async () => {
await handleGetBillTimeAnalyze();
await handleGetBillAmeAnalyzeCount();
await handleGetBillVoteAnalyze();
let chart1 = getBoxPlotChcart(chartData1.value, "天");
setChart(chart1, "chart1");
let chart2 = getBoxPlotChcart(chartData2.value, "小时");
const countLabels = {
max: '最大次数',
q3: '平均次数大',
median: '次数中位数',
q1: '平均次数小',
min: '最小次数',
current: '该法案修正案数量'
};
let chart2 = getBoxPlotChcart(chartData2.value, "次", countLabels);
setChart(chart2, "chart2");
});
</script>
......
const getBoxPlotChcart = (data,unit) => {
const getBoxPlotChcart = (data, unit, labelConfig = {}) => {
const labels = {
max: labelConfig.max || '最大耗时',
q3: labelConfig.q3 || '平均耗时大',
median: labelConfig.median || '耗时中位数',
q1: labelConfig.q1 || '平均耗时小',
min: labelConfig.min || '最小耗时',
current: labelConfig.current || '该法案耗时'
};
let option = {
// title: [
// {
......@@ -11,6 +20,22 @@ const getBoxPlotChcart = (data,unit) => {
trigger: 'item',
axisPointer: {
type: 'shadow'
},
formatter: function (params) {
if (params.seriesType === 'scatter') {
return `${params.name}<br/>${labels.current}: ${params.data[1]} ${unit}`;
}
const { name, data } = params;
let tip = `${name}<br/>`;
tip += `${labels.max}: ${data[5]} ${unit}<br/>`;
tip += `${labels.q3}: ${data[4]} ${unit}<br/>`;
tip += `${labels.median}: ${data[3]} ${unit}<br/>`;
tip += `${labels.q1}: ${data[2]} ${unit}<br/>`;
tip += `${labels.min}: ${data[1]} ${unit}<br/>`;
if (data[6] !== undefined && data[6] !== null) {
tip += `${labels.current}: ${data[6]} ${unit}`;
}
return tip;
}
},
grid: {
......@@ -48,13 +73,20 @@ const getBoxPlotChcart = (data,unit) => {
{
name: 'boxplot',
type: 'boxplot',
datasetIndex: 1,
data: data.dataY,
},
{
name: 'outlier',
name: labels.current,
type: 'scatter',
datasetIndex: 2
data: data.dataY.map((item, index) => [index, item[5]]),
itemStyle: {
color: '#ff5722'
},
tooltip: {
formatter: function (params) {
return `${params.name}<br/>${labels.current}: ${params.data[1]} ${unit}`;
}
}
}
]
}
......
<template>
<div class="container">
<div class="svg-timeline">
<svg :width="svgWidth" :height="svgHeight">
<svg :viewBox="`0 0 ${svgWidth} ${svgHeight}`" width="100%" height="100%">
<!-- <line
:x1="lines[0].x1 - 100"
:y1="lines[0].y1"
......@@ -31,10 +31,11 @@
stroke-width="2"
/> -->
<line
:x1="lines[0].x1 - 100"
:y1="lines[0].y1"
:x2="lines[0].x1"
:y2="lines[0].y1"
v-if="nodes.length > 0"
:x1="nodes[0].x - 100"
:y1="nodes[0].y"
:x2="nodes[0].x"
:y2="nodes[0].y"
stroke="#e8f2ff"
stroke-width="2"
marker-end="url(#arrow)"
......@@ -68,51 +69,60 @@
v-for="(node, idx) in nodes.slice(0, nodes.length)"
:key="'line' + idx"
:x1="node.x"
:y1="node.y + 2"
:y1="node.y + 4"
:x2="node.x"
:y2="node.dyms ? node.y + 80 : node.y + 50"
:y2="node.y + verticalLineLength"
stroke="#1677ff"
stroke-width="2"
:stroke-width="verticalLineWidth"
/>
<text
<foreignObject
v-for="(node, idx) in nodes"
:key="'actionDate' + idx"
:x="node.x + 10"
:y="node.y + 30"
text-anchor="start"
fill="#1677ff"
font-size="14"
font-weight="600"
:key="'fo-' + idx"
:x="node.x + 15"
:y="node.y + 5"
:width="nodeGapX - 30"
height="100"
style="overflow: visible;"
>
{{ node.actionDate }}
</text>
<div class="node-content" xmlns="http://www.w3.org/1999/xhtml">
<div class="date">{{ node.formattedDate }}</div>
<div class="title">{{ node.actionTitle }}</div>
<div class="votes" v-if="node.voteString">{{ node.voteString }}</div>
</div>
</foreignObject>
<text
v-for="(node, idx) in nodes"
:key="'actionTitle' + idx"
:x="node.x + 10"
:y="node.y + 50"
text-anchor="start"
fill="#3b414b"
font-size="14"
textLength="170"
lengthAdjust="spacing"
font-weight="bold"
v-if="startMonth && nodes.length > 0"
:x="nodes[0].x - 110"
:y="nodes[0].y + 5"
text-anchor="end"
fill="rgb(5, 95, 194)"
font-size="16"
font-weight="700"
>
{{ node.actionTitle.slice(0,24) }}
{{ startMonth }}
</text>
<line
v-if="nodes.length > 0"
:x1="nodes[nodes.length - 1].x"
:y1="nodes[nodes.length - 1].y"
:x2="nodes[nodes.length - 1].row % 2 === 0 ? nodes[nodes.length - 1].x + 100 : nodes[nodes.length - 1].x - 100"
:y2="nodes[nodes.length - 1].y"
stroke="#e8f2ff"
stroke-width="2"
marker-end="url(#arrow)"
/>
<text
v-for="(node, idx) in nodes"
:key="'actionTitle' + idx"
:x="node.x + 10"
:y="node.y + 70"
text-anchor="start"
fill="#84888e"
font-size="14"
v-if="endMonth && nodes.length > 0"
:x="nodes[nodes.length - 1].row % 2 === 0 ? nodes[nodes.length - 1].x + 110 : nodes[nodes.length - 1].x - 110"
:y="nodes[nodes.length - 1].y + 5"
:text-anchor="nodes[nodes.length - 1].row % 2 === 0 ? 'start' : 'end'"
fill="rgb(5, 95, 194)"
font-size="16"
font-weight="700"
>
{{ node.dyms ? node.dyms : "" }}
{{ endMonth }}
</text>
</svg>
</div>
......@@ -134,26 +144,55 @@ export default {
// ],
maxPerRow: 5,
nodeGapX: 200,
nodeGapY: 100
nodeGapY: 180,
leftMargin: 150,
verticalLineLength: 80,
verticalLineWidth: 1
};
},
computed: {
startMonth() {
if (this.sortedDataList.length === 0) return '';
const date = new Date(this.sortedDataList[0].actionDate);
return `${date.getFullYear()}${date.getMonth() + 1}月`;
},
endMonth() {
if (this.sortedDataList.length === 0) return '';
const date = new Date(this.sortedDataList[this.sortedDataList.length - 1].actionDate);
return `${date.getFullYear()}${date.getMonth() + 1}月`;
},
sortedDataList() {
if (!this.dataList) return [];
// Clone and sort by date ascending (Old -> New)
return [...this.dataList].sort((a, b) => new Date(a.actionDate) - new Date(b.actionDate));
},
nodes() {
// 计算每个节点的坐标(蛇形)
return this.dataList.map((item, idx) => {
return this.sortedDataList.map((item, idx) => {
const row = Math.floor(idx / this.maxPerRow);
const col = idx % this.maxPerRow;
let x, y;
const leftMargin = 10; // 你可以自定义这个值
// const leftMargin = 150; // 你可以自定义这个值
if (row % 2 === 0) {
x = leftMargin + col * this.nodeGapX + 50;
x = this.leftMargin + col * this.nodeGapX + 50;
} else {
x = leftMargin + (this.maxPerRow - 1 - col) * this.nodeGapX + 50;
x = this.leftMargin + (this.maxPerRow - 1 - col) * this.nodeGapX + 50;
}
// 节点纵坐标起始值
y = 60 + row * this.nodeGapY;
return { ...item, x, y, row };
// Format Date: 2025-07-04 -> 7月4日
const dateObj = new Date(item.actionDate);
const formattedDate = `${dateObj.getMonth() + 1}${dateObj.getDate()}日`;
// Format Votes
let voteString = '';
if (item.agreeVote !== null && item.disagreeVote !== null) {
voteString = `${item.agreeVote}票赞成 : ${item.disagreeVote}票反对`;
}
return { ...item, x, y, row, formattedDate, voteString };
});
},
lines() {
......@@ -194,7 +233,7 @@ export default {
console.log("prev", prev);
// 判断是否是行尾转折点
const isTurnPoint = i % 5 === 0;
const isTurnPoint = i % this.maxPerRow === 0;
if (isTurnPoint) {
// 计算半圆路径
......@@ -221,7 +260,7 @@ export default {
return path;
},
svgWidth() {
return this.maxPerRow * this.nodeGapX + 100;
return this.leftMargin + this.maxPerRow * this.nodeGapX + 50;
},
svgHeight() {
// SVG高度
......@@ -240,7 +279,7 @@ export default {
align-items: center;
}
.svg-timeline {
width: 1000px;
width: 100%;
// background-size: 100% 100%;
// position: relative;
// .title {
......@@ -264,5 +303,40 @@ export default {
// height: 24px;
// }
// }
.node-content {
font-family: Microsoft YaHei, sans-serif;
text-align: left;
padding-left: 4px;
.date {
color: rgb(5, 95, 194);
font-weight: 700;
font-size: 14px;
line-height: 22px;
margin-bottom: 0px;
margin-top: 6px;
}
.title {
color: rgb(59, 65, 75);
font-weight: 700;
font-size: 14px;
line-height: 22px;
margin-bottom: 0px;
white-space: nowrap; /* Keep text on one line */
overflow: hidden; /* Hide overflow */
text-overflow: ellipsis; /* Show ... for overflow */
width: 100%; /* Ensure it takes full width of container */
display: block; /* Block level for ellipsis to work */
}
.votes {
color: rgb(95, 101, 108);
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
}
}
</style>
......@@ -31,9 +31,10 @@
<div class="box1-right-item">
<div class="item-left">相关领域:</div>
<div class="item-right1">
<div class="right1-item">跨境电商</div>
<!-- <div class="right1-item">跨境电商</div>
<div class="right1-item">新能源产业</div>
<div class="right1-item">半导体产业</div>
<div class="right1-item">半导体产业</div> -->
<div class="right1-item" v-for="item in basicInfo.hylyList" :key="item">{{ item }}</div>
</div>
</div>
<div class="box1-right-item">
......@@ -81,7 +82,7 @@
<div class="box-header">
<div class="header-left"></div>
<div class="title">法案进展</div>
<div class="header-btn-box">
<!-- <div class="header-btn-box">
<div class="btn" @click="handleClcikBox2Btn(1)">
<el-badge :value="warningNum">
<el-button type="primary" plain v-if="box2BtnActive === 1">最新进展</el-button>
......@@ -92,7 +93,7 @@
<el-button type="primary" plain v-if="box2BtnActive === 2">前期进展</el-button>
<el-button type="info" plain v-else>前期进展</el-button>
</div>
</div>
</div> -->
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
......@@ -104,8 +105,8 @@
</div>
<div class="box2-main">
<div class="box2-main-center">
<STimeline v-if="box2BtnActive == 2" :dataList="timelineData" />
<div class="box2-center-item-box" v-if="box2BtnActive == 1">
<STimeline :dataList="timelineData" />
<!-- <div class="box2-center-item-box" v-if="box2BtnActive == 1">
<div class="box2-center-item" v-for="(item, index) in progressList" :key="index">
<div class="tip" :class="{ tipActive: item.fxdj }"></div>
<div class="date">{{ item.actionDate }}</div>
......@@ -128,7 +129,7 @@
<el-icon size="22" color="#777"><ArrowRightBold /></el-icon>
</div>
</div>
</div>
</div> -->
</div>
</div>
<div class="box2-footer">
......@@ -210,15 +211,15 @@
<div
class="tag-box"
:class="{
status0: tag.status === 0,
status1: tag.status === 1,
status2: tag.status === 2,
status3: tag.status === 3
status0: index === 0 || index === 4,
status1: index === 1 || index === 5,
status2: index === 2 || index === 6,
status3: index === 3 || index === 7
}"
v-for="(tag, index) in tagList"
v-for="(tag, index) in curPerson.tagList"
:key="index"
>
{{ tag.title }}
{{ tag }}
</div>
</div>
<div class="right-main-box3">
......@@ -231,13 +232,13 @@
<div class="right-main-box3-main">
<el-timeline style="max-width: 500px">
<el-timeline-item
:timestamp="item.sjsj"
:timestamp="item.newsDate"
placement="top"
v-for="(item, index) in personEventList"
v-for="(item, index) in curPerson.newsList"
:key="index"
>
<div class="timeline-content">
{{ item.sjnr }}
{{ item.newsContent }}
</div>
</el-timeline-item>
<!-- <el-timeline-item timestamp="2018/4/3" placement="top">
......@@ -324,14 +325,14 @@
<div class="inner-right-main">
<el-timeline style="max-width: 840px">
<el-timeline-item
:timestamp="item.sjsj"
:timestamp="item.newsDate"
placement="top"
v-for="(item, index) in personEventList"
v-for="(item, index) in curPerson.newsList"
:key="index"
>
<div class="timeline-content1">
<div class="text">
{{ item.sjnr }}
{{ item.newsContent }}
</div>
<div class="pic">
<img src="./assets/imgs/img1.png" alt="" />
......@@ -544,6 +545,7 @@ const handleGetBasicInfo = async () => {
const res = await getBillInfo(params);
console.log("基本信息", res);
basicInfo.value = res.data
basicInfo.value.stageList.reverse()
} catch (error) {
console.error(error);
}
......@@ -552,24 +554,24 @@ const handleGetBasicInfo = async () => {
const warningNum = ref(0);
// 法案进展 获取最新进展
const handleGetBillEvent = async () => {
warningNum.value = 0;
const params = {
id: window.sessionStorage.getItem("billId")
};
try {
const res = await getBillEvent(params);
console.log("最新进展", res);
progressList.value = res.data;
progressList.value.forEach(item => {
if (item.fxdj) {
warningNum.value++;
}
});
} catch (error) {
console.error(error);
}
};
// const handleGetBillEvent = async () => {
// warningNum.value = 0;
// const params = {
// id: window.sessionStorage.getItem("billId")
// };
// try {
// const res = await getBillEvent(params);
// console.log("最新进展", res);
// progressList.value = res.data;
// progressList.value.forEach(item => {
// if (item.fxdj) {
// warningNum.value++;
// }
// });
// } catch (error) {
// console.error(error);
// }
// };
// 法案进展 获取前期进展 --也是提出人左上角列表
const handleGetBillDyqk = async () => {
......@@ -601,9 +603,26 @@ const curPerson = ref({});
const personEventList = ref([]);
// 提出人 --动议id
const handleGetBillPerson = async id => {
// const handleGetBillPerson = async id => {
// const params = {
// id: id
// };
// try {
// const res = await getBillPerson(params);
// console.log("提出人", res);
// personList.value = res.data;
// box3BtnActive.value = res.data.length ? res.data[0].name : "";
// curPerson.value = res.data.length ? res.data[0] : {};
// personEventList.value = res.data.length ? res.data[0].eventList : [];
// } catch (error) {
// console.error(error);
// }
// };
// 法案提出人
const handleGetBillPerson = async () => {
const params = {
id: id
billId: window.sessionStorage.getItem("billId")
};
try {
const res = await getBillPerson(params);
......@@ -619,8 +638,9 @@ const handleGetBillPerson = async id => {
onMounted(() => {
handleGetBasicInfo();
handleGetBillEvent();
// handleGetBillEvent();
handleGetBillDyqk();
handleGetBillPerson();
});
</script>
......@@ -695,11 +715,11 @@ onMounted(() => {
.box1-right {
margin-left: 31px;
margin-top: 5px;
width: 623px;
// width: 623px;
height: 350px;
.box1-right-item {
display: flex;
margin-bottom: 24px;
margin-bottom: 21px;
.item-left {
width: 100px;
height: 14px;
......@@ -722,7 +742,23 @@ onMounted(() => {
}
.item-right1 {
display: flex;
align-items: center;
width: 700px;
height: 40px;
overflow-x: auto;
overflow-y: hidden;
&::-webkit-scrollbar {
height: 4px;
}
&::-webkit-scrollbar-thumb {
border-radius: 4px;
background: #e1e1e1;
}
&::-webkit-scrollbar-track {
background: transparent;
}
.right1-item {
flex-shrink: 0;
margin-right: 10px;
padding: 1px 8px;
box-sizing: border-box;
......@@ -774,10 +810,14 @@ onMounted(() => {
text-align: center;
position: relative;
.step-box {
padding: 0 10px 0 20px;
padding: 4px 10px;
color: #333;
position: relative;
font-size: 14px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 14px;
margin-left: 10px;
.right-arrow {
position: absolute;
right: -21px;
......@@ -792,7 +832,7 @@ onMounted(() => {
}
}
.step-box-active {
padding: 0 12px;
padding: 4 10px;
color: #fff;
background: #ce4f51;
position: relative;
......@@ -851,11 +891,13 @@ onMounted(() => {
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
.box2-main {
margin-top: 10px;
height: calc(100% - 70px); // Subtract header height
width: 100%;
.box2-main-center {
margin-left: 23px;
border-top: 1px solid rgba(243, 243, 244, 1);
// width: 100%;
height: 300px;
width: calc(100% - 46px); // Subtract margin
height: 100%;
// background: orange;
.box2-center-item {
display: flex;
......@@ -1082,10 +1124,10 @@ onMounted(() => {
}
}
.right-main-box2 {
width: 576px;
height: 150px;
// width: 576px;
// height: 150px;
box-sizing: border-box;
padding: 10px 26px;
padding: 22px 26px;
// border-bottom: 1px solid rgb(243, 243, 244);
// display: flex;
// flex-wrap: wrap;
......@@ -1140,7 +1182,7 @@ onMounted(() => {
}
.right-main-box3-main {
margin-top: 18px;
height: 299px;
height: 412px;
overflow-y: auto;
}
.right-main-box3-footer {
......@@ -1330,6 +1372,10 @@ onMounted(() => {
color: var(--btn-active-text-color);
}
}
.inner-right-main {
height: 860px;
overflow: auto;
}
}
}
}
......
......@@ -16,9 +16,15 @@
</div>
<div class="left-top">
<el-select v-model="curBill" placeholder="请选择" style="width: 240px" @change="handleChangeBill">
<el-option v-for="item in billList" :key="item.value" :label="item.label" :value="item.id" />
<el-option v-for="item in billList" :key="item.id" :label="item.label" :value="item.value" />
</el-select>
<el-checkbox style="margin-left: 30px" v-model="checkedValue" label="只看涉华条款" size="large" />
<el-checkbox
style="margin-left: 30px"
v-model="checkedValue"
label="只看涉华条款"
size="large"
@change="handleChangeCheckbox"
/>
<div class="search" style="width: 240px; margin-left: 475px">
<el-input v-model="searchValue" style="width: 240px" placeholder="搜索条款" />
<div class="icon">
......@@ -28,32 +34,32 @@
</div>
<div class="left-main">
<div class="left-main-item" v-for="(term, index) in mainTermsList" :key="index">
<div class="id">{{ index + 1 }}</div>
<div class="id">{{ (currentPage - 1) * pageSize + index + 1 }}</div>
<div class="info">
<div class="title">
<span class="title-active">{{ term.header }}</span>
{{ term.title }}
<span class="title-active">{{ term.tkxh }}条.</span>
{{ term.fynr }}
</div>
<div class="content">
<span class="content-active">{{ term.headerEn }}</span>
{{ term.content }}
<span class="content-active">Sec.{{ term.tkxh }}</span>
{{ term.ywnr }}
</div>
</div>
<div class="tags-box">
<div
class="tag"
v-for="(val, idx) in term.tags"
v-for="(val, idx) in (term.hylyList || []).slice(0, 2)"
:key="idx"
:class="{
tag1: val.status === 1,
tag2: val.status === 2,
tag3: val.status === 3,
tag4: val.status === 4,
tag5: val.status === 5,
tag6: val.status === 6
'tag1': val === '人工智能',
'tag2': val === '新一代信息技术' || !['人工智能', '政治', '经济', '军事', '科技'].includes(val),
'tag3': val === '政治',
'tag4': val === '经济',
'tag5': val === '军事',
'tag6': val === '科技'
}"
>
{{ val.name }}
{{ val }}
</div>
</div>
<div class="open">
......@@ -63,10 +69,17 @@
</div>
<div class="left-footer">
<div class="left-footer-text">
{{ "共96条涉华条款" }}
{{ `共${total}条${checkedValue ? "涉华" : ""}条款` }}
</div>
<div class="left-footer-right">
<el-pagination background layout="prev, pager, next" :total="96" />
<el-pagination
background
layout="prev, pager, next"
:total="total"
v-model:current-page="currentPage"
v-model:page-size="pageSize"
@current-change="handleCurrentChange"
/>
</div>
</div>
</div>
......@@ -137,16 +150,12 @@ import { getBillContentId, getBillContentTk, getBillContentXzfs, getBillHyly } f
const curBill = ref("");
const curBillId = ref(null);
const billList = ref([
{
value: "公法(2025年7月4日)",
label: "公法(2025年7月4日)"
},
{
value: "公法(2025年7月8日)",
label: "公法(2025年7月8日)"
}
]);
const checkedValue = ref(false);
const searchValue = ref("");
const billList = ref([]);
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(0);
const mainTermsList = ref([
{
......@@ -351,7 +360,13 @@ const setChart = (option, chartId) => {
// 切换原文
const handleChangeBill = val => {
curBillId.value = val;
curBill.value = val;
const item = billList.value.find(item => item.value === val);
if (item) {
curBillId.value = item.id;
currentPage.value = 1;
handleGetBillContentTk(checkedValue.value ? "Y" : "N");
}
};
// 获取法案id列表
......@@ -369,22 +384,69 @@ const handleGetBillList = async () => {
id: item.ywid
};
});
curBill.value = billList.value[0].label;
curBillId.value = billList.value[0].id;
if (billList.value.length > 0) {
curBill.value = billList.value[0].value;
curBillId.value = billList.value[0].id;
}
} catch (error) {}
};
const handleChangeCheckbox = val => {
currentPage.value = 1;
handleGetBillContentTk(val ? "Y" : "N");
};
const handleCurrentChange = val => {
currentPage.value = val;
handleGetBillContentTk(checkedValue.value ? "Y" : "N");
};
// 根据原文ID获取条款列表
const handleGetBillContentTk = async cRelated => {
const params = {
id: curBillId.value,
billid: window.sessionStorage.getItem("billId"),
id: curBill.value,
cRelated: cRelated,
currentPage: 0,
pageSize: 10
currentPage: currentPage.value - 1,
pageSize: pageSize.value
};
try {
const res = await getBillContentTk(params);
console.log("条款内容", res);
mainTermsList.value = res.data.content.map(item => {
// 处理 fynr
if (item.fynr) {
const matchComplex = item.fynr.match(/^(?:正文内容[::]\s*)?[“"]?\s*第\s*([0-9a-zA-Z]+)\s*[条节][::\.\。]?[”"]?\s*/);
if (matchComplex) {
// 匹配 "第xxx条"、"正文内容:第xxx条"、"“第xxx条" 等
if (!item.tkxh) {
item.tkxh = matchComplex[1];
}
item.fynr = item.fynr.replace(matchComplex[0], "");
} else {
// 匹配 "xxx."
item.fynr = item.fynr.replace(/^\d+\.\s*/, "");
}
}
// 处理 ywnr
if (item.ywnr) {
const matchSec = item.ywnr.match(/^(?:SEC\.|SECTION)\s*([0-9a-zA-Z]+)[\.:]?\s*/i);
if (matchSec) {
if (!item.tkxh) {
item.tkxh = matchSec[1];
}
item.ywnr = item.ywnr.replace(matchSec[0], "");
} else {
item.ywnr = item.ywnr.replace(/^\d+\.\s*/, "");
}
}
// 处理 tkxh 末尾的点
if (item.tkxh) {
item.tkxh = item.tkxh.replace(/\.$/, "");
}
return item;
});
total.value = res.data.totalElements;
} catch (error) {}
};
......@@ -430,7 +492,7 @@ const handleGetBillHyly = async () => {
onMounted(async () => {
await handleGetBillList();
handleGetBillContentTk(false);
handleGetBillContentTk("N");
await handleGetBillContentXzfs();
await handleGetBillHyly();
let chart1 = getPieChart(chart1Data.value, chart1ColorList.value);
......@@ -528,6 +590,7 @@ onMounted(async () => {
border-radius: 2px;
background: rgba(255, 255, 255, 1);
display: flex;
position: relative;
.id {
margin-top: 20px;
margin-left: 15px;
......@@ -543,7 +606,7 @@ onMounted(async () => {
.info {
margin-left: 13px;
margin-top: 15px;
width: 813px;
width: 780px;
.title {
height: 14px;
color: rgba(59, 65, 75, 1);
......@@ -577,20 +640,19 @@ onMounted(async () => {
}
}
.tags-box {
margin-left: 20px;
margin-top: 21px;
width: 160px;
height: 22px;
display: flex;
justify-content: flex-end;
justify-content: right;
align-items: center;
flex: 1;
margin-right: 50px;
.tag {
height: 18px;
text-align: right;
line-height: 18px;
padding: 0 8px;
padding: 1px 8px;
border-radius: 4px;
margin-left: 5px;
font-size: 12px;
font-family: Microsoft YaHei;
font-family: "Microsoft YaHei";
font-size: 14px;
font-weight: 400;
}
......@@ -626,8 +688,9 @@ onMounted(async () => {
}
}
.open {
margin-left: 10px;
margin-top: 22px;
position: absolute;
top: 22px;
right: 23px;
width: 20px;
height: 20px;
img {
......
......@@ -4,6 +4,7 @@ const getPieChart = (data,colorList) => {
series: [
{
type: 'pie',
minAngle: 28,
radius: [70, 100],
height: '100%',
left: 'center',
......@@ -38,7 +39,9 @@ const getPieChart = (data,colorList) => {
? params.labelRect.x
: params.labelRect.x + params.labelRect.width;
return {
labelLinePoints: points
labelLinePoints: points,
hideOverlap: false,
moveOverlap: 'shiftY'
};
},
data: data
......
<template>
<div class="fishbone">
<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-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-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="left-bone-item" v-for="(item, index) in getLeftItems(causeGroup.causes)" :key="index">
<!-- <div class="icon">
<img :src="item.picture" alt="" />
</div> -->
<div class="text">{{ item.name }}</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="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>
</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 class="text">{{ item.name }}</div>
<!-- <div class="icon">
<img :src="item.picture" :alt="item.name" />
</div> -->
</div>
</div>
</div>
......@@ -474,12 +27,39 @@
<script setup>
import { getChainFishbone } from "@/api/exportControl";
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 () => {
try {
const chainFishboneData = await getChainFishbone(chainId.value);
chainFishbone.value = chainFishboneData ?? [];
fishboneData.value = chainFishboneData ?? {
text: "",
causes: []
};
console.log("鱼骨图数据:", fishboneData.value);
} catch (error) {
console.log(error);
}
......@@ -487,11 +67,14 @@ onMounted(async () => {
</script>
<style lang="scss" scoped>
/* ... 原有的样式保持不变 ... */
.fishbone {
position: relative;
width: 100%;
height: 100%;
margin-top: 40px;
overflow-x: auto;
.main-line {
position: absolute;
top: 280px;
......@@ -501,6 +84,7 @@ onMounted(async () => {
background: rgba(174, 208, 255, 1);
}
}
.top-bone {
position: absolute;
top: 20px;
......@@ -517,13 +101,11 @@ onMounted(async () => {
left: -150px;
width: 150px;
height: 260px;
// background: orange;
.left-bone-item {
transform: skew(-30deg);
height: 35px;
margin-bottom: 5px;
margin-top: 15px;
// background: #fff;
display: flex;
justify-content: flex-end;
.icon {
......@@ -537,8 +119,11 @@ onMounted(async () => {
}
.text {
margin-left: 4px;
height: 35px;
height: 70px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
......@@ -556,7 +141,6 @@ onMounted(async () => {
right: -150px;
width: 150px;
height: 260px;
// background: pink;
.right-bone-item {
transform: skew(-30deg);
height: 35px;
......@@ -564,7 +148,6 @@ onMounted(async () => {
margin-top: 5px;
display: flex;
justify-content: flex-start;
.line {
margin-right: 7px;
margin-top: 16px;
......@@ -572,11 +155,14 @@ onMounted(async () => {
height: 2px;
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.icon {
margin-top: 7px;
......@@ -590,6 +176,7 @@ onMounted(async () => {
}
}
}
.top-bone1 {
position: absolute;
top: 20px;
......@@ -606,13 +193,11 @@ onMounted(async () => {
left: -150px;
width: 150px;
height: 260px;
// background: orange;
.left-bone-item {
transform: skew(-30deg);
height: 35px;
margin-bottom: 5px;
margin-top: 15px;
// background: #fff;
display: flex;
justify-content: flex-end;
.icon {
......@@ -625,9 +210,13 @@ onMounted(async () => {
}
}
.text {
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
......@@ -645,7 +234,6 @@ onMounted(async () => {
right: -150px;
width: 150px;
height: 260px;
// background: pink;
.right-bone-item {
transform: skew(-30deg);
height: 35px;
......@@ -653,7 +241,6 @@ onMounted(async () => {
margin-top: 5px;
display: flex;
justify-content: flex-start;
.line {
margin-right: 7px;
margin-top: 16px;
......@@ -661,11 +248,14 @@ onMounted(async () => {
height: 2px;
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.icon {
margin-top: 7px;
......@@ -679,6 +269,7 @@ onMounted(async () => {
}
}
}
.top-bone2 {
position: absolute;
top: 20px;
......@@ -695,13 +286,11 @@ onMounted(async () => {
left: -150px;
width: 150px;
height: 260px;
// background: orange;
.left-bone-item {
transform: skew(-30deg);
height: 35px;
margin-bottom: 5px;
margin-top: 15px;
// background: #fff;
display: flex;
justify-content: flex-end;
.icon {
......@@ -714,9 +303,13 @@ onMounted(async () => {
}
}
.text {
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
......@@ -734,7 +327,6 @@ onMounted(async () => {
right: -150px;
width: 150px;
height: 260px;
// background: pink;
.right-bone-item {
transform: skew(-30deg);
height: 35px;
......@@ -742,7 +334,6 @@ onMounted(async () => {
margin-top: 5px;
display: flex;
justify-content: flex-start;
.line {
margin-right: 7px;
margin-top: 16px;
......@@ -750,11 +341,14 @@ onMounted(async () => {
height: 2px;
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.icon {
margin-top: 7px;
......@@ -768,6 +362,7 @@ onMounted(async () => {
}
}
}
.bottom-bone {
position: absolute;
top: 280px;
......@@ -784,13 +379,11 @@ onMounted(async () => {
left: -150px;
width: 150px;
height: 260px;
// background: orange;
.left-bone-item {
transform: skew(30deg);
height: 35px;
margin-bottom: 5px;
margin-top: 15px;
// background: #fff;
display: flex;
justify-content: flex-end;
.icon {
......@@ -803,9 +396,13 @@ onMounted(async () => {
}
}
.text {
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
......@@ -823,7 +420,6 @@ onMounted(async () => {
right: -150px;
width: 150px;
height: 260px;
// background: pink;
.right-bone-item {
transform: skew(30deg);
height: 35px;
......@@ -831,7 +427,6 @@ onMounted(async () => {
margin-top: 5px;
display: flex;
justify-content: flex-start;
.line {
margin-right: 7px;
margin-top: 16px;
......@@ -839,11 +434,14 @@ onMounted(async () => {
height: 2px;
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.icon {
margin-top: 7px;
......@@ -857,6 +455,7 @@ onMounted(async () => {
}
}
}
.bottom-bone1 {
position: absolute;
top: 280px;
......@@ -873,13 +472,11 @@ onMounted(async () => {
left: -150px;
width: 150px;
height: 260px;
// background: orange;
.left-bone-item {
transform: skew(30deg);
height: 35px;
margin-bottom: 5px;
margin-top: 15px;
// background: #fff;
display: flex;
justify-content: flex-end;
.icon {
......@@ -892,9 +489,13 @@ onMounted(async () => {
}
}
.text {
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
......@@ -912,7 +513,6 @@ onMounted(async () => {
right: -150px;
width: 150px;
height: 260px;
// background: pink;
.right-bone-item {
transform: skew(30deg);
height: 35px;
......@@ -920,7 +520,6 @@ onMounted(async () => {
margin-top: 5px;
display: flex;
justify-content: flex-start;
.line {
margin-right: 7px;
margin-top: 16px;
......@@ -928,11 +527,14 @@ onMounted(async () => {
height: 2px;
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.icon {
margin-top: 7px;
......@@ -946,6 +548,7 @@ onMounted(async () => {
}
}
}
.bottom-bone2 {
position: absolute;
top: 280px;
......@@ -962,13 +565,11 @@ onMounted(async () => {
left: -150px;
width: 150px;
height: 260px;
// background: orange;
.left-bone-item {
transform: skew(30deg);
height: 35px;
margin-bottom: 5px;
margin-top: 15px;
// background: #fff;
display: flex;
justify-content: flex-end;
.icon {
......@@ -981,9 +582,13 @@ onMounted(async () => {
}
}
.text {
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.line {
margin-left: 7px;
......@@ -1001,7 +606,6 @@ onMounted(async () => {
right: -150px;
width: 150px;
height: 260px;
// background: pink;
.right-bone-item {
transform: skew(30deg);
height: 35px;
......@@ -1009,7 +613,6 @@ onMounted(async () => {
margin-top: 5px;
display: flex;
justify-content: flex-start;
.line {
margin-right: 7px;
margin-top: 16px;
......@@ -1017,11 +620,14 @@ onMounted(async () => {
height: 2px;
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.icon {
margin-top: 7px;
......
[
{
"id": null,
"name": "2025-10-09",
"count": 10
},
{
"id": null,
"name": "2025-10-08",
"count": 8
},
{
"id": null,
"name": "2025-09-16",
"count": 16
"yearDomainCount": [
{
"sanTypeName": null,
"year": 2020,
"domainCountInfo": [
{
"year": 2020,
"id": "1",
"name": "人工智能",
"count": 11
},
{
"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>
<div class="fishbone">
<div class="main-line"></div>
<div v-for="(causeGroup, groupIndex) in fishboneData.causes" :key="groupIndex" :class="getBoneClass(groupIndex)">
<div class="left-bone">
<div class="left-bone-item" v-for="(item, index) in getLeftItems(causeGroup.causes)" :key="index">
<!-- <div class="icon">
<img :src="item.picture" alt="" />
</div> -->
<div class="text">{{ item.name }}</div>
<div class="line"></div>
<div class="fishbone-wrapper">
<div class="fishbone-scroll-container" ref="scrollContainerRef">
<div class="fishbone" ref="fishboneRef" v-if="fishboneData.length > 0">
<div class="main-line" :style="{ width: (fishboneData.length / 2) * 340 - 275 + 'px' }"></div>
<!-- 奇数索引的数据组放在上方 -->
<div
v-for="(causeGroup, groupIndex) in getOddGroups(fishboneData)"
:key="'top-' + groupIndex"
:class="getTopBoneClass(groupIndex)"
: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 class="right-bone">
<div class="right-bone-item" v-for="(item, index) in getRightItems(causeGroup.causes)" :key="index">
<div class="line"></div>
<div class="text">{{ item.name }}</div>
<!-- <div class="icon">
<img :src="item.picture" :alt="item.name" />
</div> -->
<!-- 偶数索引的数据组放在下方 -->
<div
v-for="(causeGroup, groupIndex) in getEvenGroups(fishboneData)"
:key="'bottom-' + groupIndex"
:class="getBottomBoneClass(groupIndex)"
:style="{ left: groupIndex * 300 + 200 + 'px' }"
>
<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 v-else style="display: flex; justify-content: center; align-items: center; height: 200px; width: 100%">
<el-empty description="暂无相关数据" />
</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>
</template>
<script setup>
import { getChainFishbone } from "@/api/exportControl";
import { onMounted, ref } from "vue";
const chainId = ref(1);
const fishboneData = ref({
text: "",
causes: []
import { onMounted, ref, nextTick, watch } from "vue";
// 这儿需要接收父组件传递来的产业链ID
const props = defineProps({
chainId: {
type: Number,
default: 1
}
});
// 根据索引确定鱼骨图位置类名
const getBoneClass = index => {
const positions = ["top-bone", "top-bone1", "top-bone2", "bottom-bone", "bottom-bone1", "bottom-bone2"];
return positions[index] || "top-bone";
// const chainId = ref(1);
const fishboneData = ref([]);
const scrollContainerRef = ref(null);
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 => {
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 () => {
try {
const chainFishboneData = await getChainFishbone(chainId.value);
fishboneData.value = chainFishboneData ?? {
text: "",
causes: []
};
const chainFishboneData = await getChainFishbone(props.chainId);
fishboneData.value = chainFishboneData?.causes ?? [];
// 等待DOM更新后检查是否需要滚动
nextTick(() => {
if (scrollContainerRef.value && fishboneRef.value) {
showScrollIndicator.value = fishboneRef.value.scrollWidth > scrollContainerRef.value.clientWidth;
updateScrollState();
}
});
console.log("鱼骨图数据:", fishboneData.value);
} catch (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>
<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 {
position: relative;
width: 100%;
width: fit-content;
height: 100%;
margin-top: 40px;
min-width: 100%;
padding-left: 275px;
.main-line {
position: absolute;
top: 280px;
right: 0;
width: 888px;
margin-top: 280px;
width: 1888px;
height: 3px;
background: rgba(174, 208, 255, 1);
}
......@@ -106,15 +266,6 @@ onMounted(async () => {
margin-top: 15px;
display: flex;
justify-content: flex-end;
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.text {
margin-left: 4px;
height: 70px;
......@@ -154,7 +305,7 @@ onMounted(async () => {
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
......@@ -162,15 +313,6 @@ onMounted(async () => {
text-overflow: ellipsis;
white-space: nowrap;
}
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
}
}
}
......@@ -198,17 +340,8 @@ onMounted(async () => {
margin-top: 15px;
display: flex;
justify-content: flex-end;
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.text {
width: 100px;
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
......@@ -247,7 +380,7 @@ onMounted(async () => {
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
......@@ -255,15 +388,6 @@ onMounted(async () => {
text-overflow: ellipsis;
white-space: nowrap;
}
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
}
}
}
......@@ -291,17 +415,8 @@ onMounted(async () => {
margin-top: 15px;
display: flex;
justify-content: flex-end;
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.text {
width: 100px;
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
......@@ -340,7 +455,7 @@ onMounted(async () => {
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
......@@ -348,15 +463,6 @@ onMounted(async () => {
text-overflow: ellipsis;
white-space: nowrap;
}
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
}
}
}
......@@ -384,17 +490,8 @@ onMounted(async () => {
margin-top: 15px;
display: flex;
justify-content: flex-end;
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.text {
width: 100px;
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
......@@ -433,7 +530,7 @@ onMounted(async () => {
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
......@@ -441,15 +538,6 @@ onMounted(async () => {
text-overflow: ellipsis;
white-space: nowrap;
}
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
}
}
}
......@@ -477,17 +565,8 @@ onMounted(async () => {
margin-top: 15px;
display: flex;
justify-content: flex-end;
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.text {
width: 100px;
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
......@@ -526,7 +605,7 @@ onMounted(async () => {
background: rgba(174, 208, 255, 1);
}
.text {
width: 100px;
width: 100px;
margin-right: 4px;
height: 35px;
line-height: 35px;
......@@ -534,15 +613,6 @@ onMounted(async () => {
text-overflow: ellipsis;
white-space: nowrap;
}
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
}
}
}
......@@ -570,17 +640,8 @@ onMounted(async () => {
margin-top: 15px;
display: flex;
justify-content: flex-end;
.icon {
margin-top: 7px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.text {
width: 100px;
width: 100px;
margin-left: 4px;
height: 35px;
line-height: 35px;
......@@ -627,16 +688,49 @@ onMounted(async () => {
text-overflow: ellipsis;
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>
......@@ -25,7 +25,7 @@
<div class="chartsWrap">
<div class="right-main-content">
<div class="right-main-content-main">
<Fishbone />
<Fishbone :chainId="activeButtonId" />
</div>
<div class="right-main-content-footer">
<div class="footer-item1">
......@@ -76,30 +76,12 @@ import Echarts from "@/components/Chart/index.vue";
import Hint from "./hint.vue";
import ButtonList from "@/components/buttonList/buttonList.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 { getDomainDistribution, getChainEntities } from "@/api/exportControl";
import { useRoute } from "vue-router";
const route = useRoute();
const buttonList = ref([
{ id: 1, text: "新能源" },
{ id: 2, text: "半导体" },
{ id: 3, text: "跨境电商" },
{ id: 4, text: "金融业" },
{ id: 5, text: "军工" },
{ id: 6, text: "贸易" }
]);
const activeButtonId = ref(buttonList.value[0].id);
const buttonList = ref([]);
const activeButtonId = ref(buttonList.value[0]?.id || 1);
const setActiveButtonId = id => {
activeButtonId.value = id;
};
......@@ -178,10 +160,12 @@ const fetchDomainDistribution = async () => {
horizontalBarOptions.value = getHorizontalBarChart2(yAxisData, seriesData, false);
// 更新buttonList
buttonList.value = sortedData.map(item => ({
id: item.id,
text: item.name
}));
buttonList.value = sortedData
.map(item => ({
id: item.id,
text: item.name
}))
.sort((a, b) => a.id - b.id);
console.log("buttonList.value", buttonList.value);
setActiveButtonId(buttonList.value[0].id);
}
......
......@@ -8,9 +8,23 @@
import Echarts from "@/components/Chart/index.vue";
import { getMapOption } from "../../utils/charts";
import { ref, onMounted, shallowRef } from "vue";
import { getAreaDistribution } from "@/api/exportControl";
// 这儿接收父组件传递过来的date参数
const props = defineProps({
date: {
type: String,
default: ""
}
});
const mapOption = shallowRef({});
onMounted(() => {
mapOption.value = getMapOption();
// 区域分布查询
getAreaDistribution(props.date).then(res => {
console.log("res", res);
});
});
</script>
......
......@@ -8,6 +8,7 @@
size="mini"
v-model="domainValue"
placeholder="领域选择"
@change="handleDomainChange"
>
<el-option v-for="item in domainOptions" :key="item.value" :label="item.label" :value="item.value">
</el-option>
......@@ -24,7 +25,8 @@
</template>
<div class="subPanel1">
<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>
<Hint text="近几次新增受制裁实体中,中国实体占比提高,美方针对中国的出口管制风险显著增加。"></Hint>
</div>
......@@ -123,7 +125,7 @@ onMounted(async () => {
try {
const [entitiesGrowthTrendData, entitiesUpdateCountData] = await Promise.all([
getEntitiesGrowthTrend(),
getEntitiesUpdateCount()
getEntitiesUpdateCount(1)
]);
const list = _.reverse(entitiesGrowthTrendData);
......@@ -133,12 +135,25 @@ onMounted(async () => {
});
line1Option.value = getLineChart({ xAxisData, seriesData, name: "增长趋势", color: "rgba(146, 84, 222, 1)" }, true);
bar2Option.value = getBarChart(
_.reverse(entitiesUpdateCountData.xAxis),
_.reverse(entitiesUpdateCountData.series),
["rgba(22, 119, 255, 1)", "rgba(22, 119, 255, 0)"],
"更新频率"
);
if (entitiesUpdateCountData && Array.isArray(entitiesUpdateCountData)) {
const sortedData = _.sortBy(entitiesUpdateCountData, "year");
// 提取 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);
} catch (err) {
......@@ -248,11 +263,13 @@ const typeOptions = [
const domainValue = ref(domainOptions[0].value);
const typeValue = ref(typeOptions[0].value);
const bar1Option = shallowRef({});
const bar1DataIsEmpty = ref(false);
watch(
[domainValue, typeValue],
async ([domain, type]) => {
let EntitiesChangeCount = await getEntitiesChangeCount(domain, type);
EntitiesChangeCount = _.reverse(EntitiesChangeCount);
bar1DataIsEmpty.value = EntitiesChangeCount.length === 0;
bar1Option.value = getBarChart(
_.map(EntitiesChangeCount, "year"),
_.map(EntitiesChangeCount, "count"),
......@@ -317,6 +334,10 @@ watch(
await fetchKeyEntityList(route.query.startTime, newVal);
}, 300)
);
const handleDomainChange = async domain => {
await fetchKeyEntityList(route.query.startTime, value3.value, domain);
};
</script>
<style lang="scss" scoped>
......
......@@ -19,7 +19,7 @@
</CardCustom>
</div>
<div class="row">
<CardCustom title="历制裁涉及领域数" :style="{ width: '798px', height: '422px' }">
<CardCustom title="历制裁涉及领域数" :style="{ width: '798px', height: '422px' }">
<div class="subPanel3">
<div class="chartsWrap" :style="{ paddingBottom: '10px' }">
<Echarts :option="bar2Option" height="100%"></Echarts>
......@@ -46,7 +46,12 @@ import { Search } from "@element-plus/icons-vue";
import Echarts from "@/components/Chart/index.vue";
import { getBarChart, getLineChart, getPieOption1 } from "../../utils/charts";
import Hint from "./hint.vue";
import { getEntitiesAreaCountByYear, getEntitiesDomainCount, getCountThisDomain } from "@/api/exportControl";
import {
getEntitiesAreaCountByYear,
getEntitiesDomainCount,
getCountThisDomain,
getDomainDistribution
} from "@/api/exportControl";
import _ from "lodash";
import { useRoute } from "vue-router";
const route = useRoute();
......@@ -85,13 +90,18 @@ const fetchEntitiesDomainCount = async () => {
onMounted(async () => {
try {
const [entitiesAreaCountByYearData, countThisDomainData4, countThisDomainData10] = await Promise.all([
getEntitiesAreaCountByYear(route.query.startTime),
getDomainDistribution(route.query.startTime),
getCountThisDomain("4"),
// getEntitiesDomainCount(),
getCountThisDomain("10")
]);
pie1Option.value = getPieOption1(entitiesAreaCountByYearData ?? []);
pie1Option.value = getPieOption1(
entitiesAreaCountByYearData.map(item => ({
name: item.name,
value: item.count
})) ?? []
);
const list4 = _.reverse(countThisDomainData4 ?? []);
line1Option.value = getLineChart({
xAxisData: _.map(list4, "year"),
......
......@@ -42,7 +42,7 @@ onMounted(async () => {
.filter(item => item.count > 0)
.map(item => {
return {
name: item?.type,
name: item?.name,
value: item?.count
};
})
......
......@@ -57,7 +57,7 @@
<div class="panel3">
<div class="chartWrap">
<PieCharts v-if="panel3ActiveIndex === 1"></PieCharts>
<MapCharts v-if="panel3ActiveIndex === 2"></MapCharts>
<MapCharts v-if="panel3ActiveIndex === 2" :date="route.query.startTime"></MapCharts>
</div>
<Hint text="本次制裁共新增83个实体,其中53个中国大陆实体、1个中国台湾实体。"></Hint>
</div>
......@@ -94,7 +94,7 @@
</div>
</div>
<div class="tableWrap">
<div class="tableWrap" ref="tableWrapRef" @scroll="handleScroll">
<el-table
:data="selectEntitiesList"
class="sanction-table"
......@@ -115,7 +115,7 @@
</template>
</el-table-column>
<el-table-column prop="domains" label="涉及领域" width="100">
<el-table-column prop="domains" label="涉及领域" width="180">
<template #default="{ row }">
<div class="domain-tags">
<el-tag v-for="tag in row.domains" :key="tag" :type="panel5TypeMap[tag]">{{
......@@ -125,11 +125,11 @@
</template>
</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 }">
{{ row.address }}
</template>
</el-table-column>
</el-table-column> -->
<el-table-column prop="time" label="制裁时间" width="120" align="center">
<template #default="{ row }">
......@@ -137,11 +137,11 @@
</template>
</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 }">
{{ row.revenue }}
</template>
</el-table-column>
</el-table-column> -->
<el-table-column prop="subCompany" label="50%规则子企业" min-width="140" align="left">
<template #default="{ row }">
......@@ -183,7 +183,7 @@ import PieCharts from "../components/pieCharts.vue";
import MapCharts from "../components/mapCharts.vue";
import ButtonList from "@/components/buttonList/buttonList.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 panel2_1 from "../../assets/images/panel2_1.png";
import panel2_2 from "../../assets/images/panel2_2.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_7 from "../../assets/images/panel5_7.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 { useRoute } from "vue-router";
import { formatAnyDateToChinese } from "../../utils";
......@@ -246,15 +253,26 @@ const sanReasonSelect = shallowRef([
text: "将中国定位为“通过强制技术转让获取先进制程的威胁”"
}
]);
const panel5IsChecked = ref(true);
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 () => {
try {
const [organizationInfoData, personListData, sanReasonSelectData, selectEntitiesListData] = await Promise.all([
const [organizationInfoData, sanReasonSelectData] = await Promise.all([
getOrganizationInfo(),
getPersonList(),
getSanReasonSelect(route.query.startTime),
getSelectEntitiesList(route.query.startTime)
// getPersonList(),
getSanReasonSelect(route.query.startTime)
// getSelectEntitiesList(route.query.startTime)
]);
console.log("organizationInfoData", organizationInfoData);
organizationInfo.value = {
img: panel1_1,
mingcheng: organizationInfoData?.orgNameZh,
......@@ -274,22 +292,103 @@ onMounted(async () => {
return { text: item };
});
selectEntitiesList.value = _.map(selectEntitiesListData, item => {
return {
name: item?.entityNameZh,
domains: item.techDomainList,
// selectEntitiesList.value = _.map(selectEntitiesListData, item => {
// return {
// name: item?.entityNameZh,
// 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: "--",
time: formatAnyDateToChinese(item?.startTime),
isUp: true,
revenue: "--",
subCompany: "--",
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) {
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 setPanel3ActiveIndex = index => {
......@@ -304,7 +403,7 @@ const panel5ButtonAcitveID = ref(panel5ButtonList[0].id);
const panel5SetButtonAcitveID = id => {
panel5ButtonAcitveID.value = id;
};
const panel5IsChecked = ref(true);
const panel5TypeMap = {
人工智能: "danger",
通信网络: "warning",
......@@ -662,6 +761,10 @@ const panel6 = ref([
margin-top: 14px;
min-height: 0;
overflow: auto;
.domain-tags {
display: flex;
gap: 4px;
}
}
.name {
display: flex;
......
......@@ -35,14 +35,19 @@
</div>
<div class="layout-main-header-right-box">
<div class="right-box-top">
<div class="time">{{ "2025年7月" }}</div>
<div class="time">{{ route.query.startTime }}</div>
<div class="name">{{ "美国商务部工业与安全局" }}</div>
</div>
<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 type="primary" size="large" icon="EditPen">分析报告</el-button>
<el-button type="primary" size="large" disabled icon="EditPen">分析报告</el-button>
</div>
</div>
</div>
......
......@@ -7,7 +7,7 @@
<div class="sub-title">{{ subtitle }}</div>
</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>
</template>
......@@ -30,6 +30,10 @@ defineProps({
type: [Number, String],
default: 0
},
unit: {
type: String,
default: ""
},
color: {
type: String,
default: "#409EFF"
......
......@@ -67,13 +67,14 @@
</div>
<div class="home-main-header-footer-info">
<InfoCard
v-for="item in infoList"
:key="item.title"
:title="item.title"
:subtitle="item.subTitle"
:description="item.des"
:quantity="item.num"
:color="item.color"
v-for="(item, index) in infoList"
:key="item.id"
:title="item.nameZh"
:subtitle="item.nameAbbr"
:description="item.description"
:quantity="item.postCount"
unit="次"
:color="infoListColor[index]"
/>
</div>
</div>
......@@ -90,95 +91,80 @@
<template #default>
<div class="box1">
<!-- <el-image
:src="box1Image"
alt=""
style="width: 458px; height: 353px; object-fit: cover; flex-shrink: 0"
></el-image> -->
<!-- <div class="box1-right">
<div class="box1-right-title">关于进一步延长TikTok执法宽限期的行政令</div>
<div class="box1-right-tags">
<el-tag type="primary">互联网</el-tag>
<el-tag type="danger">人工智能</el-tag>
</div>
<div class="box1-right-content">
9月16日,美国白宫官方网站发布总统政令,再次推迟(第四次)对TikTok禁令的执法,新的宽限期截止日为2025年12月16日​。在宽限期内及对于宽限期前的行为,司法部不得强制执行​《保护美国人免受外国对手控制应用程序法》或因此处罚相关实体​(如TikTok及其分发平台)。司法部还需向提供商发出无违规和无责任的信函,并强调执行该法的权力专属联邦司法部长,意在阻止各州或私人提起诉讼。
</div>
<div class="box1-right-footer">
<span class="box1-right-footer-time"> 2025年9月16日 </span>
<el-button type="primary" link>
美国白宫官方网站
<el-image
src="./assets/images/icon-open.png"
alt=""
style="width: 16px; height: 16px; margin-left: 4px"
></el-image>
</el-button>
</div>
</div> -->
<div class="box1-top">
<div class="box1-top-title">
{{ entitiesDataInfoReactive.startTime }}——BIS《实体清单增列与修订条目》
</div>
<div class="box1-top-content">
<div class="box1-top-content-item">
<span class="box1-top-content-item-title">· 发布机构:</span>
<span class="box1-top-content-item-content">{{
entitiesDataInfoReactive.orgName
}}</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
>
<el-carousel ref="carouselRef" trigger="click" height="350px" :autoplay="true">
<el-carousel-item v-for="(item, index) in entitiesDataInfoList" :key="item.id + index">
<div>
<div class="box1-top">
<div class="box1-top-title">
{{ item.postDate }}——BIS《实体清单增列与修订条目》
</div>
<div class="box1-top-content">
<div class="box1-top-content-item">
<span class="box1-top-content-item-title">· 发布机构:</span>
<span class="box1-top-content-item-content">{{ item.postOrgName }}</span>
</div>
<div class="box1-top-content-item">
<span class="box1-top-content-item-title">· 生效日期:</span>
<span class="box1-top-content-item-content">{{ item.postDate }}</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="domainItem in item.domains"
:key="domainItem"
>
<el-tag
:type="
domainItem === '航空航天'
? 'primary'
: item === '人工智能'
? 'danger'
: 'info'
"
>{{ domainItem }}</el-tag
>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="box1-bottom">
<div class="box1-bottom-title">· 涉及主要实体:</div>
<div class="box1-bottom-content">
<div
class="box1-bottom-content-item"
v-for="(item, index) in entitiesDataInfoReactive.entityList"
:key="index"
>
<el-image
v-if="item.img"
class="box1-bottom-content-item-img"
:src="item.img"
alt=""
></el-image>
<div v-else class="box1-bottom-content-item-imgUndefined">
{{ (item.name || item.enName)?.match(/[\u4e00-\u9fa5a-zA-Z0-9]/)?.[0] }}
<div class="box1-bottom">
<div class="box1-bottom-title">· 涉及主要实体:</div>
<div class="box1-bottom-content">
<div
class="box1-bottom-content-item"
v-for="(ett, index) in item.sanEntities"
:key="index"
>
<el-image
v-if="ett.img"
class="box1-bottom-content-item-img"
:src="ett.img"
alt=""
></el-image>
<div v-else class="box1-bottom-content-item-imgUndefined">
{{
(ett.entityNameZh || ett.enName)?.match(
/[\u4e00-\u9fa5a-zA-Z0-9]/
)?.[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 class="box1-bottom-content-item-txt">{{ item.name || item.enName }}</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">{{ entitiesDataInfoReactive.chNum }}</div>
</div>
</el-carousel-item>
</el-carousel>
</div>
</template>
</custom-container>
......@@ -300,9 +286,11 @@
</div>
</template>
</el-table-column>
<el-table-column label="重点领域" width="180">
<el-table-column label="重点领域" width="180" align="center">
<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
v-for="tag in scope.row.tags"
:key="tag"
......@@ -334,9 +322,11 @@
</div>
</template>
</el-table-column>
<el-table-column label="重点领域" width="180">
<el-table-column label="重点领域" width="180" align="center">
<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
v-for="tag in scope.row.tags"
:key="tag"
......@@ -352,7 +342,7 @@
</el-table-column>
</el-table>
</div>
<div class="box3-content">
<div class="box3-content" style="display: none">
<div class="box3-content-title">关键与新兴技术清单(CETs)</div>
<el-table :data="tableData1" stripe style="width: 100%">
<el-table-column prop="year" label="年份" width="100" />
......@@ -394,19 +384,19 @@
<el-row :gutter="20" style="width: 1600px; margin: 0 auto">
<el-col :span="8">
<custom-container title="制裁领域分析" :titleIcon="radarIcon" height="450px">
<custom-container title="制裁领域分析" :titleIcon="radarIcon" height="480px">
<template #header-right>
<el-checkbox v-model="checked" label="50%规则" size="large" />
<el-checkbox v-model="domainChecked" label="50%规则" size="large" />
</template>
<template #default>
<EChart :option="radarOption" autoresize :style="{ height: '380px' }" />
<EChart :option="radarOption" autoresize :style="{ height: '450px' }" />
</template>
</custom-container>
</el-col>
<el-col :span="16">
<custom-container title="制裁清单数量增长趋势" :titleIcon="qushiIcon" height="450px">
<custom-container title="制裁清单数量增长趋势" :titleIcon="qushiIcon" height="480px">
<template #header-right>
<el-checkbox v-model="checked" label="50%规则" size="large" />
<el-checkbox v-model="trendChecked" label="50%规则" size="large" />
</template>
<template #default>
<EChart :option="trendOption" autoresize :style="{ height: '400px' }" />
......@@ -421,23 +411,25 @@
<custom-container title="历次制裁过程" :titleIcon="listIcon" height="845px">
<template #default>
<div class="box4">
<div class="box4-item" v-for="(item, idx) in sanctionProcessList" :key="item.title">
<div class="box4-item-left">
<el-image :src="dotIcon" alt="图片" class="box4-item-left-icon" />
<div class="box4-item-left-line" v-if="idx + 1 != sanctionProcessList.length"></div>
</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 style="height: 90%; overflow-y: auto; padding-top: 10px">
<div class="box4-item" v-for="(item, idx) in sanctionProcessList" :key="item.title">
<div class="box4-item-left">
<el-image :src="dotIcon" alt="图片" class="box4-item-left-icon" />
<div class="box4-item-left-line" v-if="idx + 1 != sanctionProcessList.length"></div>
</div>
<div class="box4-item-right-content">
{{ item.content }}
<div class="box4-item-right">
<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 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-button>
......@@ -470,7 +462,7 @@
<el-table-column prop="name" label="实体名称" min-width="200">
<template #default="scope">
<div class="tableName">
<div class="tableName" @click="handleCompClick(scope.row)">
<el-image
v-if="scope.row.img"
class="box1-bottom-content-item-img"
......@@ -571,7 +563,7 @@
</template>
<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 * as echarts from "echarts";
import setChart from "@/utils/setChart";
......@@ -645,50 +637,45 @@ import { getMultipleBarChart_m } from "./utils/charts";
import { formatAnyDateToChinese } from "./utils";
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 entitiesDataInfoReactive = shallowRef({
chNum: undefined,
entityList: [],
domains: [],
startTime: "",
rawStartTime: "",
orgName: ""
});
const entitiesDataInfoList = shallowRef([]);
// 趋势图
const trendOption = ref({});
const trendChecked = ref(false);
// 发布频度
const tableData1 = ref([]);
// 历次制裁过程
const sanctionProcessList = ref([]);
const sanctionPage = ref(1);
// 制裁实体清单
const entitiesList = ref([]);
onMounted(async () => {
try {
const [dataCount, entitiesDataInfo, industryCountByYear, countDomainByYear, sanctionsInfoCount, entityBody] =
await Promise.all([
getEntitiesDataCount(),
getEntitiesDataInfo(),
getIndustryCountByYear(),
getCountDomainByYear()
// getSanctionsInfoCount()
// getEntitiesList("实体清单")
]);
infoList.value[0].num = dataCount;
const [dataCount, entitiesDataInfo, industryCountByYear, countDomainByYear] = await Promise.all([
getEntitiesDataCount(),
getEntitiesDataInfo(),
getIndustryCountByYear(1),
getCountDomainByYear(trendChecked.value)
]);
infoList.value = dataCount;
const entityList = _.map(entitiesDataInfo?.sanEntities ?? [], ({ entityNameZh, entityName }) => {
return { name: entityNameZh, enName: entityName };
});
entitiesDataInfoReactive.value = {
entityList,
chNum: entitiesDataInfo?.cnEntityCount,
domains: entitiesDataInfo?.domains ?? [],
// startTime: formatAnyDateToChinese(entitiesDataInfo?.startTime ?? ""),
startTime: entitiesDataInfo.postDate,
rawStartTime: entitiesDataInfo?.startTime ?? "",
orgName: entitiesDataInfo?.postOrgName
};
entitiesDataInfoList.value = entitiesDataInfo || [];
const list = _.chain(industryCountByYear).filter("year").orderBy("year", "desc").value();
const total = _.sumBy(list, "count");
tableData1.value = _.map(list, item => {
......@@ -699,8 +686,12 @@ onMounted(async () => {
tags: item.domain
};
}).slice(0, 5);
console.log("tableData1", tableData1.value);
trendOption.value = getMultipleBarChart_m(countDomainByYear);
console.log("countDomainByYear", 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 => {
// return {
// title: item.tittle,
......@@ -711,9 +702,9 @@ onMounted(async () => {
// };
// });
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);
// entitiesList.value = _.map(entityBody.content, item => {
// return {
......@@ -729,67 +720,93 @@ onMounted(async () => {
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 = () => {
router.push({
path: "/overview"
});
};
const carouselRef = ref(null);
const handleToDetail = () => {
// router.push({
// path: "/exportControlAnalysis"
// });
// router.push({
// path: "/exportControl/analysis"
// });
let activeIndex = 0;
if (carouselRef.value) {
activeIndex = carouselRef.value.activeIndex;
}
const route = router.resolve({
path: "/exportControlAnalysis",
query: {
startTime: entitiesDataInfoReactive.value.startTime
}
});
window.open(route.href, "_blank");
console.log("当前 Carousel 激活索引:", activeIndex);
// 使用当前激活项的数据
const currentItem = entitiesDataInfoList.value[activeIndex];
if (currentItem) {
const route = router.resolve({
path: "/exportControlAnalysis",
query: {
startTime: currentItem.postDate
}
});
window.open(route.href, "_blank");
}
};
const billList = ref([]);
const curBillListIndex = ref(0);
const searchKey = 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 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 entityList = ref([
{
......@@ -865,15 +882,20 @@ const customNewsData = ref([
]);
// 雷达图
const domainChecked = ref(false);
const radarOption = ref({
title: {
text: ""
},
legend: {
top: 0,
top: "0%",
icon: "circle",
data: ["实体清单", "商业管制清单", "关键和新型技术清单"]
},
grid: {
top: "15%",
containLabel: true
},
radar: {
// shape: 'circle',
indicator: [
......@@ -923,9 +945,9 @@ const radarOption = ref({
});
// 获取雷达图数据
const fetchRadarData = async () => {
const fetchRadarData = async checked => {
try {
const data = await getSanDomainCount();
const data = await getSanDomainCount(checked);
if (data && Array.isArray(data) && data.length > 0) {
// 收集所有可能的领域名称
const allDomains = new Set();
......@@ -984,6 +1006,11 @@ const fetchRadarData = async () => {
}
};
watch(
() => domainChecked.value,
() => fetchRadarData(domainChecked.value)
);
// 进度条状态
const getStatus = _percent => {
const percent = _percent * 100;
......@@ -1020,12 +1047,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) => {
try {
const res = await getSanctionProcess("实体清单", page, size);
if (res) {
sanctionProcessList.value = res.content.slice(0, 5).map(item => ({
sanctionProcessList.value = res.content.map(item => ({
...item,
title: item.name,
desc: `${item.cnEntityCount} 家中国实体`,
......@@ -1416,6 +1468,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 () => {
try {
......@@ -1742,8 +1805,9 @@ onMounted(async () => {
.box3 {
display: flex;
justify-content: space-between;
// justify-content: space-between;
align-items: flex-start;
gap: 60px;
.box3-content-title {
font-size: 18px;
font-weight: 700;
......@@ -1769,7 +1833,10 @@ onMounted(async () => {
overflow: auto;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-top: 16px;
// padding-bottom: 50px;
position: relative;
.box4-item {
display: flex;
gap: 10px;
......@@ -1802,6 +1869,7 @@ onMounted(async () => {
position: relative;
top: -7.5px;
padding-bottom: 8px;
cursor: pointer;
&-title {
font-size: 18px;
color: $base-color;
......@@ -1827,14 +1895,15 @@ onMounted(async () => {
}
}
.box4-footer {
margin-top: auto;
position: absolute;
// margin-top: auto;
display: flex;
justify-content: center;
align-items: center;
bottom: 30px;
left: 50%;
margin-left: -20px;
margin-bottom: 30px;
margin-left: -30px;
// margin-bottom: 30px;
}
}
......@@ -2550,6 +2619,7 @@ onMounted(async () => {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
.box1-bottom-content-item-imgUndefined {
width: 24px;
height: 24px;
......
import * as echarts from 'echarts';
import chinaJson from './China.json'
import _ from 'lodash';
import * as echarts from "echarts";
import chinaJson from "./China.json";
import _ from "lodash";
//饼图
export function getPieOption(data, title) {
let option = {
color: ["#4096ff", "#b37feb", "#ff7875", "#85a5ff", "#69b1ff", "#ffc069", "#87e8de"],
title: {
text: title,
top: 10,
left: 10,
textStyle: {
color: "rgba(59, 65, 75, 1)",
fontSize: 16,
fontWeight: 700
}
},
series: [
{
type: 'pie',
radius: [75, 132],
height: '100%',
center: ['50%', '50%'],
width: '100%',
itemStyle: {
borderColor: '#fff',
borderWidth: 1
},
label: {
alignTo: 'edge',
formatter: '{b} {d}%',
minMargin: 5,
edgeDistance: 10,
lineHeight: 15,
rich: {
time: {
fontSize: 10,
color: '#999'
}
}
},
labelLine: {
length: 15,
length2: 0,
maxSurfaceAngle: 80
},
labelLayout: function (params) {
const isLeft = params.labelRect.x < 556 / 2;
const points = params.labelLinePoints;
// Update the end point.
points[2][0] = isLeft
? params.labelRect.x
: params.labelRect.x + params.labelRect.width;
return {
labelLinePoints: points
};
},
data: data
}]
}
return option
let option = {
color: ["#4096ff", "#b37feb", "#ff7875", "#85a5ff", "#69b1ff", "#ffc069", "#87e8de"],
title: {
text: title,
top: 10,
left: 10,
textStyle: {
color: "rgba(59, 65, 75, 1)",
fontSize: 16,
fontWeight: 700
}
},
series: [
{
type: "pie",
radius: [75, 132],
height: "100%",
center: ["50%", "50%"],
width: "100%",
itemStyle: {
borderColor: "#fff",
borderWidth: 1
},
label: {
alignTo: "edge",
formatter: "{b} {d}%",
minMargin: 5,
edgeDistance: 10,
lineHeight: 15,
rich: {
time: {
fontSize: 10,
color: "#999"
}
}
},
labelLine: {
length: 15,
length2: 0,
maxSurfaceAngle: 80
},
labelLayout: function (params) {
const isLeft = params.labelRect.x < 556 / 2;
const points = params.labelLinePoints;
// Update the end point.
points[2][0] = isLeft ? params.labelRect.x : params.labelRect.x + params.labelRect.width;
return {
labelLinePoints: points
};
},
data: data
}
]
};
return option;
}
export function getPieOption1(data, title) {
let option = {
color: ["#4096ff", "#b37feb", "#ff7875", "#85a5ff", "#69b1ff", "#ffc069", "#87e8de"],
title: {
text: title,
top: 10,
left: 10,
textStyle: {
color: "rgba(59, 65, 75, 1)",
fontSize: 16,
fontWeight: 700
}
},
series: [
{
type: 'pie',
radius: [70, 110],
height: '100%',
center: ['50%', '50%'],
width: '100%',
itemStyle: {
borderColor: '#fff',
borderWidth: 1
},
label: {
alignTo: 'edge',
formatter: `{b}${data.length < 10 ? '\n' : ''} {two|{c}家 {d}%}`,
fontSize: 17.6,
fontWeight: 700,
minMargin: 5,
edgeDistance: 10,
lineHeight: 23,
rich: {
two: {
fontSize: 15,
color: ' rgba(95, 101, 108, 1)',
}
}
},
labelLine: {
length: 15,
length2: 0,
maxSurfaceAngle: 80
},
labelLayout: function (params) {
console.log('labelLayoutparams', params)
const isLeft = params.labelRect.x < 556 / 2;
const points = params.labelLinePoints;
// Update the end point.
points[2][0] = isLeft
? data.length < 10 ? params.labelRect.x : (params.labelRect.x + params.labelRect.width)
: data.length < 10 ? (params.labelRect.x + params.labelRect.width) : params.labelRect.x;
return {
labelLinePoints: points
};
},
data: data
}]
}
return option
let option = {
color: ["#4096ff", "#b37feb", "#ff7875", "#85a5ff", "#69b1ff", "#ffc069", "#87e8de"],
title: {
text: title,
top: 10,
left: 10,
textStyle: {
color: "rgba(59, 65, 75, 1)",
fontSize: 16,
fontWeight: 700
}
},
series: [
{
type: "pie",
radius: [70, 110],
height: "100%",
center: ["50%", "50%"],
width: "100%",
itemStyle: {
borderColor: "#fff",
borderWidth: 1
},
label: {
alignTo: "edge",
formatter: `{b}${data.length < 10 ? "\n" : ""} {two|{c}家 {d}%}`,
fontSize: 17.6,
fontWeight: 700,
minMargin: 5,
edgeDistance: 10,
lineHeight: 23,
rich: {
two: {
fontSize: 15,
color: " rgba(95, 101, 108, 1)"
}
}
},
labelLine: {
length: 15,
length2: 0,
maxSurfaceAngle: 80
},
labelLayout: function (params) {
console.log("labelLayoutparams", params);
const isLeft = params.labelRect.x < 556 / 2;
const points = params.labelLinePoints;
// Update the end point.
points[2][0] = isLeft
? data.length < 10
? params.labelRect.x
: params.labelRect.x + params.labelRect.width
: data.length < 10
? params.labelRect.x + params.labelRect.width
: params.labelRect.x;
return {
labelLinePoints: points
};
},
data: data
}
]
};
return option;
}
export function getPieOption2(data, title) {
let option = {
color: ["#4096ff", "#b37feb", "#ff7875", "#85a5ff", "#69b1ff", "#ffc069", "#87e8de"],
title: {
text: title,
top: 10,
left: 10,
textStyle: {
color: "rgba(59, 65, 75, 1)",
fontSize: 16,
fontWeight: 700
}
},
legend: {
icon: 'rect',
top: 'center',
right: '40',
orient: 'vertical',
itemWidth: 30,
itemHeight: 20,
borderRadius: 2,
formatter: function (name) {
// 获取系列数据,假设第一个系列为饼图
let seriesData = option.series[0].data; // [citation:8]
// 也可以考虑使用 this.getSeries()[0].data [citation:8]
let currentValue;
// 计算数据总和并查找当前图例名对应的数值
seriesData.forEach(item => {
if (item.name === name) {
currentValue = item.value;
}
});
return ` ${name} ${currentValue} %`
},
itemGap: 10,
textStyle: {
color: 'rgba(59, 65, 75 ,0.8)',
fontSize: 18,
fontWeight: 500
},
},
series: [
{
type: 'pie',
radius: [70, 120],
height: '100%',
center: ['33%', '50%'],
width: '100%',
itemStyle: {
borderColor: '#fff',
borderWidth: 1
},
emphasis: {
scale: false,
},
label: {
show: false,
},
data: data
}]
}
return option
let option = {
color: ["#4096ff", "#b37feb", "#ff7875", "#85a5ff", "#69b1ff", "#ffc069", "#87e8de"],
title: {
text: title,
top: 10,
left: 10,
textStyle: {
color: "rgba(59, 65, 75, 1)",
fontSize: 16,
fontWeight: 700
}
},
legend: {
icon: "rect",
top: "center",
right: "40",
orient: "vertical",
itemWidth: 30,
itemHeight: 20,
borderRadius: 2,
formatter: function (name) {
// 获取系列数据,假设第一个系列为饼图
let seriesData = option.series[0].data; // [citation:8]
// 也可以考虑使用 this.getSeries()[0].data [citation:8]
let currentValue;
// 计算数据总和并查找当前图例名对应的数值
seriesData.forEach(item => {
if (item.name === name) {
currentValue = item.value;
}
});
return ` ${name} ${currentValue} %`;
},
itemGap: 10,
textStyle: {
color: "rgba(59, 65, 75 ,0.8)",
fontSize: 18,
fontWeight: 500
}
},
series: [
{
type: "pie",
radius: [70, 120],
height: "100%",
center: ["33%", "50%"],
width: "100%",
itemStyle: {
borderColor: "#fff",
borderWidth: 1
},
emphasis: {
scale: false
},
label: {
show: false
},
data: data
}
]
};
return option;
}
export function getMapOption() {
echarts.registerMap('china', chinaJson);
let data = [{
name: '2256',
value: 2256
}, {
name: '578',
value: 578
}, {
name: '744',
value: 744
}, {
name: '806',
value: 806
}, {
name: '336',
value: 336
}, {
name: '325',
value: 325
}, {
name: '487',
value: 487
}, {
name: '343',
value: 343
}, {
name: '432',
value: 432
}, {
name: '273',
value: 273
}, {
name: '1055',
value: 1055
}, {
name: '590',
value: 590
}, {
name: '319',
value: 319
}, {
name: '349',
value: 349
}, {
name: '126',
value: 126
}, {
name: '97',
value: 97
}, {
name: '201',
value: 201
}, {
name: '398',
value: 398
}, {
name: '795',
value: 795
}, {
name: '655',
value: 655
}, {
name: '295',
value: 295
}, {
name: '311',
value: 311
}, {
name: '993',
value: 993
}, {
name: '601',
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
},];
echarts.registerMap("china", chinaJson);
let data = [
{
name: "2256",
value: 2256
},
{
name: "578",
value: 578
},
{
name: "744",
value: 744
},
{
name: "806",
value: 806
},
{
name: "336",
value: 336
},
{
name: "325",
value: 325
},
{
name: "487",
value: 487
},
{
name: "343",
value: 343
},
{
name: "432",
value: 432
},
{
name: "273",
value: 273
},
{
name: "1055",
value: 1055
},
{
name: "590",
value: 590
},
{
name: "319",
value: 319
},
{
name: "349",
value: 349
},
{
name: "126",
value: 126
},
{
name: "97",
value: 97
},
{
name: "201",
value: 201
},
{
name: "398",
value: 398
},
{
name: "795",
value: 795
},
{
name: "655",
value: 655
},
{
name: "295",
value: 295
},
{
name: "311",
value: 311
},
{
name: "993",
value: 993
},
{
name: "601",
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 = {
'2256': [116.46, 39.92],
'578': [121.29, 31.14],
'744': [117.2, 39.13],
'806': [106.32, 29.32],
'336': [126.41, 45.45],
'325': [125.19, 43.52],
'487': [123.24, 41.50],
'343': [111.48, 40.49],
'432': [114.28, 38.02],
'273': [112.34, 37.52],
'1055': [117, 36.38],
'590': [113.42, 34.48],
'319': [108.54, 34.16],
'349': [103.49, 36.03],
'126': [106.16, 38.20],
'97': [101.45, 36.38],
'201': [87.36, 43.48],
'398': [117.18, 31.51],
'795': [118.50, 32.02],
'655': [120.09, 30.14],
'295': [113, 28.11],
'311': [115.52, 28.41],
'993': [114.21, 30.37],
'601': [104.05, 30.39],
'275': [106.42, 26.35],
'317': [119.18, 26.05],
'1000': [113.15, 23.08],
'186': [110.20, 20.02],
'261': [108.20, 22.48],
'132': [102.41, 25],
'18': [91.10, 29.40],
'11': [114.10, 22.18],
};
let geoCoordMap = {
2256: [116.46, 39.92],
578: [121.29, 31.14],
744: [117.2, 39.13],
806: [106.32, 29.32],
336: [126.41, 45.45],
325: [125.19, 43.52],
487: [123.24, 41.5],
343: [111.48, 40.49],
432: [114.28, 38.02],
273: [112.34, 37.52],
1055: [117, 36.38],
590: [113.42, 34.48],
319: [108.54, 34.16],
349: [103.49, 36.03],
126: [106.16, 38.2],
97: [101.45, 36.38],
201: [87.36, 43.48],
398: [117.18, 31.51],
795: [118.5, 32.02],
655: [120.09, 30.14],
295: [113, 28.11],
311: [115.52, 28.41],
993: [114.21, 30.37],
601: [104.05, 30.39],
275: [106.42, 26.35],
317: [119.18, 26.05],
1000: [113.15, 23.08],
186: [110.2, 20.02],
261: [108.2, 22.48],
132: [102.41, 25],
18: [91.1, 29.4],
11: [114.1, 22.18]
};
function convertData(data) {
var res = [];
for (var i = 0; i < data.length; i++) {
var geoCoord = geoCoordMap[data[i].name];
if (geoCoord) {
res.push({
name: data[i].name,
value: geoCoord.concat(data[i].value)
});
}
}
console.log(res)
return res;
}
function convertData(data) {
var res = [];
for (var i = 0; i < data.length; i++) {
var geoCoord = geoCoordMap[data[i].name];
if (geoCoord) {
res.push({
name: data[i].name,
value: geoCoord.concat(data[i].value)
});
}
}
console.log(res);
return res;
}
let option = {
tooltip: {
show: false
},
let option = {
tooltip: {
show: false
},
geo: {
map: 'china',
roam: true,
center: [104.95, 35.27],
// 地图尺寸为容器宽高较小值的80%
zoom: 1.7,
// scaleLimit:{
// max:'1.2',
// min:'0.7'
// },
label: {
normal: {
show: false,
textStyle: {
color: 'rgba(0,0,0,0.6)'
}
}
},
itemStyle: {
areaColor: 'rgba(231, 243, 255, 1)', // 设置所有区域的默认填充色[citation:8]
borderColor: 'rgb(5, 95, 194)', // 设置边界线颜色[citation:8]
borderWidth: 1 // 设置边界线宽度[citation:8]
},
},
// backgroundColor: 'rgba(0,51,102, 1)',
series: [{
type: 'scatter',
coordinateSystem: 'geo',
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
geo: {
map: "china",
roam: true,
center: [104.95, 35.27],
// 地图尺寸为容器宽高较小值的80%
zoom: 1.7,
// scaleLimit:{
// max:'1.2',
// min:'0.7'
// },
label: {
normal: {
show: false,
textStyle: {
color: "rgba(0,0,0,0.6)"
}
}
},
itemStyle: {
areaColor: "rgba(231, 243, 255, 1)", // 设置所有区域的默认填充色[citation:8]
borderColor: "rgb(5, 95, 194)", // 设置边界线颜色[citation:8]
borderWidth: 1 // 设置边界线宽度[citation:8]
}
},
// backgroundColor: 'rgba(0,51,102, 1)',
series: [
{
type: "scatter",
coordinateSystem: "geo",
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;
}
export const getBarChart = (nameList, valueList, color = ['rgba(255, 159, 22, 1)', 'rgba(255, 159, 22, 0)'], name) => {
const option = {
tooltip: {
trigger: "axis",
formatter: function (params) {
let result = params[0].name + '<br/>';
params.forEach(function (item, index) {
// 自定义颜色数组
const customColors = [color[0]];
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: '3%',
right: '3%',
bottom: '1%',
left: '1%',
containLabel: true
},
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: nameList
},
yAxis: {
type: "value",
axisLine: {
lineStyle: {
type: "dashed"
}
},
export const getBarChart = (nameList, valueList, color = ["rgba(255, 159, 22, 1)", "rgba(255, 159, 22, 0)"], name) => {
const option = {
tooltip: {
trigger: "axis",
formatter: function (params) {
let result = params[0].name + "<br/>";
params.forEach(function (item, index) {
// 自定义颜色数组
const customColors = [color[0]];
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: "3%",
right: "3%",
bottom: "1%",
left: "1%",
containLabel: true
},
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: nameList
},
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)"
},
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: [
{
name: name,
type: "bar",
data: valueList,
}
},
series: [{
name: name,
type: 'bar',
data: valueList,
barWidth: 12,
itemStyle: {
color: function (params) {
return new echarts.graphic.LinearGradient(0, 1, 0, 0,
[{
offset: 0,
color: color[1]
},
{
offset: 1,
color: color[0]
}
]);
},
barBorderRadius: 10,
}
}]
}
return option
}
barWidth: 12,
itemStyle: {
color: function (params) {
return new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{
offset: 0,
color: color[1]
},
{
offset: 1,
color: color[0]
}
]);
},
barBorderRadius: 10
}
}
]
};
return option;
};
export const getLineChart = (object, isPercent) => {
const option = {
title: {
text: ""
},
tooltip: {
trigger: "axis",
formatter: function (params) {
let result = params[0].name + '<br/>';
params.forEach(function (item, index) {
// 自定义颜色数组
const customColors = [object.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}${isPercent ? '%' : ''}<br/>`;
});
return result;
}
},
grid: {
top: '3%',
right: '3%',
bottom: '1%',
left: '1%',
containLabel: true
},
// toolbox: {
// feature: {
// saveAsImage: {}
// }
// },
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: object.xAxisData
},
yAxis: {
type: "value",
axisLine: {
lineStyle: {
type: "dashed"
}
},
const option = {
title: {
text: ""
},
tooltip: {
trigger: "axis",
formatter: function (params) {
let result = params[0].name + "<br/>";
params.forEach(function (item, index) {
// 自定义颜色数组
const customColors = [object.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}${isPercent ? "%" : ""}<br/>`;
});
return result;
}
},
grid: {
top: "3%",
right: "3%",
bottom: "1%",
left: "1%",
containLabel: true
},
// toolbox: {
// feature: {
// saveAsImage: {}
// }
// },
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: object.xAxisData
},
yAxis: {
type: "value",
axisLine: {
lineStyle: {
type: "dashed"
}
},
axisLabel: {
color: "rgba(95, 101, 108, 1)",
// fontSize: 22,
// fontWeight: 400
formatter: `{value} ${isPercent ? '%' : ''}`
},
axisLabel: {
color: "rgba(95, 101, 108, 1)",
// fontSize: 22,
// fontWeight: 400
formatter: `{value} ${isPercent ? "%" : ""}`
},
splitNumber: 8,
splitLine: {
lineStyle: {
width: 1,
type: "dashed",
color: "rgba(231, 243, 255, 1)"
},
}
},
series: [
{
name: object.name,
type: "line",
symbolSize: 8,
symbol: 'circle',
itemStyle: {
color: "#ffffff",
borderColor: object.color,
borderWidth: 3
},
lineStyle: {
color: object.color,
},
data: object.seriesData
}
]
};
return option;
}
splitNumber: 8,
splitLine: {
lineStyle: {
width: 1,
type: "dashed",
color: "rgba(231, 243, 255, 1)"
}
}
},
series: [
{
name: object.name,
type: "line",
symbolSize: 8,
symbol: "circle",
itemStyle: {
color: "#ffffff",
borderColor: object.color,
borderWidth: 3
},
lineStyle: {
color: object.color
},
data: object.seriesData
}
]
};
return option;
};
export const getHorizontalBarChart1 = (nameList, valueList, isPer) => {
const colorList = ['#ce4f51', '#1778ff']
const option = {
tooltip: {},
grid: {
top: '3%',
right: '3%',
bottom: '1%',
left: '1%',
containLabel: true
},
color: ['#ce4f51', '#1778ff'],
xAxis: {
type: 'value',
splitLine: {
show: false
},
show: false
},
yAxis: {
type: 'category',
data: nameList,
splitLine: {
show: false
},
axisTick: {
show: false
},
axisLine: {
show: false
},
axisLabel: {
show: true
}
},
series: [{
type: 'bar',
data: valueList.map((item, index) => {
return {
value: item,
label: {
textStyle: {
color: index < 4 ? '#1778ff' : '#ce4f51'
}
}
};
}),
label: {
show: true,
position: [450, -2],
formatter: function (params) {
return isPer ? params.value + '%' : params.value
}
},
barWidth: 8,
itemStyle: {
color: function (params) {
if (params.dataIndex < 4) {
return new echarts.graphic.LinearGradient(0, 0, 1, 0,
[{
offset: 0,
color: 'rgba(22, 119, 255, 0)'
},
{
offset: 1,
color: colorList[1]
}
]);
} else {
return new echarts.graphic.LinearGradient(0, 0, 1, 0,
[{
offset: 0,
color: 'rgba(206, 79, 81, 0)'
},
{
offset: 1,
color: colorList[0]
}
]);
}
},
barBorderRadius: 4,
}
}]
}
return option
}
const colorList = ["#ce4f51", "#1778ff"];
const option = {
tooltip: {},
grid: {
top: "3%",
right: "3%",
bottom: "1%",
left: "1%",
containLabel: true
},
color: ["#ce4f51", "#1778ff"],
xAxis: {
type: "value",
splitLine: {
show: false
},
show: false
},
yAxis: {
type: "category",
data: nameList,
splitLine: {
show: false
},
axisTick: {
show: false
},
axisLine: {
show: false
},
axisLabel: {
show: true
}
},
series: [
{
type: "bar",
data: valueList.map((item, index) => {
return {
value: item,
label: {
textStyle: {
color: index < 4 ? "#1778ff" : "#ce4f51"
}
}
};
}),
label: {
show: true,
position: [450, -2],
formatter: function (params) {
return isPer ? params.value + "%" : params.value;
}
},
barWidth: 8,
itemStyle: {
color: function (params) {
if (params.dataIndex < 4) {
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{
offset: 0,
color: "rgba(22, 119, 255, 0)"
},
{
offset: 1,
color: colorList[1]
}
]);
} else {
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{
offset: 0,
color: "rgba(206, 79, 81, 0)"
},
{
offset: 1,
color: colorList[0]
}
]);
}
},
barBorderRadius: 4
}
}
]
};
return option;
};
export const getHorizontalBarChart2 = (nameList, valueList, isPer) => {
const colorList = [
['rgba(64, 150, 255, 1)', 'rgba(64, 150, 255, 0)'],
['rgba(255, 120, 117, 1)', 'rgba(255, 120, 117, 0)'],
['rgba(89, 126, 247, 1)', 'rgba(89, 126, 247, 0)'],
['rgba(54, 207, 201, 1)', 'rgba(54, 207, 201, 0)'],
['rgba(255, 197, 61, 1)', 'rgba(255, 197, 61, 0)'],
['rgba(179, 127, 235, 1)', 'rgba(179, 127, 235, 0)']
]
console.log(colorList)
const option = {
tooltip: {},
grid: {
top: '6%',
right: '6%',
bottom: '0',
left: '1%',
containLabel: true
},
xAxis: {
type: 'value',
splitLine: {
show: false
},
show: false
},
yAxis: {
type: 'category',
data: nameList,
splitLine: {
show: false
},
axisTick: {
show: false
},
axisLine: {
show: false
},
axisLabel: {
show: true
}
},
series: [{
type: 'bar',
data: valueList.map((item, index) => {
return {
value: item,
label: {
textStyle: {
color: colorList[index][0]
}
}
};
}),
label: {
show: true,
position: [340, -2],
formatter: function (params) {
return isPer ? params.value + '%' : params.value
}
},
barWidth: 8,
itemStyle: {
color: function (params) {
console.log('params', params)
return new echarts.graphic.LinearGradient(0, 0, 1, 0,
[{
offset: 0,
color: colorList[params.dataIndex][1]
},
{
offset: 1,
color: colorList[params.dataIndex][0]
}
]);
const colorList = [
["rgba(64, 150, 255, 1)", "rgba(64, 150, 255, 0)"],
["rgba(255, 120, 117, 1)", "rgba(255, 120, 117, 0)"],
["rgba(89, 126, 247, 1)", "rgba(89, 126, 247, 0)"],
["rgba(54, 207, 201, 1)", "rgba(54, 207, 201, 0)"],
["rgba(255, 197, 61, 1)", "rgba(255, 197, 61, 0)"],
["rgba(179, 127, 235, 1)", "rgba(179, 127, 235, 0)"]
];
console.log(colorList);
const option = {
tooltip: {},
grid: {
top: "6%",
right: "6%",
bottom: "0",
left: "1%",
containLabel: true
},
xAxis: {
type: "value",
splitLine: {
show: false
},
show: false
},
yAxis: {
type: "category",
data: nameList,
splitLine: {
show: false
},
axisTick: {
show: false
},
axisLine: {
show: false
},
axisLabel: {
show: true
}
},
series: [
{
type: "bar",
data: valueList.map((item, index) => {
return {
value: item,
label: {
textStyle: {
color: colorList[index % 6][0]
}
}
};
}),
label: {
show: true,
position: [340, -2],
formatter: function (params) {
return isPer ? params.value + "%" : params.value;
}
},
barWidth: 8,
itemStyle: {
color: function (params) {
console.log("params", params);
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{
offset: 0,
color: colorList[params.dataIndex % 6][1]
},
{
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"
}
},
},
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: {
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;
}
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) => {
const list = _.chain(object.data).filter('year').orderBy('year', 'asc').value();
const colors = [
['rgba(22, 119, 255, 1)', 'rgba(22, 119, 255, 0)'],
['rgba(206, 79, 81, 1)', 'rgba(206, 79, 81, 0)'],
['rgba(255, 197, 61, 1)', 'rgba(255, 197, 61, 0)'],
['rgba(255, 204, 199, 1)', 'rgba(255, 204, 199, 0)'],
['rgba(179, 127, 235, 1)', 'rgba(179, 127, 235, 0)'],
['rgba(127, 218, 235, 1)', 'rgba(127, 214, 235, 0)'],
];
const names = _.map(list, 'year');
const datas = _.chain(object.domains).splice(0, 6).map((name, index) => {
console.log(_.map(list, name))
return {
name,
data: _.map(list, `domainNum.${name}`),
type: "bar",
barWidth: 12,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: colors[index % colors.length][0] },
// { offset: 0.5, color: '#188df0' },
{ offset: 1, color: colors[index % colors.length][1] }
]),
borderRadius: [6, 6, 0, 0]
}
}
}).value();
console.log('names', names)
const option = {
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow"
}
},
grid: {
top: 50
},
legend: {
// type: "scroll",
// show: true,
// orient: "horizontal",
icon: "circle",
},
xAxis: {
type: "category",
data: names
},
yAxis: {
type: "value"
},
series: datas
}
return option;
}
export const getMultipleBarChart_m = object => {
const list = _.chain(object.data).filter("year").orderBy("year", "asc").value();
const colors = [
["rgba(22, 119, 255, 1)", "rgba(22, 119, 255, 0)"],
["rgba(206, 79, 81, 1)", "rgba(206, 79, 81, 0)"],
["rgba(255, 197, 61, 1)", "rgba(255, 197, 61, 0)"],
["rgba(255, 204, 199, 1)", "rgba(255, 204, 199, 0)"],
["rgba(179, 127, 235, 1)", "rgba(179, 127, 235, 0)"],
["rgba(127, 218, 235, 1)", "rgba(127, 214, 235, 0)"]
];
const names = _.map(list, "year");
const datas = _.chain(object.domains)
.splice(0, 6)
.map((name, index) => {
console.log(_.map(list, name));
return {
name,
data: _.map(list, `domainNum.${name}`),
type: "bar",
barWidth: 12,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: colors[index % colors.length][0] },
// { offset: 0.5, color: '#188df0' },
{ offset: 1, color: colors[index % colors.length][1] }
]),
borderRadius: [6, 6, 0, 0]
}
};
})
.value();
console.log("names", names);
const option = {
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow"
}
},
grid: {
top: 50
},
legend: {
// type: "scroll",
// show: true,
// orient: "horizontal",
icon: "circle"
},
xAxis: {
type: "category",
data: names
},
yAxis: {
type: "value"
},
series: datas
};
return option;
};
......@@ -9,7 +9,7 @@
<div class="divider"></div>
<div class="thematic-content">
<div class="item-header">
<img class="item-header-icon" src="@/assets/images/icon/thematic-card-header-time.png"></img>
<img class="item-header-icon" src="../assets/icon1.png"></img>
<div class="item-header-text">科技博弈历程</div>
</div>
......
......@@ -13,8 +13,10 @@
<img class="item-header-icon" src="@/assets/images/icon/thematic-card-header-time.png"></img>
<div class="item-header-text">科技人才对比分析</div>
<div class="item-header-btn">
<img class="item-header-ibtn-con" src="@/assets/images/icon/header-btn.png"></img>
数据来源:美国某某发展基金会
<img class="item-header-btn-icon" src="@/assets/images/icon/header-btn.png"></img>
<div style="">
数据来源:美国某某发展基金会
</div>
</div>
</div>
<div class="divider"></div>
......@@ -31,8 +33,10 @@
<img class="item-header-icon" src="@/assets/images/icon/thematic-card-header-time.png"></img>
<div class="item-header-text">科研投入对比分析</div>
<div class="item-header-btn">
<img class="item-header-ibtn-con" src="@/assets/images/icon/header-btn.png"></img>
数据来源:美国某某发展基金会
<img class="item-header-btn-icon" src="@/assets/images/icon/header-btn.png"></img>
<div style="">
数据来源:美国某某发展基金会
</div>
</div>
</div>
......@@ -55,8 +59,10 @@
<img class="item-header-icon" src="@/assets/images/icon/thematic-card-header-time.png"></img>
<div class="item-header-text">创新主体对比分析</div>
<div class="item-header-btn">
<img class="item-header-icon" src="@/assets/images/icon/header-btn.png"></img>
数据来源:美国某某发展基金会
<img class="item-header-btn-icon" src="@/assets/images/icon/header-btn.png"></img>
<div style="">
数据来源:美国某某发展基金会
</div>
</div>
</div>
<div class="divider"></div>
......@@ -73,21 +79,23 @@
<img class="item-header-icon" src="@/assets/images/icon/thematic-card-header-time.png"></img>
<div class="item-header-text">科学数据对比分析</div>
<div class="item-header-btn">
<img class="item-header-ibtn-con" src="@/assets/images/icon/header-btn.png"></img>
数据来源:美国某某发展基金会
<img class="item-header-btn-icon" src="@/assets/images/icon/header-btn.png"></img>
<div style="">
数据来源:美国某某发展基金会
</div>
</div>
</div>
<div class="divider"></div>
<div style="display: flex;">
<div style="display: flex;height: 320px;width: calc(50% - 40px) ;margin: 20px;" id="char7">
<div style="display: flex;height: 360px;width: 433px ; " id="char7">
</div>
<div style="width: 50%;padding-top: 50px;">
<div style="width: 50%;padding-top: 34px;">
<div v-for="value in radar2Data" class="radar2Data-line">
<div class="radar2Data-circle" :style="{ backgroundColor: value.color }"></div>
<div style=" width: 40px;margin: 0 7px">{{ value.name }}</div>
<div style="width: calc(100% - 150px) ;padding: 10px;"> <el-progress :percentage="value.percent"
:color="value.color" :show-text="false" /></div>
<div style=" margin: 0 5px;font-size: 16px;">{{ value.name }}</div>
<div style="width: 168px ; "> <el-progress :percentage="value.percent" :color="value.color"
:show-text="false" /></div>
<div style=" width: 70px;text-align: right;">{{ value.percent < 50 ? '低依赖' : value.percent < 80
? '中度依赖' : '高度依赖' }} </div>
</div>
......@@ -312,7 +320,6 @@ onMounted(() => {
display: flex;
color: rgba(132, 136, 142, 1);
width: calc(100% - 210px);
font-family: Microsoft YaHei;
font-size: 16px;
......@@ -322,10 +329,23 @@ onMounted(() => {
text-align: right;
}
.item-header-ibtn-con {
width: 16px;
.item-header-btn-icon {
width: 14px;
height: 16px;
margin-top: 15px;
margin-left: 270px;
margin-right: 4px;
}
.item-header-btn-text {
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
}
......@@ -384,13 +404,17 @@ onMounted(() => {
width: 100%;
height: 30px;
margin-top: 18px;
align-items: center;
/* 垂直居中 */
color: rgba(95, 101, 108, 1);
font-size: 16px;
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 24px;
line-height: 22px;
letter-spacing: 0px;
align-items: center;
/* 垂直居中 */
text-align: right;
}
......@@ -400,4 +424,9 @@ onMounted(() => {
height: 12px;
border-radius: 50%;
}
:deep(.el-progress-bar__outer) {
height: 8px !important;
}
</style>
......@@ -20,13 +20,13 @@
</div>
<!-- 卡片 -->
<div class="card" :class="[cardPos(item), 'right-side']" :style="widthStyle()"
@click="$emit('click-card', item)">
<div class="card" :class="[cardPos(item), 'right-side']" @click="$emit('click-card', item)">
<img :src="`/icon/${item.unit}.png`" class="icon"></img>
<div class="title">{{ item.time }}</div>
<div class="title" :style="{
color: item.unit === '中国' ? ' #CF4F51' : ''
}">{{ item.title }}</div>
<div class="time">{{ item.time }}</div>
<div class="content">{{ item.content }}</div>
</div>
</div>
......@@ -50,7 +50,15 @@ export default {
},
/* 水平位置:按索引均匀分布 */
leftStyle(i) {
return { left: `${(i * 100) / (this.data.length - 1)}vw` };
// let pos = ``
// if (i === 0) {
// pos = 0
// } else {
// this.linePos(this.data[i - 1]) !== this.linePos(this.data[i]) ? pos = { left: `${(i * 270) - 125}px` } : pos = { left: `${(i * 270)}px` }
// }
return { left: `${(i * 270)}px` }
// return pos;
},
/* 卡片上下位置:unit=0 -> 下侧,其余 -> 上侧 */
cardPos(item) {
......@@ -166,28 +174,30 @@ export default {
.dot.up::after {
bottom: 100%;
height: 180px
height: 240px
}
.dot.down::after {
top: 100%;
height: 180px
height: 240px
}
.card {
position: absolute;
height: 180px;
/* 容器 28 */
width: 220px;
height: 176px;
padding: 8px 20px;
font-size: 14px;
cursor: pointer
}
.card.up {
bottom: 20px
bottom: 86px
}
.card.down {
top: 20px;
top: 94px;
}
......@@ -196,15 +206,39 @@ export default {
}
.title {
/* 美国进一步收紧对华AI芯片出口限制 */
width: 220px;
color: #055fc2;
font-size: 18px;
font-weight: 700;
line-height: 26px
line-height: 26px;
margin-top: -25px;
margin-left: 10px;
}
.time {
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
margin-left: 10px;
}
.content {
color: #5f656c;
width: 220px;
height: 90px;
color: rgba(59, 65, 75, 1);
margin-left: 10px;
font-family: Microsoft YaHei;
font-size: 16px;
line-height: 24px
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
}
</style>
\ No newline at end of file
......@@ -46,13 +46,16 @@
</div>
<div class="item-card-right">
<div style="overflow: auto;height: 400px;">
<div style=" height: 400px;">
<div v-for="(item, index) in technologicalTrends.data" :key="index" style="height: 46px;">
<div class="list-row">
<div class="item-list-punblier">{{ item.tag1 }}</div>
<div class="item-list-content">{{ item.text }}</div>
<div class="item-list-time">{{ item.time }}</div>
<div class="item-list-punblier">{{ item.tag2 }}</div>
<div class="item-list-punblier"
:style="{ color: item.tagColor, backgroundColor: item.tagBg, borderColor: item.color }">{{
item.tag2 }}
</div>
</div>
<div class="item-header-divider" />
</div>
......@@ -66,7 +69,7 @@
<div class="item-header">
<img class="item-header-icon" src="@/assets/images/icon/waring-card-header-icon.png" />
<div class="item-header-text" style="background-color: #ce4f51">风险信号 <div class="num">{{ warningList.length
}}</div>
}}</div>
</div>
</div>
......@@ -130,14 +133,70 @@ const technologicalTrends = ref({
txt: '2025年9月14日,欧盟委员会宣布通过“地平线欧洲”2025年工作计划投入约73亿欧元的专项资金,增强欧洲的科研创新引擎和',
},
data: [
{ tag1: '科研仪器', text: '欧盟投资73亿欧元推进数字化转型', time: '1小时前', tag2: '新能源' },
{ tag1: '创新主体', text: '美财政部发布拟议规则限制对华网...', time: '3小时前', tag2: '人工智能' },
{ tag1: '科技人才', text: '美NIST发布《美国关键和新兴技术....', time: '昨天', tag2: '量子科技' },
{ tag1: '创新主体', text: '美《开创未来先进计算生态系统:战...', time: '昨天', tag2: '人工智能' },
{ tag1: '科研仪器', text: '欧盟启动初代“数字地球”系统..', time: '昨天', tag2: '量子科技' },
{ tag1: '科研仪器', text: '美NSF投建国家AI可编程云实验室网...', time: '昨天', tag2: '新能源' },
{ tag1: '科研仪器', text: '英启动全球顶尖科技人才引进计划瞄...', time: '昨天', tag2: '新能源' },
{ tag1: '科研仪器', text: '美国家科学基金会致力改进下一代无...', time: '昨天', tag2: '新能源' },
{
"tag1": "科研仪器",
"text": "欧盟投资73亿欧元推进数字化转型",
"time": "1小时前",
"tag2": "新能源",
"tagColor": "rgba(250, 140, 22, 1)",
"tagBg": "rgba(255, 247, 230, 1)"
},
{
"tag1": "创新主体",
"text": "美财政部发布拟议规则限制对华网...",
"time": "3小时前",
"tag2": "人工智能",
"tagColor": "rgba(114, 46, 209, 1)",
"tagBg": "rgba(249, 240, 255, 1)"
},
{
"tag1": "科技人才",
"text": "美NIST发布《美国关键和新兴技术....",
"time": "昨天",
"tag2": "量子科技",
"tagColor": "rgba(206, 79, 81, 1)",
"tagBg": "rgba(255, 241, 240, 1)"
},
{
"tag1": "创新主体",
"text": "美《开创未来先进计算生态系统:战...",
"time": "昨天",
"tag2": "人工智能",
"tagColor": "rgba(114, 46, 209, 1)",
"tagBg": "rgba(249, 240, 255, 1)"
},
{
"tag1": "科研仪器",
"text": "欧盟启动初代“数字地球”系统..",
"time": "昨天",
"tag2": "量子科技",
"tagColor": "rgba(206, 79, 81, 1)",
"tagBg": "rgba(255, 241, 240, 1)"
},
{
"tag1": "科研仪器",
"text": "美NSF投建国家AI可编程云实验室网...",
"time": "昨天",
"tag2": "新能源",
"tagColor": "rgba(250, 140, 22, 1)",
"tagBg": "rgba(255, 247, 230, 1)"
},
{
"tag1": "科研仪器",
"text": "英启动全球顶尖科技人才引进计划瞄...",
"time": "昨天",
"tag2": "新能源",
"tagColor": "rgba(250, 140, 22, 1)",
"tagBg": "rgba(255, 247, 230, 1)"
},
{
"tag1": "科研仪器",
"text": "美国家科学基金会致力改进下一代无...",
"time": "昨天",
"tag2": "新能源",
"tagColor": "rgba(250, 140, 22, 1)",
"tagBg": "rgba(255, 247, 230, 1)"
}
],
});
......@@ -158,10 +217,16 @@ const handleToMoreRiskSignal = () => {
<style lang="scss" scoped>
.content-main {
width: 100%;
height: 100%;
overflow: hidden;
font-family: Microsoft YaHei;
// width: 100%;
// height: 100%;
// overflow: hidden;
// font-family: Microsoft YaHei;
width: 1920px;
margin: 0 auto;
background: url("./assets/bg.png");
background-repeat: no-repeat;
background-color: #fff;
background-size: contain;
}
.content-title {
......@@ -355,7 +420,7 @@ const handleToMoreRiskSignal = () => {
.list-row {
display: flex;
align-items: center;
margin: 10px 0;
margin: 5px 0;
height: 40px;
......@@ -382,6 +447,7 @@ const handleToMoreRiskSignal = () => {
.item-list-time {
width: 68px;
text-align: right;
margin-right: 8px;
color: rgba(132, 136, 142, 1);
}
}
......
......@@ -31,14 +31,22 @@ const getColumnChart = (nameList, series1, series2, isPer) => {
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { show: true, color: '#666' },
splitLine: { show: true, lineStyle: { color: '#ebebeb' } }
splitLine: {
show: true,
lineStyle: {
type: "dashed",
color: "#E7F3FF"
}
},
},
series: [
{
name: '研究型大学',
type: 'bar',
data: series1,
barWidth: 8,
barWidth: 12,
barGap: '60%', // 同一类别内不同系列的间隔
barCategoryGap: "-60%",
label: { show: false },
itemStyle: { borderRadius: [8, 8, 0, 0], color: gradBlue }
},
......@@ -46,7 +54,7 @@ const getColumnChart = (nameList, series1, series2, isPer) => {
name: '科技企业',
type: 'bar',
data: series2,
barWidth: 8,
barWidth: 12,
label: { show: false },
itemStyle: { borderRadius: [8, 8, 0, 0], color: gradCyan }
},
......@@ -54,7 +62,7 @@ const getColumnChart = (nameList, series1, series2, isPer) => {
name: '研究机构',
type: 'bar',
data: series2,
barWidth: 8,
barWidth: 12,
label: { show: false },
itemStyle: { borderRadius: [8, 8, 0, 0], color: '#FFC63D' }
}
......
......@@ -67,7 +67,7 @@ const getMultiLineChart = (dataX, dataY1, dataY2, dataY3, dataY4, dataY5) => {
return {
tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } },
grid: { top: '5%', right: '0%', bottom: '0%', left: '0%', containLabel: true },
grid: { top: '5%', right: '10%', bottom: '0%', left: '0%', containLabel: true },
xAxis: { type: 'category', boundaryGap: false, data: dataX },
yAxis: {
......
import * as echarts from "echarts";
const getBarChart = (nameList, valueList, isPer) => {
const colors = [
'rgba(165, 42, 42, 1)', // 红色
'rgba(0, 0, 128, 1)', // 蓝色
'rgba(0, 128, 128, 1)', // 青色
'rgba(75, 0, 130, 1)', // 紫色
'rgba(255, 165, 0, 1)', // 橙色
'rgba(173, 216, 230, 1)' // 浅蓝色
];
const getRadarChart = () => {
const option = {
title: { text: '' },
tooltip: {},
radar: {
radius: '50%', // 关键:缩小整个雷达
center: ['50%', '45%'], // 可选:再往下挪一点,避免图例挤在一起
radius: '50%',
center: ['50%', '45%'],
indicator: [
{ name: '能源', max: 6500 },
{ name: '集成电路', max: 16000 },
{ name: '人工智能', max: 30000 },
{ name: '通信网络', max: 38000 },
{ name: '量子科技', max: 52000 },
{ name: '生物科技', max: 25000 }
{ name: '能源领域', max: 100 },
{ name: '集成电路', max: 100 },
{ name: '生物科技', max: 100 },
{ name: '人工智能', max: 100 },
{ name: '通信网络', max: 100 },
{ name: '量子科技', max: 100 }
],
axisName: {
formatter: '{value}',
color: 'rgba(59, 65, 75, 1)',
fontSize: 14,
fontSize: 16,
fontWeight: 400
}
},
series: [
{
name: 'Budget vs spending',
name: '科技领域表现',
type: 'radar',
symbol: 'none',
data: [
{
value: [4200, 3000, 20000, 35000, 50000, 18000],
name: '中国',
areaStyle: { color: 'rgba(10, 87, 166, 0.2)' }
"value": [20, 95, 30, 85, 70, 60],
"name": "中国",
"areaStyle": { "color": "rgba(165, 42, 42, 0.2)" },
lineStyle: {
width: 1.5, // 设置线条宽度为2
color: 'rgba(165, 42, 42, 1)' // 设置线条颜色
}
},
{
value: [5000, 14000, 28000, 26000, 42000, 21000],
name: '美国',
areaStyle: { color: 'rgba(206, 79, 81, 0.2)' }
"value": [90, 40, 85, 20, 60, 75],
"name": "美国",
"areaStyle": { "color": "rgba(0, 0, 128, 0.2)" },
lineStyle: {
width: 1.5, // 设置线条宽度为2
color: 'rgba(0, 0, 128, 1)' // 设置线条颜色
}
},
{
value: [4000, 14000, 18000, 21000, 32000, 10000],
name: '欧盟',
areaStyle: { color: 'rgba(250, 140, 22, 0.2)' }
"value": [80, 68, 92, 82, 58, 88],
"name": "欧盟",
"areaStyle": { "color": "rgba(0, 128, 128, 0.2)" },
lineStyle: {
width: 1.5, // 设置线条宽度为2
color: 'rgba(0, 128, 128, 1)' // 设置线条颜色
}
},
{
value: [4000, 14000, 18000, 21000, 32000, 10000],
name: '英国',
areaStyle: { color: 'rgba(250, 140, 22, 0.2)' }
"value": [57, 81, 76, 91, 87, 67],
"name": "英国",
"areaStyle": { "color": "rgba(75, 0, 130, 0.2)" },
lineStyle: {
width: 1.5, // 设置线条宽度为2
color: 'rgba(75, 0, 130, 1)' // 设置线条颜色
}
},
{
value: [4000, 14000, 18000, 21000, 32000, 10000],
name: '日本',
areaStyle: { color: 'rgba(250, 140, 22, 0.2)' }
"value": [93, 59, 79, 89, 69, 83],
"name": "日本",
"areaStyle": { "color": "rgba(255, 165, 0, 0.2)" },
lineStyle: {
width: 1.5, // 设置线条宽度为2
color: 'rgba(255, 165, 0, 1)' // 设置线条颜色
}
},
{
value: [4000, 14000, 18000, 21000, 32000, 10000],
name: '韩国',
areaStyle: { color: 'rgba(250, 140, 22, 0.2)' }
"value": [86, 56, 77, 94, 80, 66],
"name": "韩国",
"areaStyle": { "color": "rgba(173, 216, 230,0.2)" },
lineStyle: {
width: 1.5, // 设置线条宽度为2
color: 'rgba(173, 216, 230, 1)' // 设置线条颜色
}
}
]
}
]
}
return option
}
};
return option;
};
export default getBarChart
\ No newline at end of file
export default getRadarChart;
\ No newline at end of file
<!-- SourceLibrary.vue -->
<template>
<<<<<<< HEAD
<div class="source-library-container">
<div class="source-library-grid">
<div v-for="(item, index) in sourceLibraryData" :key="index" class="source-library-card">
......@@ -36,6 +37,42 @@
/>
</div>
</div>
=======
<div class="source-library-container">
<div class="source-library-grid">
<div v-for="(item, index) in sourceLibraryData" :key="index" class="source-library-card">
<div class="source-library-avatar-wrapper">
<img :src="item.avatar" alt="" class="source-library-avatar" />
<div class="person-tags">
<div class="person-tag-bg" v-for="(tag, tIdx) in item.icon" :key="tIdx">
<img :src="'/public/icon/header-icon' + tag + '.png'" class="tag-icon" alt="tag" />
</div>
</div>
</div>
<div class="source-library-text-content">
<div style=" width: 240px;">
<h3 class="source-library-name">{{ item.name }}</h3>
<p class="source-library-title">{{ item.title }}</p>
<p class="source-library-tag" :style="{
background: item.colorArray[2],
color: item.colorArray[0],
borderColor: item.colorArray[1],
}">{{ item.tag }}</p>
</div>
</div>
</div>
</div>
<div class="page">
<div class="count">共1205项调查</div>
<el-pagination v-model:current-page="currentPage" :page-size="pageSize" :total="total" layout="prev, pager, next"
background @current-change="handlePageChange" />
</div>
</div>
>>>>>>> af566f60d3ab2563ec38173a60323b1404cd7081
</template>
<script setup>
......@@ -84,6 +121,28 @@ const handlePageChange = p => {
margin-right: 18px;
}
.person-tags {
display: flex;
margin-top: -20px;
width: 42px;
padding-left: 28px;
text-align: center;
}
.person-tag-bg {
/* 椭圆 6 */
width: 32px;
height: 32px;
background: rgba(255, 255, 255, 0.8);
border-radius: 50%;
}
.tag-icon {
width: 24px;
height: 24px;
object-fit: contain;
}
.source-library-avatar {
/* 椭圆 142 */
width: 88px;
......@@ -145,6 +204,7 @@ const handlePageChange = p => {
}
.page {
<<<<<<< HEAD
/* width: 1221px; */
width: 1600px;
height: 40px;
......@@ -162,6 +222,27 @@ const handlePageChange = p => {
letter-spacing: 0px;
text-align: left;
}
=======
width: 1600px;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
margin: 36px 0 0 0;
padding-left: 11px;
.count {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
>>>>>>> af566f60d3ab2563ec38173a60323b1404cd7081
}
:deep(.el-pagination) {
......
......@@ -76,7 +76,7 @@
<div class="box1-header">
<div class="box1-header-left">
<div class="icon">
<img src="./assets/images/box1-header-icon.png" alt="" />
<img src="./assets/images/TechnologyFigures-icon4.png" alt="" />
</div>
<div class="title">{{ "人物新闻动态" }}</div>
</div>
......@@ -128,7 +128,7 @@
<div class="box3-header">
<div class="box3-header-left">
<div class="box3-header-icon">
<img src="./assets/images/header-news.png" alt="" />
<img src="./assets/images/TechnologyFigures-icon3.png" alt="" />
</div>
<!-- <div class="box3-header-title">{{ "人物动向" }}</div> -->
<div class="header-title"
......@@ -178,7 +178,7 @@
<div class="box5-header">
<div class="box5-header-left">
<div class="box5-header-icon">
<img src="./assets/images/box3-header-icon.png" alt=""
<img src="./assets/images/TechnologyFigures-icon1.png" alt=""
style="margin: 13px 21px 13px 21px; height: 22px" />
<div class="box5-header-title">{{ "科技人物观点词云" }}</div>
</div>
......@@ -200,7 +200,7 @@
<div class="box6">
<div class="box6-header" style="width: 790px">
<div class="header-icon">
<img src="./assets/images/box6-header-icon.png" alt="" />
<img src="./assets/images/box3-header-icon.png" alt="" />
</div>
<div class="header-title"
style="display: flex; width: 740px; justify-content: space-between; align-items: center">
......@@ -223,7 +223,7 @@
<div class="box7-header">
<div class="box7-header-left">
<div class="box7-header-icon">
<img src="./assets/images/box3-header-icon.png" alt="" />
<img src="./assets/images/TechnologyFigures-icon2.png" alt="" />
</div>
<div class="box7-header-title">{{ "科技人物类型" }}</div>
</div>
......@@ -234,7 +234,7 @@
<div class="box8-header">
<div class="box8-header-left">
<div class="box8-header-icon">
<img src="./assets/images/box6-header-icon.png" alt="" />
<img src="./assets/images/TechnologyFigures-icon2.png" alt="" />
</div>
<div style="display: flex; width: 730px; justify-content: space-between; align-items: center">
<div class="box8-header-title">{{ "主要人物涉华观点统计" }}</div>
......@@ -839,7 +839,7 @@ onMounted(() => {
line-height: 47px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-size: 16px;
font-weight: 400;
}
}
......
......@@ -4,111 +4,127 @@
"title": "美国总统(2017-2021、2025-至今),共和党党员",
"tag": "行政主管",
"avatar": "/public/testData/data2.png",
"colorArray": ["#1677FF", "#BAE0FF", "#E6F4FF"]
"colorArray": ["#1677FF", "#BAE0FF", "#E6F4FF"],
"icon": ["1"]
},
{
"name": "詹姆斯·戴维·万斯",
"title": "美国副总统、参议院议长、共和党全国委员会财务主席",
"tag": "行政主管",
"avatar": "/public/testData/data2.png",
"colorArray": ["#1677FF", "#BAE0FF", "#E6F4FF"]
"colorArray": ["#1677FF", "#BAE0FF", "#E6F4FF"],
"icon": []
},
{
"name": "黄仁勋",
"title": "NVIDIA公司创始人兼首席执行官,美国工程院院士",
"tag": "科技企业领袖",
"avatar": "/public/testData/data2.png",
"colorArray": ["#13A8A8", "#87E8DE", "#E6FFFFB"]
"colorArray": ["#13A8A8", "#87E8DE", "#E6FFFFB"],
"icon": ["1"]
},
{
"name": "马尔科·鲁比奥",
"title": "美国国务卿,美国总统国家安全事务临时助理",
"tag": "行政主管",
"avatar": "/public/testData/data2.png",
"colorArray": ["#1677FF", "#BAE0FF", "#E6F4FF"]
"colorArray": ["#1677FF", "#BAE0FF", "#E6F4FF"],
"icon": []
},
{
"name": "埃隆·马斯克",
"title": "特斯拉创始人兼首席执行官、SpaceX、美国党创始人",
"tag": "科技企业领袖",
"avatar": "/public/testData/data2.png",
"colorArray": ["#13A8A8", "#87E8DE", "#E6FFFFB"]
"colorArray": ["#13A8A8", "#87E8DE", "#E6FFFFB"],
"icon": ["1"]
},
{
"name": "乔迪·阿灵顿",
"title": "共和党党员,美国国会众议院议员,预算委员会主席",
"tag": "国会议员",
"avatar": "/public/testData/data2.png",
"colorArray": ["#FAAD14", "#FFE58F", "#FFFBE6"]
"colorArray": ["#FAAD14", "#FFE58F", "#FFFBE6"],
"icon": []
},
{
"name": "霍华德·卢特尼克",
"title": "美国商务部长",
"tag": "行政主管",
"avatar": "/public/testData/data2.png",
"colorArray": ["#1677FF", "#BAE0FF", "#E6F4FF"]
"colorArray": ["#1677FF", "#BAE0FF", "#E6F4FF"],
"icon": ["1"]
},
{
"name": "蒂姆·库克",
"title": "苹果公司首席执行官,清华大学经济管理学院顾问委员会主席",
"tag": "科技企业领袖",
"avatar": "/public/testData/data2.png",
"colorArray": ["#13A8A8", "#87E8DE", "#E6FFFFB"]
"colorArray": ["#13A8A8", "#87E8DE", "#E6FFFFB"],
"icon": []
},
{
"name": "朱棣文",
"title": "第12任美国能源部部长,主要研究领域为原子物理、激光科学",
"tag": "顶级科学家",
"avatar": "/public/testData/data2.png",
"colorArray": ["#722ED1", "#D3ADF7", "#F9F0FF"]
"colorArray": ["#722ED1", "#D3ADF7", "#F9F0FF"],
"icon": ["1"]
},
{
"name": "约翰·图恩",
"title": "共和党党员,美国国会众议院议员",
"tag": "国会议员",
"avatar": "/public/testData/data2.png",
"colorArray": ["#FAAD14", "#FFE58F", "#FFFBE6"]
"colorArray": ["#FAAD14", "#FFE58F", "#FFFBE6"],
"icon": []
},
{
"name": "珍妮弗·道德纳",
"title": "主要研究领域为RNA领域和基因编辑技术",
"tag": "顶级科学家",
"avatar": "/public/testData/data2.png",
"colorArray": ["#722ED1", "#D3ADF7", "#F9F0FF"]
"colorArray": ["#722ED1", "#D3ADF7", "#F9F0FF"],
"icon": ["1"]
},
{
"name": "迈克·约翰逊",
"title": "美国众议院议长",
"tag": "国会议员",
"avatar": "/public/testData/data2.png",
"colorArray": ["#FAAD14", "#FFE58F", "#FFFBE6"]
"colorArray": ["#FAAD14", "#FFE58F", "#FFFBE6"],
"icon": []
},
{
"name": "亚当·史密斯",
"title": "美国国会众议院议员,众议院军事委员会民主党领袖",
"tag": "国会议员",
"avatar": "/public/testData/data2.png",
"colorArray": ["#FAAD14", "#FFE58F", "#FFFBE6"]
"colorArray": ["#FAAD14", "#FFE58F", "#FFFBE6"],
"icon": ["1"]
},
{
"name": "查尔斯·本内特",
"title": "美国国家科学院院士、美国物理学会院士,量子信息论主要创立者",
"tag": "顶级科学家",
"avatar": "/public/testData/data2.png",
"colorArray": ["#722ED1", "#D3ADF7", "#F9F0FF"]
"colorArray": ["#722ED1", "#D3ADF7", "#F9F0FF"],
"icon": []
},
{
"name": "桑达尔·皮查伊",
"title": "谷歌母公司Alphabet首席执行官",
"tag": "科技企业领袖",
"avatar": "/public/testData/data2.png",
"colorArray": ["#13A8A8", "#87E8DE", "#E6FFFFB"]
"colorArray": ["#13A8A8", "#87E8DE", "#E6FFFFB"],
"icon": ["1"]
},
{
"name": "威康·汤姆逊",
"title": "美国国家科学院院士、美国物理学会院士,量子信息论主要创立者",
"tag": "顶级科学家",
"avatar": "/public/testData/data2.png",
"colorArray": ["#722ED1", "#D3ADF7", "#F9F0FF"]
"colorArray": ["#722ED1", "#D3ADF7", "#F9F0FF"],
"icon": []
}
]
# 交互消息类
## 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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论