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

update

...@@ -14,13 +14,13 @@ export function getBillInfo(params) { ...@@ -14,13 +14,13 @@ export function getBillInfo(params) {
// 提出人-根据动议ID获取对应的提出人信息 // 提出人-根据动议ID获取对应的提出人信息
/** /**
* @param {id} * @param {billId}
* @header token * @header token
*/ */
export function getBillPerson(params) { export function getBillPerson(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/billInfoBean/person/${params.id}`, url: `/api/billInfoBean/person/${params.billId}`,
params, params,
}) })
} }
...@@ -63,6 +63,18 @@ export function getBillBackground(params) { ...@@ -63,6 +63,18 @@ export function getBillBackground(params) {
params, params,
}) })
} }
// 相关事件-根据法案ID获取相关事件信息
/**
* @param {id}
* @header token
*/
export function getBillInfoEvent(params) {
return request({
method: 'GET',
url: `/api/billInfoBean/event/${params.id}`,
params,
})
}
// 议员相关性-根据法案ID获取议员分析信息(现在只包括名称 支持 反对,没有标签和事件动态) // 议员相关性-根据法案ID获取议员分析信息(现在只包括名称 支持 反对,没有标签和事件动态)
/** /**
...@@ -92,20 +104,20 @@ export function getBillContentId(params) { ...@@ -92,20 +104,20 @@ export function getBillContentId(params) {
// 主要条款-根据原文ID获取条款内容 // 主要条款-根据原文ID获取条款内容
/** /**
* @param {id,cRelated,currentPage,pageSize} * @param {billid,id,cRelated,currentPage,pageSize}
* @header token * @header token
*/ */
export function getBillContentTk(params) { export function getBillContentTk(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/billInfoBean/content/tk/${params.id}`, url: `/api/billInfoBean/content/tk/${params.billid}/${params.id}`,
params, params,
}) })
} }
// 限制方式-根据法案原文ID获取限制方式列表 // 限制方式-根据法案原文ID获取限制方式列表
/** /**
* @param {id} * @param {billId}
* @header token * @header token
*/ */
export function getBillContentXzfs(params) { export function getBillContentXzfs(params) {
......
...@@ -13,11 +13,11 @@ export function getBillIndustry(params) { ...@@ -13,11 +13,11 @@ export function getBillIndustry(params) {
} }
// 涉华法案统计 // 涉华法案统计
export function getBillCount() { export function getBillCount(params) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/BillOverview/billCount`, url: `/api/BillOverview/billCount`,
params
}) })
} }
...@@ -65,3 +65,34 @@ export function getHylyList() { ...@@ -65,3 +65,34 @@ export function getHylyList() {
url: `/api/billImpactAnalysis/industry/hylyList`, 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) { ...@@ -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获取党派政治献金 // 根据法案ID获取党派政治献金
/** /**
* @param {id, personCongress} * @param {id, personCongress}
......
...@@ -23,7 +23,8 @@ export function getEntitiesDataCount() { ...@@ -23,7 +23,8 @@ export function getEntitiesDataCount() {
return request200( return request200(
request({ request({
method: "GET", method: "GET",
url: "/api/entitiesDataCount/countData" // url: "/api/entitiesDataCount/countData",
url: "/api/sanctionList/export/getTotalInfo"
}) })
); );
} }
...@@ -62,12 +63,15 @@ export function getEntitiesDataInfo() { ...@@ -62,12 +63,15 @@ export function getEntitiesDataInfo() {
* maxCount: number * maxCount: number
* }[]>} * }[]>}
*/ */
export function getIndustryCountByYear() { export function getIndustryCountByYear(sanTypeId) {
return request200( return request200(
request({ request({
method: "GET", method: "GET",
// url: "/api/entitiesDataCount/industryCountByYear" // url: "/api/entitiesDataCount/industryCountByYear"
url: "/api/entitiesDataCount/getAnnualCount" url: "/api/entitiesDataCount/getAnnualCount",
params: {
sanTypeId
}
}) })
); );
} }
...@@ -84,11 +88,16 @@ export function getIndustryCountByYear() { ...@@ -84,11 +88,16 @@ export function getIndustryCountByYear() {
* domains: string[] * domains: string[]
* }>} * }>}
*/ */
export function getCountDomainByYear() { export function getCountDomainByYear(isRule, startYear = "2020", endYear = new Date().getFullYear()) {
return request200( return request200(
request({ request({
method: "GET", method: "POST",
url: "/api/entitiesDataCount/countDomainByYear" url: "/api/entitiesDataCount/getAnnualSanDomain",
data: {
isRule,
startYear,
endYear
}
}) })
); );
} }
...@@ -187,6 +196,21 @@ export function getKeyEntityList(date, keyword = "") { ...@@ -187,6 +196,21 @@ export function getKeyEntityList(date, keyword = "") {
); );
} }
/**
* 区域分布查询
*/
export function getAreaDistribution(date) {
return request200(
request({
method: "GET",
url: "/api/entitiesDataInfo/getRegionDistribution",
params: {
sanctionDate: date || "2025-11-11"
}
})
);
}
/** /**
* 不同领域实体统计 * 不同领域实体统计
* @param {string} startTime - 统计开始时间,格式为 'YYYY-MM-DD' * @param {string} startTime - 统计开始时间,格式为 'YYYY-MM-DD'
...@@ -292,7 +316,7 @@ export function getDomainDistribution(sanctionDate = "2025-11-11") { ...@@ -292,7 +316,7 @@ export function getDomainDistribution(sanctionDate = "2025-11-11") {
* startTime: string * startTime: string
* }[]>} * }[]>}
*/ */
export function getEntitiesList(typeName = "实体清单", pageNum = 1, pageSize = 10) { export function getEntitiesList(typeName = "实体清单", pageNum = 1, pageSize = 10, sanctionDate = "", rule = false) {
return request200( return request200(
request({ request({
method: "POST", method: "POST",
...@@ -300,7 +324,9 @@ export function getEntitiesList(typeName = "实体清单", pageNum = 1, pageSize ...@@ -300,7 +324,9 @@ export function getEntitiesList(typeName = "实体清单", pageNum = 1, pageSize
data: { data: {
typeName, typeName,
pageNum, pageNum,
pageSize pageSize,
sanctionDate,
rule
} }
}) })
); );
...@@ -340,15 +366,16 @@ export function getCompareCountSan(startTime) { ...@@ -340,15 +366,16 @@ export function getCompareCountSan(startTime) {
* count:number * count:number
* }[]>} * }[]>}
*/ */
export function getEntitiesChangeCount(domain, type) { export function getEntitiesChangeCount(domianId, typeId) {
return request200( return request200(
request({ request({
method: "GET", method: "GET",
// url: '/api/entitiesDataCount/entitiesChangeCount', // url: '/api/entitiesDataCount/entitiesChangeCount',
url: "/api/entitiesDataCount/sanCountByYear", // url: "/api/entitiesDataCount/sanCountByYear",
url: "/api/entitiesDataInfo/getCountByDomianAndType",
params: { params: {
domain, domianId,
type typeId
} }
}) })
); );
...@@ -377,11 +404,14 @@ export function getEntitiesGrowthTrend() { ...@@ -377,11 +404,14 @@ export function getEntitiesGrowthTrend() {
* xAxis: string[] * xAxis: string[]
* }>} * }>}
*/ */
export function getEntitiesUpdateCount() { export function getEntitiesUpdateCount(sanTypeId = 1) {
return request200( return request200(
request({ request({
method: "GET", method: "GET",
url: "/api/entitiesDataCount/entitiesUpdateCount" url: "/api/entitiesDataCount/getAnnualCount",
params: {
sanTypeId
}
}) })
); );
} }
...@@ -389,11 +419,14 @@ export function getEntitiesUpdateCount() { ...@@ -389,11 +419,14 @@ export function getEntitiesUpdateCount() {
/** /**
* 制裁领域分析 * 制裁领域分析
*/ */
export function getSanDomainCount() { export function getSanDomainCount(rule) {
return request200( return request200(
request({ request({
method: "GET", method: "GET",
url: "/api/entitiesDataCount/getSanDomainCount" url: "/api/entitiesDataCount/getSanDomainCount",
params: {
rule
}
}) })
); );
} }
......
...@@ -32,6 +32,7 @@ const emit = defineEmits(["click"]); ...@@ -32,6 +32,7 @@ const emit = defineEmits(["click"]);
cursor: pointer; cursor: pointer;
padding-left: 8px; padding-left: 8px;
padding-right: 8px; padding-right: 8px;
flex-shrink: 0;
} }
.activeButton { .activeButton {
border: 1px solid rgb(10, 87, 166); border: 1px solid rgb(10, 87, 166);
......
...@@ -61,5 +61,6 @@ function setActiveIndex(item) { ...@@ -61,5 +61,6 @@ function setActiveIndex(item) {
.buttonList { .buttonList {
display: flex; display: flex;
gap: 8px; gap: 8px;
overflow-x: auto;
} }
</style> </style>
...@@ -35,8 +35,8 @@ ...@@ -35,8 +35,8 @@
v-for="(item, index) in backgroundList" v-for="(item, index) in backgroundList"
:key="item.id" :key="item.id"
> >
<div class="id">{{ index + 1 }}</div> <div class="id">{{ (currentPage - 1) * 10 + index + 1 }}</div>
<div class="title">{{ item.bjnr }}</div> <div class="title">{{ item.backgroundTitle }}</div>
<div class="share"> <div class="share">
<img src="./assets/icons/open.png" alt="打开" /> <img src="./assets/icons/open.png" alt="打开" />
</div> </div>
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
<div class="box1-main-footer"> <div class="box1-main-footer">
<div class="info"> <div class="info">
{{ {{
`共有${backgroundList.length}条${ `共有${total}条${
box1BtnActive === 1 ? "涉华" : "全部" box1BtnActive === 1 ? "涉华" : "全部"
}背景` }背景`
}} }}
...@@ -54,7 +54,9 @@ ...@@ -54,7 +54,9 @@
<el-pagination <el-pagination
background background
layout="prev, pager, next" layout="prev, pager, next"
:total="backgroundList.length" :total="total"
v-model:current-page="currentPage"
@current-change="handleGetBillBackground"
/> />
</div> </div>
</div> </div>
...@@ -80,7 +82,7 @@ ...@@ -80,7 +82,7 @@
:key="index" :key="index"
> >
<div class="left"> <div class="left">
<img :src="item.image" alt="" /> <img :src="item.imageUrl || item.image" alt="" />
</div> </div>
<div class="center"> <div class="center">
<div class="title">{{ item.sjbt }}</div> <div class="title">{{ item.sjbt }}</div>
...@@ -157,7 +159,7 @@ ...@@ -157,7 +159,7 @@
/></el-icon> /></el-icon>
</div> </div>
<div class="right-box1-main-bottom"> <div class="right-box1-main-bottom">
<WordCloudMap :data="wordCloudData" :shape="circle" /> <WordCloudMap :data="wordCloudData" shape="circle" />
</div> </div>
</div> </div>
</div> </div>
...@@ -214,25 +216,26 @@ import { ...@@ -214,25 +216,26 @@ import {
getBillBackground, getBillBackground,
getBillEvent, getBillEvent,
getBillPersonAnalyze, getBillPersonAnalyze,
getBillInfoEvent
} from "@/api/bill"; } from "@/api/bill";
const box1BtnActive = ref(1); const box1BtnActive = ref(1);
const currentPage = ref(1);
const total = ref(0);
const handleClickBox1Btn = (index) => { const handleClickBox1Btn = (index) => {
box1BtnActive.value = index; box1BtnActive.value = index;
if (index === 2) { currentPage.value = 1;
handleGetBillBackground(false); handleGetBillBackground();
} else {
handleGetBillBackground(true);
}
}; };
const box2BtnActive = ref(1); const box2BtnActive = ref(1);
const handleClickBox2Btn = (index) => { const handleClickBox2Btn = (index) => {
box2BtnActive.value = index; box2BtnActive.value = index;
if (index === 1) { if (index === 1) {
handleGetBillPersonAnalyze(true);
} else {
handleGetBillPersonAnalyze(false); handleGetBillPersonAnalyze(false);
} else {
handleGetBillPersonAnalyze(true);
} }
}; };
...@@ -439,27 +442,29 @@ const wordCloudData = [ ...@@ -439,27 +442,29 @@ const wordCloudData = [
]; ];
// 获取立法背景内容 // 获取立法背景内容
const handleGetBillBackground = async (cRelated) => { const handleGetBillBackground = async () => {
const cRelated = box1BtnActive.value === 1 ? 'Y' : 'N';
const params = { const params = {
cRelated: cRelated, cRelated: cRelated,
id: window.sessionStorage.getItem("billId"), id: window.sessionStorage.getItem("billId"),
currentPage: 0, currentPage: currentPage.value - 1,
pageSize: 10, pageSize: 10,
}; };
try { try {
const res = await getBillBackground(params); const res = await getBillBackground(params);
console.log("立法背景", res); console.log("立法背景", res);
backgroundList.value = res.data.content; backgroundList.value = res.data.content;
total.value = res.data.totalElements; // 假设API返回totalElements
} catch (error) {} } catch (error) {}
}; };
// 获取相关事件 // 获取相关事件
const handleGetRelatedEvent = async () => { const handleGetRelatedEvent = async () => {
const params = { const params = {
id: 1, id: window.sessionStorage.getItem("billId"),
}; };
try { try {
const res = await getBillEvent(params); const res = await getBillInfoEvent(params);
console.log("相关事件", res); console.log("相关事件", res);
eventList.value = res.data; eventList.value = res.data;
eventList.value.forEach((item, index) => { eventList.value.forEach((item, index) => {
...@@ -488,8 +493,9 @@ const handleGetBillPersonAnalyze = async (isOppose) => { ...@@ -488,8 +493,9 @@ const handleGetBillPersonAnalyze = async (isOppose) => {
const res = await getBillPersonAnalyze(params); const res = await getBillPersonAnalyze(params);
console.log("议员相关性分析", res); console.log("议员相关性分析", res);
personList.value = res.data; personList.value = res.data;
personList.value.forEach((item) => { personList.value.forEach((item, index) => {
item.image = user1; const imgList = [user1, user2, user3, user4, user5];
item.image = imgList[index % imgList.length];
item.icon = userIcon; item.icon = userIcon;
item.icon1 = userIcon1; item.icon1 = userIcon1;
}); });
...@@ -497,9 +503,9 @@ const handleGetBillPersonAnalyze = async (isOppose) => { ...@@ -497,9 +503,9 @@ const handleGetBillPersonAnalyze = async (isOppose) => {
}; };
onMounted(() => { onMounted(() => {
handleGetBillBackground(true); handleGetBillBackground();
handleGetRelatedEvent(); handleGetRelatedEvent();
handleGetBillPersonAnalyze(true); handleGetBillPersonAnalyze(false);
}); });
</script> </script>
...@@ -572,7 +578,7 @@ onMounted(() => { ...@@ -572,7 +578,7 @@ onMounted(() => {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-between; justify-content: space-between;
align-content: flex-start;
.box1-main-item { .box1-main-item {
width: 544px; width: 544px;
height: 48px; height: 48px;
...@@ -598,11 +604,12 @@ onMounted(() => { ...@@ -598,11 +604,12 @@ onMounted(() => {
width: 440px; width: 440px;
height: 48px; height: 48px;
line-height: 48px; line-height: 48px;
color: rgba(95, 101, 108, 1); color: rgb(59, 65, 75);
font-family: Microsoft YaHei; font-family: "Microsoft YaHei";
font-size: 14px; font-size: 16px;
font-weight: 400; font-weight: 400;
text-align: left; text-align: left;
overflow: hidden;
} }
.share { .share {
margin-left: 13px; margin-left: 13px;
...@@ -626,8 +633,8 @@ onMounted(() => { ...@@ -626,8 +633,8 @@ onMounted(() => {
.info { .info {
height: 22px; height: 22px;
line-height: 22px; line-height: 22px;
color: rgba(132, 136, 142, 1); color: rgb(132, 136, 142);
font-family: Microsoft YaHei; font-family: "Microsoft YaHei";
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
text-align: left; text-align: left;
...@@ -645,6 +652,7 @@ onMounted(() => { ...@@ -645,6 +652,7 @@ onMounted(() => {
margin-top: 9px; margin-top: 9px;
margin-left: 23px; margin-left: 23px;
height: 300px; height: 300px;
overflow: auto;
.box2-main-item { .box2-main-item {
width: 1103px; width: 1103px;
height: 60px; height: 60px;
...@@ -659,9 +667,9 @@ onMounted(() => { ...@@ -659,9 +667,9 @@ onMounted(() => {
width: 64px; width: 64px;
height: 48px; height: 48px;
border-radius: 2px; border-radius: 2px;
image { img {
width: 100px; width: 64px;
height: 100%; height: 48px;
} }
} }
.center { .center {
......
import * as echarts from 'echarts' import * as echarts from 'echarts'
const getMultiLineChart = (dataX, dataY1, dataY2) => { const getMultiLineChart = (dataX, dataY1, dataY2, dataY3) => {
return { return {
tooltip: { tooltip: {
trigger: 'axis', trigger: 'axis',
...@@ -9,25 +9,34 @@ const getMultiLineChart = (dataX, dataY1, dataY2) => { ...@@ -9,25 +9,34 @@ const getMultiLineChart = (dataX, dataY1, dataY2) => {
label: { label: {
backgroundColor: '#6a7985' 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: { grid: {
top: '8%', top: '15%',
right: '5%', right: '5%',
bottom: '5%', bottom: '5%',
left: '5%', left: '5%',
containLabel: true containLabel: true
}, },
legend: { legend: {
data: ['提出法案', '通过法案'], data: ['提出法案', '通过法案', '通过率'],
show: true, show: true,
top: 0,
icon: 'circle',
textStyle: { textStyle: {
color: 'rgba(95, 101, 108, 1)', color: 'rgba(95, 101, 108, 1)',
fontFamily: 'Microsoft YaHei', fontFamily: 'Microsoft YaHei',
fontSize: '16px', fontSize: '14px',
} }
}, },
color: ['#1459bb', '#fa8c16'], color: ['#1677FF', '#FA8C16', '#D9001B'],
xAxis: [ xAxis: [
{ {
type: 'category', type: 'category',
...@@ -37,44 +46,80 @@ const getMultiLineChart = (dataX, dataY1, dataY2) => { ...@@ -37,44 +46,80 @@ const getMultiLineChart = (dataX, dataY1, dataY2) => {
], ],
yAxis: [ 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: [ series: [
{ {
name: '提出法案', name: '提出法案',
type: 'line', type: 'line',
symbol: 'emptyCircle',
symbolSize: 6,
areaStyle: { areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0, offset: 0,
color: 'rgba(22, 119, 255, 1)' // 起始颜色 color: 'rgba(22, 119, 255, 0.4)' // 起始颜色
}, { }, {
offset: 1, offset: 1,
color: 'rgba(22, 119, 255, 0)' // 结束颜色 color: 'rgba(22, 119, 255, 0)' // 结束颜色
}]) }])
}, },
emphasis: { itemStyle: {
focus: 'series' color: '#1677FF'
}, },
data: dataY1 data: dataY1
}, },
{ {
name: '通过法案', name: '通过法案',
type: 'line', type: 'line',
symbol: 'emptyCircle',
symbolSize: 6,
areaStyle: { areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0, offset: 0,
color: 'rgba(255, 172, 77, 1)' // 起始颜色 color: 'rgba(250, 140, 22, 0.4)' // 起始颜色
}, { }, {
offset: 1, offset: 1,
color: 'rgba(255, 172, 77, 0)' // 结束颜色 color: 'rgba(250, 140, 22, 0)' // 结束颜色
}]) }])
}, },
emphasis: { itemStyle: {
focus: 'series' color: '#FA8C16'
}, },
data: dataY2 data: dataY2
},
{
name: '通过率',
type: 'line',
yAxisIndex: 1,
symbol: 'emptyCircle',
symbolSize: 4,
lineStyle: {
type: 'dashed',
width: 2
},
itemStyle: {
color: '#D9001B'
},
data: dataY3
} }
] ]
} }
......
...@@ -11,6 +11,8 @@ const getWordCloudChart = (data) => { ...@@ -11,6 +11,8 @@ const getWordCloudChart = (data) => {
series: [ series: [
{ {
type: "wordCloud", type: "wordCloud",
width: '80%',
height: '80%',
shape: "rect", // shape: "rect", //
// 其他形状你可以使用形状路径 // 其他形状你可以使用形状路径
// 或者自定义路径 // 或者自定义路径
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
<div class="box2"> <div class="box2">
<div class="box-header"> <div class="box-header">
<div class="header-left"></div> <div class="header-left"></div>
<div class="title">辩论投票时长</div> <div class="title">修正案次数分析</div>
<div class="header-right"> <div class="header-right">
<div class="icon"> <div class="icon">
<img src="@/assets/icons/box-header-icon1.png" alt="" /> <img src="@/assets/icons/box-header-icon1.png" alt="" />
...@@ -320,7 +320,7 @@ ...@@ -320,7 +320,7 @@
<script setup> <script setup>
import { ref, onMounted } from "vue"; 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 getBoxPlotChcart from "./utils/boxplot";
import * as echarts from "echarts"; import * as echarts from "echarts";
...@@ -563,20 +563,69 @@ const handleGetBillTimeAnalyze = async () => { ...@@ -563,20 +563,69 @@ const handleGetBillTimeAnalyze = async () => {
return item.lcmc; return item.lcmc;
}); });
chartData1.value.dataY = res.data.map(item => { 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) {} } 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 () => { onMounted(async () => {
await handleGetBillTimeAnalyze(); await handleGetBillTimeAnalyze();
await handleGetBillAmeAnalyzeCount();
await handleGetBillVoteAnalyze();
let chart1 = getBoxPlotChcart(chartData1.value, "天"); let chart1 = getBoxPlotChcart(chartData1.value, "天");
setChart(chart1, "chart1"); 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"); setChart(chart2, "chart2");
}); });
</script> </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 = { let option = {
// title: [ // title: [
// { // {
...@@ -11,6 +20,22 @@ const getBoxPlotChcart = (data,unit) => { ...@@ -11,6 +20,22 @@ const getBoxPlotChcart = (data,unit) => {
trigger: 'item', trigger: 'item',
axisPointer: { axisPointer: {
type: 'shadow' 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: { grid: {
...@@ -48,13 +73,20 @@ const getBoxPlotChcart = (data,unit) => { ...@@ -48,13 +73,20 @@ const getBoxPlotChcart = (data,unit) => {
{ {
name: 'boxplot', name: 'boxplot',
type: 'boxplot', type: 'boxplot',
datasetIndex: 1,
data: data.dataY, data: data.dataY,
}, },
{ {
name: 'outlier', name: labels.current,
type: 'scatter', 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> <template>
<div class="container"> <div class="container">
<div class="svg-timeline"> <div class="svg-timeline">
<svg :width="svgWidth" :height="svgHeight"> <svg :viewBox="`0 0 ${svgWidth} ${svgHeight}`" width="100%" height="100%">
<!-- <line <!-- <line
:x1="lines[0].x1 - 100" :x1="lines[0].x1 - 100"
:y1="lines[0].y1" :y1="lines[0].y1"
...@@ -31,10 +31,11 @@ ...@@ -31,10 +31,11 @@
stroke-width="2" stroke-width="2"
/> --> /> -->
<line <line
:x1="lines[0].x1 - 100" v-if="nodes.length > 0"
:y1="lines[0].y1" :x1="nodes[0].x - 100"
:x2="lines[0].x1" :y1="nodes[0].y"
:y2="lines[0].y1" :x2="nodes[0].x"
:y2="nodes[0].y"
stroke="#e8f2ff" stroke="#e8f2ff"
stroke-width="2" stroke-width="2"
marker-end="url(#arrow)" marker-end="url(#arrow)"
...@@ -68,51 +69,60 @@ ...@@ -68,51 +69,60 @@
v-for="(node, idx) in nodes.slice(0, nodes.length)" v-for="(node, idx) in nodes.slice(0, nodes.length)"
:key="'line' + idx" :key="'line' + idx"
:x1="node.x" :x1="node.x"
:y1="node.y + 2" :y1="node.y + 4"
:x2="node.x" :x2="node.x"
:y2="node.dyms ? node.y + 80 : node.y + 50" :y2="node.y + verticalLineLength"
stroke="#1677ff" stroke="#1677ff"
stroke-width="2" :stroke-width="verticalLineWidth"
/> />
<text <foreignObject
v-for="(node, idx) in nodes" v-for="(node, idx) in nodes"
:key="'actionDate' + idx" :key="'fo-' + idx"
:x="node.x + 10" :x="node.x + 15"
:y="node.y + 30" :y="node.y + 5"
text-anchor="start" :width="nodeGapX - 30"
fill="#1677ff" height="100"
font-size="14" style="overflow: visible;"
font-weight="600"
> >
{{ node.actionDate }} <div class="node-content" xmlns="http://www.w3.org/1999/xhtml">
</text> <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 <text
v-for="(node, idx) in nodes" v-if="startMonth && nodes.length > 0"
:key="'actionTitle' + idx" :x="nodes[0].x - 110"
:x="node.x + 10" :y="nodes[0].y + 5"
:y="node.y + 50" text-anchor="end"
text-anchor="start" fill="rgb(5, 95, 194)"
fill="#3b414b" font-size="16"
font-size="14" font-weight="700"
textLength="170"
lengthAdjust="spacing"
font-weight="bold"
> >
{{ node.actionTitle.slice(0,24) }} {{ startMonth }}
</text> </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 <text
v-for="(node, idx) in nodes" v-if="endMonth && nodes.length > 0"
:key="'actionTitle' + idx" :x="nodes[nodes.length - 1].row % 2 === 0 ? nodes[nodes.length - 1].x + 110 : nodes[nodes.length - 1].x - 110"
:x="node.x + 10" :y="nodes[nodes.length - 1].y + 5"
:y="node.y + 70" :text-anchor="nodes[nodes.length - 1].row % 2 === 0 ? 'start' : 'end'"
text-anchor="start" fill="rgb(5, 95, 194)"
fill="#84888e" font-size="16"
font-size="14" font-weight="700"
> >
{{ node.dyms ? node.dyms : "" }} {{ endMonth }}
</text> </text>
</svg> </svg>
</div> </div>
...@@ -134,26 +144,55 @@ export default { ...@@ -134,26 +144,55 @@ export default {
// ], // ],
maxPerRow: 5, maxPerRow: 5,
nodeGapX: 200, nodeGapX: 200,
nodeGapY: 100 nodeGapY: 180,
leftMargin: 150,
verticalLineLength: 80,
verticalLineWidth: 1
}; };
}, },
computed: { 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() { nodes() {
// 计算每个节点的坐标(蛇形) // 计算每个节点的坐标(蛇形)
return this.dataList.map((item, idx) => { return this.sortedDataList.map((item, idx) => {
const row = Math.floor(idx / this.maxPerRow); const row = Math.floor(idx / this.maxPerRow);
const col = idx % this.maxPerRow; const col = idx % this.maxPerRow;
let x, y; let x, y;
const leftMargin = 10; // 你可以自定义这个值 // const leftMargin = 150; // 你可以自定义这个值
if (row % 2 === 0) { if (row % 2 === 0) {
x = leftMargin + col * this.nodeGapX + 50; x = this.leftMargin + col * this.nodeGapX + 50;
} else { } 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; 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() { lines() {
...@@ -194,7 +233,7 @@ export default { ...@@ -194,7 +233,7 @@ export default {
console.log("prev", prev); console.log("prev", prev);
// 判断是否是行尾转折点 // 判断是否是行尾转折点
const isTurnPoint = i % 5 === 0; const isTurnPoint = i % this.maxPerRow === 0;
if (isTurnPoint) { if (isTurnPoint) {
// 计算半圆路径 // 计算半圆路径
...@@ -221,7 +260,7 @@ export default { ...@@ -221,7 +260,7 @@ export default {
return path; return path;
}, },
svgWidth() { svgWidth() {
return this.maxPerRow * this.nodeGapX + 100; return this.leftMargin + this.maxPerRow * this.nodeGapX + 50;
}, },
svgHeight() { svgHeight() {
// SVG高度 // SVG高度
...@@ -240,7 +279,7 @@ export default { ...@@ -240,7 +279,7 @@ export default {
align-items: center; align-items: center;
} }
.svg-timeline { .svg-timeline {
width: 1000px; width: 100%;
// background-size: 100% 100%; // background-size: 100% 100%;
// position: relative; // position: relative;
// .title { // .title {
...@@ -264,5 +303,40 @@ export default { ...@@ -264,5 +303,40 @@ export default {
// height: 24px; // 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> </style>
...@@ -31,9 +31,10 @@ ...@@ -31,9 +31,10 @@
<div class="box1-right-item"> <div class="box1-right-item">
<div class="item-left">相关领域:</div> <div class="item-left">相关领域:</div>
<div class="item-right1"> <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">半导体产业</div> -->
<div class="right1-item" v-for="item in basicInfo.hylyList" :key="item">{{ item }}</div>
</div> </div>
</div> </div>
<div class="box1-right-item"> <div class="box1-right-item">
...@@ -81,7 +82,7 @@ ...@@ -81,7 +82,7 @@
<div class="box-header"> <div class="box-header">
<div class="header-left"></div> <div class="header-left"></div>
<div class="title">法案进展</div> <div class="title">法案进展</div>
<div class="header-btn-box"> <!-- <div class="header-btn-box">
<div class="btn" @click="handleClcikBox2Btn(1)"> <div class="btn" @click="handleClcikBox2Btn(1)">
<el-badge :value="warningNum"> <el-badge :value="warningNum">
<el-button type="primary" plain v-if="box2BtnActive === 1">最新进展</el-button> <el-button type="primary" plain v-if="box2BtnActive === 1">最新进展</el-button>
...@@ -92,7 +93,7 @@ ...@@ -92,7 +93,7 @@
<el-button type="primary" plain v-if="box2BtnActive === 2">前期进展</el-button> <el-button type="primary" plain v-if="box2BtnActive === 2">前期进展</el-button>
<el-button type="info" plain v-else>前期进展</el-button> <el-button type="info" plain v-else>前期进展</el-button>
</div> </div>
</div> </div> -->
<div class="header-right"> <div class="header-right">
<div class="icon"> <div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" /> <img src="@/assets/icons/box-header-icon2.png" alt="" />
...@@ -104,8 +105,8 @@ ...@@ -104,8 +105,8 @@
</div> </div>
<div class="box2-main"> <div class="box2-main">
<div class="box2-main-center"> <div class="box2-main-center">
<STimeline v-if="box2BtnActive == 2" :dataList="timelineData" /> <STimeline :dataList="timelineData" />
<div class="box2-center-item-box" v-if="box2BtnActive == 1"> <!-- <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="box2-center-item" v-for="(item, index) in progressList" :key="index">
<div class="tip" :class="{ tipActive: item.fxdj }"></div> <div class="tip" :class="{ tipActive: item.fxdj }"></div>
<div class="date">{{ item.actionDate }}</div> <div class="date">{{ item.actionDate }}</div>
...@@ -128,7 +129,7 @@ ...@@ -128,7 +129,7 @@
<el-icon size="22" color="#777"><ArrowRightBold /></el-icon> <el-icon size="22" color="#777"><ArrowRightBold /></el-icon>
</div> </div>
</div> </div>
</div> </div> -->
</div> </div>
</div> </div>
<div class="box2-footer"> <div class="box2-footer">
...@@ -210,15 +211,15 @@ ...@@ -210,15 +211,15 @@
<div <div
class="tag-box" class="tag-box"
:class="{ :class="{
status0: tag.status === 0, status0: index === 0 || index === 4,
status1: tag.status === 1, status1: index === 1 || index === 5,
status2: tag.status === 2, status2: index === 2 || index === 6,
status3: tag.status === 3 status3: index === 3 || index === 7
}" }"
v-for="(tag, index) in tagList" v-for="(tag, index) in curPerson.tagList"
:key="index" :key="index"
> >
{{ tag.title }} {{ tag }}
</div> </div>
</div> </div>
<div class="right-main-box3"> <div class="right-main-box3">
...@@ -231,13 +232,13 @@ ...@@ -231,13 +232,13 @@
<div class="right-main-box3-main"> <div class="right-main-box3-main">
<el-timeline style="max-width: 500px"> <el-timeline style="max-width: 500px">
<el-timeline-item <el-timeline-item
:timestamp="item.sjsj" :timestamp="item.newsDate"
placement="top" placement="top"
v-for="(item, index) in personEventList" v-for="(item, index) in curPerson.newsList"
:key="index" :key="index"
> >
<div class="timeline-content"> <div class="timeline-content">
{{ item.sjnr }} {{ item.newsContent }}
</div> </div>
</el-timeline-item> </el-timeline-item>
<!-- <el-timeline-item timestamp="2018/4/3" placement="top"> <!-- <el-timeline-item timestamp="2018/4/3" placement="top">
...@@ -324,14 +325,14 @@ ...@@ -324,14 +325,14 @@
<div class="inner-right-main"> <div class="inner-right-main">
<el-timeline style="max-width: 840px"> <el-timeline style="max-width: 840px">
<el-timeline-item <el-timeline-item
:timestamp="item.sjsj" :timestamp="item.newsDate"
placement="top" placement="top"
v-for="(item, index) in personEventList" v-for="(item, index) in curPerson.newsList"
:key="index" :key="index"
> >
<div class="timeline-content1"> <div class="timeline-content1">
<div class="text"> <div class="text">
{{ item.sjnr }} {{ item.newsContent }}
</div> </div>
<div class="pic"> <div class="pic">
<img src="./assets/imgs/img1.png" alt="" /> <img src="./assets/imgs/img1.png" alt="" />
...@@ -544,6 +545,7 @@ const handleGetBasicInfo = async () => { ...@@ -544,6 +545,7 @@ const handleGetBasicInfo = async () => {
const res = await getBillInfo(params); const res = await getBillInfo(params);
console.log("基本信息", res); console.log("基本信息", res);
basicInfo.value = res.data basicInfo.value = res.data
basicInfo.value.stageList.reverse()
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
...@@ -552,24 +554,24 @@ const handleGetBasicInfo = async () => { ...@@ -552,24 +554,24 @@ const handleGetBasicInfo = async () => {
const warningNum = ref(0); const warningNum = ref(0);
// 法案进展 获取最新进展 // 法案进展 获取最新进展
const handleGetBillEvent = async () => { // const handleGetBillEvent = async () => {
warningNum.value = 0; // warningNum.value = 0;
const params = { // const params = {
id: window.sessionStorage.getItem("billId") // id: window.sessionStorage.getItem("billId")
}; // };
try { // try {
const res = await getBillEvent(params); // const res = await getBillEvent(params);
console.log("最新进展", res); // console.log("最新进展", res);
progressList.value = res.data; // progressList.value = res.data;
progressList.value.forEach(item => { // progressList.value.forEach(item => {
if (item.fxdj) { // if (item.fxdj) {
warningNum.value++; // warningNum.value++;
} // }
}); // });
} catch (error) { // } catch (error) {
console.error(error); // console.error(error);
} // }
}; // };
// 法案进展 获取前期进展 --也是提出人左上角列表 // 法案进展 获取前期进展 --也是提出人左上角列表
const handleGetBillDyqk = async () => { const handleGetBillDyqk = async () => {
...@@ -601,9 +603,26 @@ const curPerson = ref({}); ...@@ -601,9 +603,26 @@ const curPerson = ref({});
const personEventList = ref([]); const personEventList = ref([]);
// 提出人 --动议id // 提出人 --动议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 = { const params = {
id: id billId: window.sessionStorage.getItem("billId")
}; };
try { try {
const res = await getBillPerson(params); const res = await getBillPerson(params);
...@@ -619,8 +638,9 @@ const handleGetBillPerson = async id => { ...@@ -619,8 +638,9 @@ const handleGetBillPerson = async id => {
onMounted(() => { onMounted(() => {
handleGetBasicInfo(); handleGetBasicInfo();
handleGetBillEvent(); // handleGetBillEvent();
handleGetBillDyqk(); handleGetBillDyqk();
handleGetBillPerson();
}); });
</script> </script>
...@@ -695,11 +715,11 @@ onMounted(() => { ...@@ -695,11 +715,11 @@ onMounted(() => {
.box1-right { .box1-right {
margin-left: 31px; margin-left: 31px;
margin-top: 5px; margin-top: 5px;
width: 623px; // width: 623px;
height: 350px; height: 350px;
.box1-right-item { .box1-right-item {
display: flex; display: flex;
margin-bottom: 24px; margin-bottom: 21px;
.item-left { .item-left {
width: 100px; width: 100px;
height: 14px; height: 14px;
...@@ -722,7 +742,23 @@ onMounted(() => { ...@@ -722,7 +742,23 @@ onMounted(() => {
} }
.item-right1 { .item-right1 {
display: flex; 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 { .right1-item {
flex-shrink: 0;
margin-right: 10px; margin-right: 10px;
padding: 1px 8px; padding: 1px 8px;
box-sizing: border-box; box-sizing: border-box;
...@@ -774,10 +810,14 @@ onMounted(() => { ...@@ -774,10 +810,14 @@ onMounted(() => {
text-align: center; text-align: center;
position: relative; position: relative;
.step-box { .step-box {
padding: 0 10px 0 20px; padding: 4px 10px;
color: #333; color: #333;
position: relative; position: relative;
font-size: 14px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 14px;
margin-left: 10px;
.right-arrow { .right-arrow {
position: absolute; position: absolute;
right: -21px; right: -21px;
...@@ -792,7 +832,7 @@ onMounted(() => { ...@@ -792,7 +832,7 @@ onMounted(() => {
} }
} }
.step-box-active { .step-box-active {
padding: 0 12px; padding: 4 10px;
color: #fff; color: #fff;
background: #ce4f51; background: #ce4f51;
position: relative; position: relative;
...@@ -851,11 +891,13 @@ onMounted(() => { ...@@ -851,11 +891,13 @@ onMounted(() => {
box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1); box-shadow: 0px 0px 15px 0px rgba(22, 119, 255, 0.1);
.box2-main { .box2-main {
margin-top: 10px; margin-top: 10px;
height: calc(100% - 70px); // Subtract header height
width: 100%;
.box2-main-center { .box2-main-center {
margin-left: 23px; margin-left: 23px;
border-top: 1px solid rgba(243, 243, 244, 1); border-top: 1px solid rgba(243, 243, 244, 1);
// width: 100%; width: calc(100% - 46px); // Subtract margin
height: 300px; height: 100%;
// background: orange; // background: orange;
.box2-center-item { .box2-center-item {
display: flex; display: flex;
...@@ -1082,10 +1124,10 @@ onMounted(() => { ...@@ -1082,10 +1124,10 @@ onMounted(() => {
} }
} }
.right-main-box2 { .right-main-box2 {
width: 576px; // width: 576px;
height: 150px; // height: 150px;
box-sizing: border-box; box-sizing: border-box;
padding: 10px 26px; padding: 22px 26px;
// border-bottom: 1px solid rgb(243, 243, 244); // border-bottom: 1px solid rgb(243, 243, 244);
// display: flex; // display: flex;
// flex-wrap: wrap; // flex-wrap: wrap;
...@@ -1140,7 +1182,7 @@ onMounted(() => { ...@@ -1140,7 +1182,7 @@ onMounted(() => {
} }
.right-main-box3-main { .right-main-box3-main {
margin-top: 18px; margin-top: 18px;
height: 299px; height: 412px;
overflow-y: auto; overflow-y: auto;
} }
.right-main-box3-footer { .right-main-box3-footer {
...@@ -1330,6 +1372,10 @@ onMounted(() => { ...@@ -1330,6 +1372,10 @@ onMounted(() => {
color: var(--btn-active-text-color); color: var(--btn-active-text-color);
} }
} }
.inner-right-main {
height: 860px;
overflow: auto;
}
} }
} }
} }
......
...@@ -16,9 +16,15 @@ ...@@ -16,9 +16,15 @@
</div> </div>
<div class="left-top"> <div class="left-top">
<el-select v-model="curBill" placeholder="请选择" style="width: 240px" @change="handleChangeBill"> <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-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"> <div class="search" style="width: 240px; margin-left: 475px">
<el-input v-model="searchValue" style="width: 240px" placeholder="搜索条款" /> <el-input v-model="searchValue" style="width: 240px" placeholder="搜索条款" />
<div class="icon"> <div class="icon">
...@@ -28,32 +34,32 @@ ...@@ -28,32 +34,32 @@
</div> </div>
<div class="left-main"> <div class="left-main">
<div class="left-main-item" v-for="(term, index) in mainTermsList" :key="index"> <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="info">
<div class="title"> <div class="title">
<span class="title-active">{{ term.header }}</span> <span class="title-active">{{ term.tkxh }}条.</span>
{{ term.title }} {{ term.fynr }}
</div> </div>
<div class="content"> <div class="content">
<span class="content-active">{{ term.headerEn }}</span> <span class="content-active">Sec.{{ term.tkxh }}</span>
{{ term.content }} {{ term.ywnr }}
</div> </div>
</div> </div>
<div class="tags-box"> <div class="tags-box">
<div <div
class="tag" class="tag"
v-for="(val, idx) in term.tags" v-for="(val, idx) in (term.hylyList || []).slice(0, 2)"
:key="idx" :key="idx"
:class="{ :class="{
tag1: val.status === 1, 'tag1': val === '人工智能',
tag2: val.status === 2, 'tag2': val === '新一代信息技术' || !['人工智能', '政治', '经济', '军事', '科技'].includes(val),
tag3: val.status === 3, 'tag3': val === '政治',
tag4: val.status === 4, 'tag4': val === '经济',
tag5: val.status === 5, 'tag5': val === '军事',
tag6: val.status === 6 'tag6': val === '科技'
}" }"
> >
{{ val.name }} {{ val }}
</div> </div>
</div> </div>
<div class="open"> <div class="open">
...@@ -63,10 +69,17 @@ ...@@ -63,10 +69,17 @@
</div> </div>
<div class="left-footer"> <div class="left-footer">
<div class="left-footer-text"> <div class="left-footer-text">
{{ "共96条涉华条款" }} {{ `共${total}条${checkedValue ? "涉华" : ""}条款` }}
</div> </div>
<div class="left-footer-right"> <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> </div>
</div> </div>
...@@ -137,16 +150,12 @@ import { getBillContentId, getBillContentTk, getBillContentXzfs, getBillHyly } f ...@@ -137,16 +150,12 @@ import { getBillContentId, getBillContentTk, getBillContentXzfs, getBillHyly } f
const curBill = ref(""); const curBill = ref("");
const curBillId = ref(null); const curBillId = ref(null);
const billList = ref([ const checkedValue = ref(false);
{ const searchValue = ref("");
value: "公法(2025年7月4日)", const billList = ref([]);
label: "公法(2025年7月4日)" const currentPage = ref(1);
}, const pageSize = ref(10);
{ const total = ref(0);
value: "公法(2025年7月8日)",
label: "公法(2025年7月8日)"
}
]);
const mainTermsList = ref([ const mainTermsList = ref([
{ {
...@@ -351,7 +360,13 @@ const setChart = (option, chartId) => { ...@@ -351,7 +360,13 @@ const setChart = (option, chartId) => {
// 切换原文 // 切换原文
const handleChangeBill = val => { 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列表 // 获取法案id列表
...@@ -369,22 +384,69 @@ const handleGetBillList = async () => { ...@@ -369,22 +384,69 @@ const handleGetBillList = async () => {
id: item.ywid id: item.ywid
}; };
}); });
curBill.value = billList.value[0].label; if (billList.value.length > 0) {
curBill.value = billList.value[0].value;
curBillId.value = billList.value[0].id; curBillId.value = billList.value[0].id;
}
} catch (error) {} } catch (error) {}
}; };
const handleChangeCheckbox = val => {
currentPage.value = 1;
handleGetBillContentTk(val ? "Y" : "N");
};
const handleCurrentChange = val => {
currentPage.value = val;
handleGetBillContentTk(checkedValue.value ? "Y" : "N");
};
// 根据原文ID获取条款列表 // 根据原文ID获取条款列表
const handleGetBillContentTk = async cRelated => { const handleGetBillContentTk = async cRelated => {
const params = { const params = {
id: curBillId.value, billid: window.sessionStorage.getItem("billId"),
id: curBill.value,
cRelated: cRelated, cRelated: cRelated,
currentPage: 0, currentPage: currentPage.value - 1,
pageSize: 10 pageSize: pageSize.value
}; };
try { try {
const res = await getBillContentTk(params); const res = await getBillContentTk(params);
console.log("条款内容", res); 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) {} } catch (error) {}
}; };
...@@ -430,7 +492,7 @@ const handleGetBillHyly = async () => { ...@@ -430,7 +492,7 @@ const handleGetBillHyly = async () => {
onMounted(async () => { onMounted(async () => {
await handleGetBillList(); await handleGetBillList();
handleGetBillContentTk(false); handleGetBillContentTk("N");
await handleGetBillContentXzfs(); await handleGetBillContentXzfs();
await handleGetBillHyly(); await handleGetBillHyly();
let chart1 = getPieChart(chart1Data.value, chart1ColorList.value); let chart1 = getPieChart(chart1Data.value, chart1ColorList.value);
...@@ -528,6 +590,7 @@ onMounted(async () => { ...@@ -528,6 +590,7 @@ onMounted(async () => {
border-radius: 2px; border-radius: 2px;
background: rgba(255, 255, 255, 1); background: rgba(255, 255, 255, 1);
display: flex; display: flex;
position: relative;
.id { .id {
margin-top: 20px; margin-top: 20px;
margin-left: 15px; margin-left: 15px;
...@@ -543,7 +606,7 @@ onMounted(async () => { ...@@ -543,7 +606,7 @@ onMounted(async () => {
.info { .info {
margin-left: 13px; margin-left: 13px;
margin-top: 15px; margin-top: 15px;
width: 813px; width: 780px;
.title { .title {
height: 14px; height: 14px;
color: rgba(59, 65, 75, 1); color: rgba(59, 65, 75, 1);
...@@ -577,20 +640,19 @@ onMounted(async () => { ...@@ -577,20 +640,19 @@ onMounted(async () => {
} }
} }
.tags-box { .tags-box {
margin-left: 20px;
margin-top: 21px;
width: 160px;
height: 22px;
display: flex; display: flex;
justify-content: flex-end; justify-content: right;
align-items: center;
flex: 1;
margin-right: 50px;
.tag { .tag {
height: 18px; text-align: right;
line-height: 18px; line-height: 18px;
padding: 0 8px; padding: 1px 8px;
border-radius: 4px; border-radius: 4px;
margin-left: 5px; margin-left: 5px;
font-size: 12px; font-size: 12px;
font-family: Microsoft YaHei; font-family: "Microsoft YaHei";
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
} }
...@@ -626,8 +688,9 @@ onMounted(async () => { ...@@ -626,8 +688,9 @@ onMounted(async () => {
} }
} }
.open { .open {
margin-left: 10px; position: absolute;
margin-top: 22px; top: 22px;
right: 23px;
width: 20px; width: 20px;
height: 20px; height: 20px;
img { img {
......
...@@ -4,6 +4,7 @@ const getPieChart = (data,colorList) => { ...@@ -4,6 +4,7 @@ const getPieChart = (data,colorList) => {
series: [ series: [
{ {
type: 'pie', type: 'pie',
minAngle: 28,
radius: [70, 100], radius: [70, 100],
height: '100%', height: '100%',
left: 'center', left: 'center',
...@@ -38,7 +39,9 @@ const getPieChart = (data,colorList) => { ...@@ -38,7 +39,9 @@ const getPieChart = (data,colorList) => {
? params.labelRect.x ? params.labelRect.x
: params.labelRect.x + params.labelRect.width; : params.labelRect.x + params.labelRect.width;
return { return {
labelLinePoints: points labelLinePoints: points,
hideOverlap: false,
moveOverlap: 'shiftY'
}; };
}, },
data: data data: data
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<div class="chartsWrap"> <div class="chartsWrap">
<div class="right-main-content"> <div class="right-main-content">
<div class="right-main-content-main"> <div class="right-main-content-main">
<Fishbone /> <Fishbone :chainId="activeButtonId" />
</div> </div>
<div class="right-main-content-footer"> <div class="right-main-content-footer">
<div class="footer-item1"> <div class="footer-item1">
...@@ -76,30 +76,12 @@ import Echarts from "@/components/Chart/index.vue"; ...@@ -76,30 +76,12 @@ import Echarts from "@/components/Chart/index.vue";
import Hint from "./hint.vue"; import Hint from "./hint.vue";
import ButtonList from "@/components/buttonList/buttonList.vue"; import ButtonList from "@/components/buttonList/buttonList.vue";
import Fishbone from "./fishbone.vue"; import Fishbone from "./fishbone.vue";
import college1 from "../../assets/images/college1.png";
import college2 from "../../assets/images/college2.png";
import college3 from "../../assets/images/college3.png";
import college4 from "../../assets/images/college4.png";
import college5 from "../../assets/images/college5.png";
import college6 from "../../assets/images/college6.png";
import college7 from "../../assets/images/college7.png";
import college8 from "../../assets/images/college8.png";
import college9 from "../../assets/images/college9.png";
import college10 from "../../assets/images/college10.png";
import college11 from "../../assets/images/college11.png";
import { getHorizontalBarChart2 } from "../../utils/charts"; import { getHorizontalBarChart2 } from "../../utils/charts";
import { getDomainDistribution, getChainEntities } from "@/api/exportControl"; import { getDomainDistribution, getChainEntities } from "@/api/exportControl";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
const route = useRoute(); const route = useRoute();
const buttonList = ref([ const buttonList = ref([]);
{ id: 1, text: "新能源" }, const activeButtonId = ref(buttonList.value[0]?.id || 1);
{ id: 2, text: "半导体" },
{ id: 3, text: "跨境电商" },
{ id: 4, text: "金融业" },
{ id: 5, text: "军工" },
{ id: 6, text: "贸易" }
]);
const activeButtonId = ref(buttonList.value[0].id);
const setActiveButtonId = id => { const setActiveButtonId = id => {
activeButtonId.value = id; activeButtonId.value = id;
}; };
...@@ -178,10 +160,12 @@ const fetchDomainDistribution = async () => { ...@@ -178,10 +160,12 @@ const fetchDomainDistribution = async () => {
horizontalBarOptions.value = getHorizontalBarChart2(yAxisData, seriesData, false); horizontalBarOptions.value = getHorizontalBarChart2(yAxisData, seriesData, false);
// 更新buttonList // 更新buttonList
buttonList.value = sortedData.map(item => ({ buttonList.value = sortedData
.map(item => ({
id: item.id, id: item.id,
text: item.name text: item.name
})); }))
.sort((a, b) => a.id - b.id);
console.log("buttonList.value", buttonList.value); console.log("buttonList.value", buttonList.value);
setActiveButtonId(buttonList.value[0].id); setActiveButtonId(buttonList.value[0].id);
} }
......
...@@ -8,9 +8,23 @@ ...@@ -8,9 +8,23 @@
import Echarts from "@/components/Chart/index.vue"; import Echarts from "@/components/Chart/index.vue";
import { getMapOption } from "../../utils/charts"; import { getMapOption } from "../../utils/charts";
import { ref, onMounted, shallowRef } from "vue"; import { ref, onMounted, shallowRef } from "vue";
import { getAreaDistribution } from "@/api/exportControl";
// 这儿接收父组件传递过来的date参数
const props = defineProps({
date: {
type: String,
default: ""
}
});
const mapOption = shallowRef({}); const mapOption = shallowRef({});
onMounted(() => { onMounted(() => {
mapOption.value = getMapOption(); mapOption.value = getMapOption();
// 区域分布查询
getAreaDistribution(props.date).then(res => {
console.log("res", res);
});
}); });
</script> </script>
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
size="mini" size="mini"
v-model="domainValue" v-model="domainValue"
placeholder="领域选择" placeholder="领域选择"
@change="handleDomainChange"
> >
<el-option v-for="item in domainOptions" :key="item.value" :label="item.label" :value="item.value"> <el-option v-for="item in domainOptions" :key="item.value" :label="item.label" :value="item.value">
</el-option> </el-option>
...@@ -24,7 +25,8 @@ ...@@ -24,7 +25,8 @@
</template> </template>
<div class="subPanel1"> <div class="subPanel1">
<div class="chartsWrap" :style="{ paddingBottom: '10px' }"> <div class="chartsWrap" :style="{ paddingBottom: '10px' }">
<Echarts :option="bar1Option" height="100%"></Echarts> <Echarts v-if="!bar1DataIsEmpty" :option="bar1Option" height="100%"></Echarts>
<el-empty v-else description="暂无数据" />
</div> </div>
<Hint text="近几次新增受制裁实体中,中国实体占比提高,美方针对中国的出口管制风险显著增加。"></Hint> <Hint text="近几次新增受制裁实体中,中国实体占比提高,美方针对中国的出口管制风险显著增加。"></Hint>
</div> </div>
...@@ -123,7 +125,7 @@ onMounted(async () => { ...@@ -123,7 +125,7 @@ onMounted(async () => {
try { try {
const [entitiesGrowthTrendData, entitiesUpdateCountData] = await Promise.all([ const [entitiesGrowthTrendData, entitiesUpdateCountData] = await Promise.all([
getEntitiesGrowthTrend(), getEntitiesGrowthTrend(),
getEntitiesUpdateCount() getEntitiesUpdateCount(1)
]); ]);
const list = _.reverse(entitiesGrowthTrendData); const list = _.reverse(entitiesGrowthTrendData);
...@@ -133,12 +135,25 @@ onMounted(async () => { ...@@ -133,12 +135,25 @@ onMounted(async () => {
}); });
line1Option.value = getLineChart({ xAxisData, seriesData, name: "增长趋势", color: "rgba(146, 84, 222, 1)" }, true); line1Option.value = getLineChart({ xAxisData, seriesData, name: "增长趋势", color: "rgba(146, 84, 222, 1)" }, true);
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( bar2Option.value = getBarChart(
_.reverse(entitiesUpdateCountData.xAxis), _.reverse(entitiesUpdateCountData.xAxis),
_.reverse(entitiesUpdateCountData.series), _.reverse(entitiesUpdateCountData.series),
["rgba(22, 119, 255, 1)", "rgba(22, 119, 255, 0)"], ["rgba(22, 119, 255, 1)", "rgba(22, 119, 255, 0)"],
"更新频率" "更新频率"
); );
}
// 获取重点实体列表数据 // 获取重点实体列表数据
await fetchKeyEntityList(route.query.startTime); await fetchKeyEntityList(route.query.startTime);
} catch (err) { } catch (err) {
...@@ -248,11 +263,13 @@ const typeOptions = [ ...@@ -248,11 +263,13 @@ const typeOptions = [
const domainValue = ref(domainOptions[0].value); const domainValue = ref(domainOptions[0].value);
const typeValue = ref(typeOptions[0].value); const typeValue = ref(typeOptions[0].value);
const bar1Option = shallowRef({}); const bar1Option = shallowRef({});
const bar1DataIsEmpty = ref(false);
watch( watch(
[domainValue, typeValue], [domainValue, typeValue],
async ([domain, type]) => { async ([domain, type]) => {
let EntitiesChangeCount = await getEntitiesChangeCount(domain, type); let EntitiesChangeCount = await getEntitiesChangeCount(domain, type);
EntitiesChangeCount = _.reverse(EntitiesChangeCount); EntitiesChangeCount = _.reverse(EntitiesChangeCount);
bar1DataIsEmpty.value = EntitiesChangeCount.length === 0;
bar1Option.value = getBarChart( bar1Option.value = getBarChart(
_.map(EntitiesChangeCount, "year"), _.map(EntitiesChangeCount, "year"),
_.map(EntitiesChangeCount, "count"), _.map(EntitiesChangeCount, "count"),
...@@ -317,6 +334,10 @@ watch( ...@@ -317,6 +334,10 @@ watch(
await fetchKeyEntityList(route.query.startTime, newVal); await fetchKeyEntityList(route.query.startTime, newVal);
}, 300) }, 300)
); );
const handleDomainChange = async domain => {
await fetchKeyEntityList(route.query.startTime, value3.value, domain);
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
</CardCustom> </CardCustom>
</div> </div>
<div class="row"> <div class="row">
<CardCustom title="历制裁涉及领域数" :style="{ width: '798px', height: '422px' }"> <CardCustom title="历制裁涉及领域数" :style="{ width: '798px', height: '422px' }">
<div class="subPanel3"> <div class="subPanel3">
<div class="chartsWrap" :style="{ paddingBottom: '10px' }"> <div class="chartsWrap" :style="{ paddingBottom: '10px' }">
<Echarts :option="bar2Option" height="100%"></Echarts> <Echarts :option="bar2Option" height="100%"></Echarts>
...@@ -46,7 +46,12 @@ import { Search } from "@element-plus/icons-vue"; ...@@ -46,7 +46,12 @@ import { Search } from "@element-plus/icons-vue";
import Echarts from "@/components/Chart/index.vue"; import Echarts from "@/components/Chart/index.vue";
import { getBarChart, getLineChart, getPieOption1 } from "../../utils/charts"; import { getBarChart, getLineChart, getPieOption1 } from "../../utils/charts";
import Hint from "./hint.vue"; import Hint from "./hint.vue";
import { getEntitiesAreaCountByYear, getEntitiesDomainCount, getCountThisDomain } from "@/api/exportControl"; import {
getEntitiesAreaCountByYear,
getEntitiesDomainCount,
getCountThisDomain,
getDomainDistribution
} from "@/api/exportControl";
import _ from "lodash"; import _ from "lodash";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
const route = useRoute(); const route = useRoute();
...@@ -85,13 +90,18 @@ const fetchEntitiesDomainCount = async () => { ...@@ -85,13 +90,18 @@ const fetchEntitiesDomainCount = async () => {
onMounted(async () => { onMounted(async () => {
try { try {
const [entitiesAreaCountByYearData, countThisDomainData4, countThisDomainData10] = await Promise.all([ const [entitiesAreaCountByYearData, countThisDomainData4, countThisDomainData10] = await Promise.all([
getEntitiesAreaCountByYear(route.query.startTime), getDomainDistribution(route.query.startTime),
getCountThisDomain("4"), getCountThisDomain("4"),
// getEntitiesDomainCount(), // getEntitiesDomainCount(),
getCountThisDomain("10") getCountThisDomain("10")
]); ]);
pie1Option.value = getPieOption1(entitiesAreaCountByYearData ?? []); pie1Option.value = getPieOption1(
entitiesAreaCountByYearData.map(item => ({
name: item.name,
value: item.count
})) ?? []
);
const list4 = _.reverse(countThisDomainData4 ?? []); const list4 = _.reverse(countThisDomainData4 ?? []);
line1Option.value = getLineChart({ line1Option.value = getLineChart({
xAxisData: _.map(list4, "year"), xAxisData: _.map(list4, "year"),
......
...@@ -42,7 +42,7 @@ onMounted(async () => { ...@@ -42,7 +42,7 @@ onMounted(async () => {
.filter(item => item.count > 0) .filter(item => item.count > 0)
.map(item => { .map(item => {
return { return {
name: item?.type, name: item?.name,
value: item?.count value: item?.count
}; };
}) })
......
...@@ -57,7 +57,7 @@ ...@@ -57,7 +57,7 @@
<div class="panel3"> <div class="panel3">
<div class="chartWrap"> <div class="chartWrap">
<PieCharts v-if="panel3ActiveIndex === 1"></PieCharts> <PieCharts v-if="panel3ActiveIndex === 1"></PieCharts>
<MapCharts v-if="panel3ActiveIndex === 2"></MapCharts> <MapCharts v-if="panel3ActiveIndex === 2" :date="route.query.startTime"></MapCharts>
</div> </div>
<Hint text="本次制裁共新增83个实体,其中53个中国大陆实体、1个中国台湾实体。"></Hint> <Hint text="本次制裁共新增83个实体,其中53个中国大陆实体、1个中国台湾实体。"></Hint>
</div> </div>
...@@ -94,7 +94,7 @@ ...@@ -94,7 +94,7 @@
</div> </div>
</div> </div>
<div class="tableWrap"> <div class="tableWrap" ref="tableWrapRef" @scroll="handleScroll">
<el-table <el-table
:data="selectEntitiesList" :data="selectEntitiesList"
class="sanction-table" class="sanction-table"
...@@ -115,7 +115,7 @@ ...@@ -115,7 +115,7 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="domains" label="涉及领域" width="100"> <el-table-column prop="domains" label="涉及领域" width="180">
<template #default="{ row }"> <template #default="{ row }">
<div class="domain-tags"> <div class="domain-tags">
<el-tag v-for="tag in row.domains" :key="tag" :type="panel5TypeMap[tag]">{{ <el-tag v-for="tag in row.domains" :key="tag" :type="panel5TypeMap[tag]">{{
...@@ -125,11 +125,11 @@ ...@@ -125,11 +125,11 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="address" label="上市地点" width="90" align="center"> <!-- <el-table-column prop="address" label="上市地点" width="90" align="center">
<template #default="{ row }"> <template #default="{ row }">
{{ row.address }} {{ row.address }}
</template> </template>
</el-table-column> </el-table-column> -->
<el-table-column prop="time" label="制裁时间" width="120" align="center"> <el-table-column prop="time" label="制裁时间" width="120" align="center">
<template #default="{ row }"> <template #default="{ row }">
...@@ -137,11 +137,11 @@ ...@@ -137,11 +137,11 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="revenue" label="营收(亿元)" width="100" align="center"> <!-- <el-table-column prop="revenue" label="营收(亿元)" width="100" align="center">
<template #default="{ row }"> <template #default="{ row }">
{{ row.revenue }} {{ row.revenue }}
</template> </template>
</el-table-column> </el-table-column> -->
<el-table-column prop="subCompany" label="50%规则子企业" min-width="140" align="left"> <el-table-column prop="subCompany" label="50%规则子企业" min-width="140" align="left">
<template #default="{ row }"> <template #default="{ row }">
...@@ -183,7 +183,7 @@ import PieCharts from "../components/pieCharts.vue"; ...@@ -183,7 +183,7 @@ import PieCharts from "../components/pieCharts.vue";
import MapCharts from "../components/mapCharts.vue"; import MapCharts from "../components/mapCharts.vue";
import ButtonList from "@/components/buttonList/buttonList.vue"; import ButtonList from "@/components/buttonList/buttonList.vue";
import Hint from "../components/hint.vue"; import Hint from "../components/hint.vue";
import { onMounted, reactive, ref, shallowRef } from "vue"; import { onMounted, reactive, ref, shallowRef, watch } from "vue";
import panel1_1 from "../../assets/images/panel1_1.png"; import panel1_1 from "../../assets/images/panel1_1.png";
import panel2_1 from "../../assets/images/panel2_1.png"; import panel2_1 from "../../assets/images/panel2_1.png";
import panel2_2 from "../../assets/images/panel2_2.png"; import panel2_2 from "../../assets/images/panel2_2.png";
...@@ -197,7 +197,14 @@ import panel5_5 from "../../assets/images/panel5_5.png"; ...@@ -197,7 +197,14 @@ import panel5_5 from "../../assets/images/panel5_5.png";
import panel5_6 from "../../assets/images/panel5_6.png"; import panel5_6 from "../../assets/images/panel5_6.png";
import panel5_7 from "../../assets/images/panel5_7.png"; import panel5_7 from "../../assets/images/panel5_7.png";
import panel5_8 from "../../assets/images/panel5_8.png"; import panel5_8 from "../../assets/images/panel5_8.png";
import { getOrganizationInfo, getPersonList, getSanReasonSelect, getSelectEntitiesList } from "@/api/exportControl"; import {
getOrganizationInfo,
getPersonList,
getSanReasonSelect,
getSelectEntitiesList,
getEntitiesList
} from "@/api/exportControl";
import _ from "lodash"; import _ from "lodash";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { formatAnyDateToChinese } from "../../utils"; import { formatAnyDateToChinese } from "../../utils";
...@@ -246,15 +253,26 @@ const sanReasonSelect = shallowRef([ ...@@ -246,15 +253,26 @@ const sanReasonSelect = shallowRef([
text: "将中国定位为“通过强制技术转让获取先进制程的威胁”" text: "将中国定位为“通过强制技术转让获取先进制程的威胁”"
} }
]); ]);
const panel5IsChecked = ref(true);
const selectEntitiesList = shallowRef([]); const selectEntitiesList = shallowRef([]);
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(0);
const loading = ref(false);
const tableWrapRef = ref(null);
const noMoreData = ref(false);
onMounted(async () => { onMounted(async () => {
try { try {
const [organizationInfoData, personListData, sanReasonSelectData, selectEntitiesListData] = await Promise.all([ const [organizationInfoData, sanReasonSelectData] = await Promise.all([
getOrganizationInfo(), getOrganizationInfo(),
getPersonList(), // getPersonList(),
getSanReasonSelect(route.query.startTime), getSanReasonSelect(route.query.startTime)
getSelectEntitiesList(route.query.startTime) // getSelectEntitiesList(route.query.startTime)
]); ]);
console.log("organizationInfoData", organizationInfoData);
organizationInfo.value = { organizationInfo.value = {
img: panel1_1, img: panel1_1,
mingcheng: organizationInfoData?.orgNameZh, mingcheng: organizationInfoData?.orgNameZh,
...@@ -274,22 +292,103 @@ onMounted(async () => { ...@@ -274,22 +292,103 @@ onMounted(async () => {
return { text: item }; return { text: item };
}); });
selectEntitiesList.value = _.map(selectEntitiesListData, item => { // selectEntitiesList.value = _.map(selectEntitiesListData, item => {
return { // return {
name: item?.entityNameZh, // name: item?.entityNameZh,
domains: item.techDomainList, // domains: item.techDomainList,
// address: "--",
// time: formatAnyDateToChinese(item?.startTime),
// isUp: true,
// revenue: "--",
// subCompany: "--",
// img: ""
// };
// });
// 初始化加载第一页数据
await fetchEntitiesList(currentPage.value, pageSize.value);
} catch (err) {
console.log(err);
}
});
// 获取实体清单数据
const fetchEntitiesList = async (page = 1, size = 10) => {
if (loading.value || noMoreData.value) return;
loading.value = true;
try {
const res = await getEntitiesList("实体清单", page, size, route.query.startTime, panel5IsChecked.value);
if (res) {
const newData = res.content.map(item => ({
...item,
name: item.entityNameZh,
enName: item.entityName,
domains: item.techDomains,
time: formatAnyDateToChinese(item.startTime),
address: "--", address: "--",
time: formatAnyDateToChinese(item?.startTime),
isUp: true,
revenue: "--",
subCompany: "--", subCompany: "--",
img: "" img: ""
}; }));
});
// 如果是第一页,替换数据;否则追加数据
if (page === 1) {
selectEntitiesList.value = newData;
} else {
selectEntitiesList.value = [...selectEntitiesList.value, ...newData];
}
total.value = res.totalElements;
currentPage.value = res.number + 1;
// 检查是否还有更多数据
if (selectEntitiesList.value.length >= total.value) {
noMoreData.value = true;
}
}
} catch (err) { } catch (err) {
console.log(err); console.error(err);
if (currentPage.value > 1) {
currentPage.value--;
} }
}); } finally {
loading.value = false;
}
};
watch(
() => panel5IsChecked.value,
newVal => {
fetchEntitiesList(1, 10);
}
);
// 处理滚动事件
const debounceFlag = ref(false);
const handleScroll = () => {
if (!tableWrapRef.value || debounceFlag.value) return;
const { scrollTop, scrollHeight, clientHeight } = tableWrapRef.value;
// 当距离底部小于50px时加载更多
if (scrollHeight - scrollTop - clientHeight < 50) {
// 设置防抖标志
debounceFlag.value = true;
loadMore();
// 延迟重置防抖标志,防止连续触发
setTimeout(() => {
debounceFlag.value = false;
}, 300);
}
};
// 加载更多数据
const loadMore = () => {
if (!loading.value && !noMoreData.value) {
currentPage.value++;
fetchEntitiesList(currentPage.value, pageSize.value);
}
};
const panel3ActiveIndex = ref(1); const panel3ActiveIndex = ref(1);
const setPanel3ActiveIndex = index => { const setPanel3ActiveIndex = index => {
...@@ -304,7 +403,7 @@ const panel5ButtonAcitveID = ref(panel5ButtonList[0].id); ...@@ -304,7 +403,7 @@ const panel5ButtonAcitveID = ref(panel5ButtonList[0].id);
const panel5SetButtonAcitveID = id => { const panel5SetButtonAcitveID = id => {
panel5ButtonAcitveID.value = id; panel5ButtonAcitveID.value = id;
}; };
const panel5IsChecked = ref(true);
const panel5TypeMap = { const panel5TypeMap = {
人工智能: "danger", 人工智能: "danger",
通信网络: "warning", 通信网络: "warning",
...@@ -662,6 +761,10 @@ const panel6 = ref([ ...@@ -662,6 +761,10 @@ const panel6 = ref([
margin-top: 14px; margin-top: 14px;
min-height: 0; min-height: 0;
overflow: auto; overflow: auto;
.domain-tags {
display: flex;
gap: 4px;
}
} }
.name { .name {
display: flex; display: flex;
......
...@@ -35,14 +35,19 @@ ...@@ -35,14 +35,19 @@
</div> </div>
<div class="layout-main-header-right-box"> <div class="layout-main-header-right-box">
<div class="right-box-top"> <div class="right-box-top">
<div class="time">{{ "2025年7月" }}</div> <div class="time">{{ route.query.startTime }}</div>
<div class="name">{{ "美国商务部工业与安全局" }}</div> <div class="name">{{ "美国商务部工业与安全局" }}</div>
</div> </div>
<div class="right-box-bottom"> <div class="right-box-bottom">
<el-button type="plain" size="large" icon="Search" @click="handleSwitchActiveName('实体清单原文')" <el-button
type="plain"
size="large"
disabled
icon="Search"
@click="handleSwitchActiveName('实体清单原文')"
>实体清单原文</el-button >实体清单原文</el-button
> >
<el-button type="primary" size="large" icon="EditPen">分析报告</el-button> <el-button type="primary" size="large" disabled icon="EditPen">分析报告</el-button>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
<div class="sub-title">{{ subtitle }}</div> <div class="sub-title">{{ subtitle }}</div>
</div> </div>
<div class="description">{{ description }}</div> <div class="description">{{ description }}</div>
<div v-if="quantity > 0" class="quantity" :style="{ color: color }">{{ quantity }}</div> <div v-if="quantity > 0" class="quantity" :style="{ color: color }">{{ quantity }}{{ unit || "个" }}</div>
</div> </div>
</div> </div>
</template> </template>
...@@ -30,6 +30,10 @@ defineProps({ ...@@ -30,6 +30,10 @@ defineProps({
type: [Number, String], type: [Number, String],
default: 0 default: 0
}, },
unit: {
type: String,
default: ""
},
color: { color: {
type: String, type: String,
default: "#409EFF" default: "#409EFF"
......
差异被折叠。
差异被折叠。
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论