提交 b708ffdd authored 作者: 朱政's avatar 朱政

Merge branch 'pre' into zz-dev

流水线 #277 已通过 于阶段
in 1 分 24 秒
...@@ -143,6 +143,7 @@ export function getChartAnalysis(data, options = {}) { ...@@ -143,6 +143,7 @@ export function getChartAnalysis(data, options = {}) {
: typeof options?.onInterpretationDelta === "function" : typeof options?.onInterpretationDelta === "function"
? options.onInterpretationDelta ? options.onInterpretationDelta
: null; : null;
const externalSignal = options?.signal;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let buffer = ""; let buffer = "";
let latestInterpretation = ""; let latestInterpretation = "";
...@@ -150,16 +151,32 @@ export function getChartAnalysis(data, options = {}) { ...@@ -150,16 +151,32 @@ export function getChartAnalysis(data, options = {}) {
let lastStreamedInterpretationLen = 0; let lastStreamedInterpretationLen = 0;
let settled = false; let settled = false;
const abortController = new AbortController(); const abortController = new AbortController();
const externalAbortHandler = () => {
abortController.abort();
};
if (externalSignal) {
if (externalSignal.aborted) {
abortController.abort();
} else {
externalSignal.addEventListener("abort", externalAbortHandler, { once: true });
}
}
const safeResolve = value => { const safeResolve = value => {
if (settled) return; if (settled) return;
settled = true; settled = true;
if (externalSignal) {
externalSignal.removeEventListener("abort", externalAbortHandler);
}
resolve(value); resolve(value);
}; };
const safeReject = err => { const safeReject = err => {
if (settled) return; if (settled) return;
settled = true; settled = true;
if (externalSignal) {
externalSignal.removeEventListener("abort", externalAbortHandler);
}
reject(err); reject(err);
}; };
......
...@@ -56,11 +56,12 @@ export function getBillDyqk(params) { ...@@ -56,11 +56,12 @@ export function getBillDyqk(params) {
* @param {id,cRelated,currentPage,pageSize} * @param {id,cRelated,currentPage,pageSize}
* @header token * @header token
*/ */
export function getBillBackground(params) { export function getBillBackground(params, config = {}) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/billInfoBean/background/${params.id}`, url: `/api/billInfoBean/background/${params.id}`,
params, params,
signal: config.signal
}) })
} }
// 相关事件-根据法案ID获取相关事件信息 // 相关事件-根据法案ID获取相关事件信息
...@@ -107,11 +108,12 @@ export function getBillPersonAnalyzeDy(params) { ...@@ -107,11 +108,12 @@ export function getBillPersonAnalyzeDy(params) {
* @param {id} * @param {id}
* @header token * @header token
*/ */
export function getBillContentId(params) { export function getBillContentId(params, config = {}) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/billInfoBean/contentId/${params.id}`, url: `/api/billInfoBean/contentId/${params.id}`,
params, params,
signal: config.signal
}) })
} }
...@@ -120,11 +122,12 @@ export function getBillContentId(params) { ...@@ -120,11 +122,12 @@ export function getBillContentId(params) {
* @param {billId,id,cRelated,currentPage,pageSize,domainNameList,measuresNameList,content} * @param {billId,id,cRelated,currentPage,pageSize,domainNameList,measuresNameList,content}
* @header token * @header token
*/ */
export function getBillContentTk(params) { export function getBillContentTk(params, config = {}) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/billInfoBean/content/tk/${params.billId}/${params.id}`, url: `/api/billInfoBean/content/tk/${params.billId}/${params.id}`,
params, params,
signal: config.signal
}) })
} }
...@@ -133,11 +136,12 @@ export function getBillContentTk(params) { ...@@ -133,11 +136,12 @@ export function getBillContentTk(params) {
* @param {billId,versionId,cRelated} * @param {billId,versionId,cRelated}
* @header token * @header token
*/ */
export function getBillContentXzfs(params) { export function getBillContentXzfs(params, config = {}) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/billInfoBean/content/xzfs/${params.billId}/${params.versionId}`, url: `/api/billInfoBean/content/xzfs/${params.billId}/${params.versionId}`,
params, params,
signal: config.signal
}) })
} }
...@@ -146,11 +150,12 @@ export function getBillContentXzfs(params) { ...@@ -146,11 +150,12 @@ export function getBillContentXzfs(params) {
* @param {billId,versionId,cRelated} * @param {billId,versionId,cRelated}
* @header token * @header token
*/ */
export function getBillHyly(params) { export function getBillHyly(params, config = {}) {
return request({ return request({
method: 'GET', method: 'GET',
url: `/api/billInfoBean/content/hyly/${params.billId}/${params.versionId}`, url: `/api/billInfoBean/content/hyly/${params.billId}/${params.versionId}`,
params, params,
signal: config.signal
}) })
} }
......
...@@ -3,8 +3,14 @@ import request from "@/api/request.js"; ...@@ -3,8 +3,14 @@ import request from "@/api/request.js";
// 涉华法案领域分布 // 涉华法案领域分布
/** /**
* @param {Object} params * @param {Object} params
* @param {string} params.year - 年份 * @param {string} params.year - 年份(2022-2026)
* @param {string} [params.status] - 状态:提出法案/通过法案 * @param {string} [params.status] - 法案状态:
* - 提案
* - 众议院通过
* - 参议院通过
* - 分歧已解决
* - 呈交总统
* - 完成立法
*/ */
export function getBillIndustry(params) { export function getBillIndustry(params) {
return request({ return request({
......
...@@ -10,4 +10,11 @@ export function search(data) { ...@@ -10,4 +10,11 @@ export function search(data) {
url: `/temporarySearch/search-info/es/page`, url: `/temporarySearch/search-info/es/page`,
data:data data:data
}) })
}
export function getThinkTankList() {
return request({
method: 'GET',
url: `/temporarySearch/search-info/all-organization-names`,
})
} }
\ No newline at end of file
...@@ -292,10 +292,10 @@ export function getSingleSanctionOverviewList(data) { ...@@ -292,10 +292,10 @@ export function getSingleSanctionOverviewList(data) {
* @param {string} params.sanRecordId - 制裁记录ID * @param {string} params.sanRecordId - 制裁记录ID
* @header token * @header token
*/ */
export function getSingleSanctionTotalCount(id) { export function getSingleSanctionTotalCount(sanTypeId, recordId) {
return request({ return request({
method: "GET", method: "GET",
url: `/api/sanctionList/statistics/total?sanTypeId=${id}`, url: `/api/sanctionList/statistics/total?sanTypeId=${sanTypeId}&sanRecordId=${recordId}`,
}); });
} }
......
import request from "@/api/request.js";
\ No newline at end of file
...@@ -82,6 +82,7 @@ const getGraphChart = (nodes, links, layoutType) => { ...@@ -82,6 +82,7 @@ const getGraphChart = (nodes, links, layoutType) => {
} }
}, },
force: { force: {
layoutAnimation: false, // 关闭初始化晃来晃去的动画
repulsion: 300, repulsion: 300,
gravity: 0, gravity: 0,
edgeLength: 300 edgeLength: 300
......
<template> <template>
<div class="graph-chart-wrapper" id="graph"> <div class="graph-chart-wrapper" id="graph"></div>
</div>
</template> </template>
<script setup> <script setup>
import { onMounted, onBeforeUnmount } from 'vue'; import { onMounted, onBeforeUnmount, watch } from "vue";
import setChart from '@/utils/setChart'; import setChart from "@/utils/setChart";
import getGraphChart from './graphChart'; import getGraphChart from "./graphChart";
const emits = defineEmits(["handleClickNode"]) const emits = defineEmits(["handleClickNode"]);
const props = defineProps({ const props = defineProps({
nodes: { nodes: {
type: Array, type: Array,
default: [] default: []
}, },
links: { links: {
type: Array, type: Array,
default: [] default: []
}, },
layoutType: { layoutType: {
type: String, type: String,
default: 'force' default: "force"
}, },
width: { width: {
type: String, type: String,
default: 'force' default: "force"
}, },
height: { height: {
type: String, type: String,
default: 'force' default: "force"
} }
}) });
let chart = null let chart = null;
onMounted(() => { onMounted(() => {
const graph = getGraphChart(props.nodes, props.links, props.layoutType) const graph = getGraphChart(props.nodes, props.links, props.layoutType);
chart = setChart(graph, 'graph') chart = setChart(graph, "graph");
chart.on("click", (event) => { emits("handleClickNode", event) }) chart.on("click", event => {
}) emits("handleClickNode", event);
});
});
onBeforeUnmount(() => { onBeforeUnmount(() => {
chart.off("click") chart.off("click");
chart.dispose() chart.dispose();
}) });
watch(
() => [props.nodes, props.links],
() => {
if (chart) {
const graph = getGraphChart(props.nodes, props.links, props.layoutType);
chart.setOption(graph, true);
}
},
{ deep: true }
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.graph-chart-wrapper { .graph-chart-wrapper {
width: 100%; width: 100%;
height: 100%; height: 100%;
// width: 800px; // width: 800px;
// height: 500px; // height: 500px;
} }
</style> </style>
\ No newline at end of file
...@@ -17,7 +17,14 @@ ...@@ -17,7 +17,14 @@
</template> </template>
<script setup> <script setup>
import {ref} from 'vue' import {onMounted, ref} from 'vue'
const props = defineProps({
activeTime: {
typeof: String,
default: '近一周'
}
})
const timeList = ref([ const timeList = ref([
{ {
...@@ -34,6 +41,10 @@ const timeList = ref([ ...@@ -34,6 +41,10 @@ const timeList = ref([
}, },
]) ])
onMounted(() => {
timeList.value.forEach(item => { item.active = item.time === props.activeTime })
})
const handleTimeClick = (item, index) => { const handleTimeClick = (item, index) => {
timeList.value.forEach(time => { timeList.value.forEach(time => {
time.active = false time.active = false
......
...@@ -18,13 +18,13 @@ const getWordCloudChart = data => { ...@@ -18,13 +18,13 @@ const getWordCloudChart = data => {
// 其他形状你可以使用形状路径 // 其他形状你可以使用形状路径
// shape: 'circle', // 示例 // shape: 'circle', // 示例
// 或者自定义路径 // 或者自定义路径
gridSize: 15, // 网格大小,影响词间距。 gridSize: 5, // 网格大小,影响词间距。
sizeRange: [16, 36], // 定义词云中文字大小的范围 sizeRange: [16, 36], // 定义词云中文字大小的范围
rotationRange: [0, 0], rotationRange: [0, 0],
// rotationRange: [-90, 90], // rotationRange: [-90, 90],
// rotationStep: 10, // rotationStep: 10,
drawOutOfBound: false, // 是否超出画布 drawOutOfBound: false, // 是否超出画布
shrinkToFit: true, // 是否自动缩小以适应容器 shrinkToFit: false, // 是否自动缩小以适应容器
// 字体 // 字体
textStyle: { textStyle: {
color: function (params) { color: function (params) {
......
...@@ -128,7 +128,19 @@ const homeTitleList = ref([ ...@@ -128,7 +128,19 @@ const homeTitleList = ref([
} }
]); ]);
const homeActiveTitleIndex = ref(0); const homeActiveTitleIndex = computed(() => {
let activeIndex = 1
if (route.fullPath.includes('/ZMOverView')) {
activeIndex = 0
} else if (route.fullPath.includes('/dataLibrary')) {
activeIndex = 3
} else if (route.fullPath.includes('/chat') || route.fullPath.includes('/writtingAsstaint')) {
activeIndex = 2
} else {
activeIndex = 1
}
return activeIndex
})
const isShowMenu = ref(false); const isShowMenu = ref(false);
const handleShowMenu = (index, isShow) => { const handleShowMenu = (index, isShow) => {
...@@ -148,79 +160,150 @@ const handleHoverMenu = isShow => { ...@@ -148,79 +160,150 @@ const handleHoverMenu = isShow => {
isShowMenu.value = isShow; isShowMenu.value = isShow;
}; };
const menuList = ref([ const menuList = computed(() => {
// { let menu = [
// title: "中美科技博弈概览", // {
// icon: Menu1, // title: "中美科技博弈概览",
// path: "/ZMOverView" // icon: Menu1,
// }, // path: "/ZMOverView"
{ // },
title: "科技法案", {
icon: Menu2, title: "科技法案",
path: "/billHome", icon: Menu2,
active: false path: "/billHome",
}, active: false
{ },
title: "科技政令", {
icon: Menu3, title: "科技政令",
path: "/decree", icon: Menu3,
active: false path: "/decree",
}, active: false
{ },
title: "美国科技智库", {
icon: Menu4, title: "美国科技智库",
path: "/thinkTank", icon: Menu4,
active: false path: "/thinkTank",
}, active: false
{ },
title: "出口管制", {
icon: Menu5, title: "出口管制",
path: "/exportControl", icon: Menu5,
active: false path: "/exportControl",
}, active: false
{ },
title: "科研合作限制", {
icon: Menu6, title: "科研合作限制",
path: "/cooperationRestrictions", icon: Menu6,
active: false path: "/cooperationRestrictions",
}, active: false
{ },
title: "投融资限制", {
icon: Menu7, title: "投融资限制",
path: "/finance", icon: Menu7,
active: false path: "/finance",
}, active: false
{ },
title: "市场准入限制", {
icon: Menu8, title: "市场准入限制",
path: "/marketAccessRestrictions", icon: Menu8,
active: false path: "/marketAccessRestrictions",
}, active: false
{ },
title: "规则限制", {
icon: Menu9, title: "规则限制",
path: "/ruleRestrictions", icon: Menu9,
active: false path: "/ruleRestrictions",
}, active: false
{ },
title: "美国科技人物观点", {
icon: Menu10, title: "美国科技人物观点",
path: "/technologyFigures", icon: Menu10,
active: false path: "/technologyFigures",
}, active: false
{ },
title: "美国主要创新主体动向", {
icon: Menu11, title: "美国主要创新主体动向",
path: "/innovationSubject", icon: Menu11,
active: false path: "/innovationSubject",
}, active: false
{ },
title: "美国科研资助体系", {
icon: Menu12, title: "美国科研资助体系",
path: "/scientificFunding", icon: Menu12,
active: false path: "/scientificFunding",
active: false
}
]
switch (route.fullPath) {
case '/billHome':
menu.forEach(item => {
item.active = false
})
menu[0].active = true
break
case '/decree':
menu.forEach(item => {
item.active = false
})
menu[1].active = true
break
case '/thinkTank':
menu.forEach(item => {
item.active = false
})
menu[2].active = true
break
case '/exportControl':
menu.forEach(item => {
item.active = false
})
menu[3].active = true
break
case '/cooperationRestrictions':
menu.forEach(item => {
item.active = false
})
menu[4].active = true
break
case '/finance':
menu.forEach(item => {
item.active = false
})
menu[5].active = true
break
case '/marketAccessRestrictions':
menu.forEach(item => {
item.active = false
})
menu[6].active = true
break
case '/ruleRestrictions':
menu.forEach(item => {
item.active = false
})
menu[7].active = true
break
case '/technologyFigures':
menu.forEach(item => {
item.active = false
})
menu[8].active = true
break
case '/innovationSubject':
menu.forEach(item => {
item.active = false
})
menu[9].active = true
break
case '/scientificFunding':
menu.forEach(item => {
item.active = false
})
menu[10].active = true
break
} }
]); return menu
})
const isShowTool = ref(false); const isShowTool = ref(false);
...@@ -243,10 +326,7 @@ const toolList = ref([ ...@@ -243,10 +326,7 @@ const toolList = ref([
]) ])
const handleToModule = (item, index) => { const handleToModule = (item, index) => {
window.sessionStorage.setItem('homeActiveTitleIndex', index)
if (index === 1) { if (index === 1) {
homeActiveTitleIndex.value = index
item.active = true
router.push({ router.push({
path: item.path path: item.path
}) })
...@@ -265,10 +345,7 @@ const handleToModule = (item, index) => { ...@@ -265,10 +345,7 @@ const handleToModule = (item, index) => {
}; };
const handleClickTitle = (item, index) => { const handleClickTitle = (item, index) => {
if (index === 0 || index === 3) { if (index === 0 || index === 3) {
window.sessionStorage.setItem('homeActiveTitleIndex', index)
homeActiveTitleIndex.value = index
router.push(item.path) router.push(item.path)
} }
}; };
...@@ -279,17 +356,8 @@ const handleClickToolBox = () => { ...@@ -279,17 +356,8 @@ const handleClickToolBox = () => {
onMounted(() => { onMounted(() => {
handleGetPersonType(); handleGetPersonType();
if (route.query.titleIndex) {
homeActiveTitleIndex.value = Number(route.query.titleIndex)
} else {
homeActiveTitleIndex.value = Number(window.sessionStorage.getItem('homeActiveTitleIndex'))
}
}); });
onUnmounted(() => {
window.sessionStorage.removeItem('homeActiveTitleIndex')
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
`}} `}}
</pre> </pre>
<div class="chart-box"> <div class="chart-box">
<GraphChart :nodes="nodes" :links="links" layoutType="force"> <GraphChart :nodes="nodes" :links="links" layoutType="none">
</GraphChart> </GraphChart>
</div> </div>
</el-col> </el-col>
......
const getQuarterRange = (quatarNum) => { const getQuarterRange = (year, quatarNum) => {
const quarters = { const quarters = {
1: ['2025-01-01', '2025-03-31'], 1: [year+ '-01-01', year+ '-03-31'],
2: ['2025-04-01', '2025-06-30'], 2: [year+ '-04-01', year+ '-06-30'],
3: ['2025-07-01', '2025-09-30'], 3: [year+ '-07-01', year+ '-09-30'],
4: ['2025-10-01', '2025-12-31'] 4: [year+ '-10-01', year+ '-12-31']
}; };
return quarters[quatarNum]; return quarters[quatarNum];
......
...@@ -16,67 +16,51 @@ const setChart = (option, chartId, allowClick, selectParam) => { ...@@ -16,67 +16,51 @@ const setChart = (option, chartId, allowClick, selectParam) => {
chart.on('click', function (params) { chart.on('click', function (params) {
switch (selectParam.moduleType) { switch (selectParam.moduleType) {
case '国会法案': case '国会法案':
// 判断点击的是否为饼图的数据项 if (selectParam.key === 1) {
if (params.componentType === 'series' && params.seriesType === 'pie') { // console.log('当前点击', selectParam, params.seriesName, params.name);
console.log('点击的扇形名称:', params.name); selectParam.selectedStatus = params.seriesName
if (selectParam.key === '领域') { selectParam.selectedDate = JSON.stringify(getMonthRange(params.name))
selectParam.domains = params.name const route = router.resolve({
path: "/dataLibrary/countryBill",
query: selectParam
});
window.open(route.href, "_blank");
return
} else if (selectParam.key === 2) {
selectParam.domains = params.name
const route = router.resolve({
path: "/dataLibrary/countryBill",
query: selectParam
});
window.open(route.href, "_blank");
return
} else if (selectParam.key === 3) {
if (params.name === '众议院' || params.name === '参议院') {
selectParam.selectedCongress = params.name
selectParam.selectedOrg = '全部委员会'
if (selectParam.selectedDate.length === 4) { if (selectParam.selectedDate.length === 4) {
selectParam.selectedDate = JSON.stringify([selectParam.selectedDate + '-01-01', selectParam.selectedDate + '-12-31']) selectParam.selectedDate = JSON.stringify([selectParam.selectedDate + '-01-01', selectParam.selectedDate + '-12-31'])
} }
} else if (selectParam.key === '议院委员会') { } else {
if (params.name === '众议院' || params.name === '参议院') { selectParam.selectedOrg = params.name
selectParam.selectedCongress = params.name selectParam.selectedCongress = '全部议院'
selectParam.selectedOrg = '' if (selectParam.selectedDate.length === 4) {
if (selectParam.selectedDate.length === 4) { selectParam.selectedDate = JSON.stringify([selectParam.selectedDate + '-01-01', selectParam.selectedDate + '-12-31'])
selectParam.selectedDate = JSON.stringify([selectParam.selectedDate + '-01-01', selectParam.selectedDate + '-12-31'])
}
} else {
selectParam.selectedOrg = params.name
selectParam.selectedCongress = ''
if (selectParam.selectedDate.length === 4) {
selectParam.selectedDate = JSON.stringify([selectParam.selectedDate + '-01-01', selectParam.selectedDate + '-12-31'])
}
} }
} }
const route = router.resolve({ const route = router.resolve({
path: "/dataLibrary/countryBill", path: "/dataLibrary/countryBill",
query: selectParam query: selectParam
}); });
window.open(route.href, "_blank"); window.open(route.href, "_blank");
} else if (params.componentType === 'series' && params.seriesType === 'bar') { return
if (params.name === '已立法') { } else {
selectParam.selectedStatus = 1 selectParam.selectedStatus = params.name
} else {
selectParam.selectedStatus = 0
}
if (selectParam.selectedDate.length === 4) {
selectParam.selectedDate = JSON.stringify([selectParam.selectedDate + '-01-01', selectParam.selectedDate + '-12-31'])
}
const route = router.resolve({ const route = router.resolve({
path: "/dataLibrary/countryBill", path: "/dataLibrary/countryBill",
query: selectParam query: selectParam
}); });
window.open(route.href, "_blank"); window.open(route.href, "_blank");
} else {
console.log('当前点击', selectParam, params.seriesName, params.name);
if (params.seriesName !== '通过率') {
selectParam.selectedDate = JSON.stringify(getMonthRange(params.name))
if (params.seriesName === '通过法案') {
selectParam.selectedStatus = 1
} else {
selectParam.selectedStatus = null
}
const route = router.resolve({
path: "/dataLibrary/countryBill",
query: selectParam
});
window.open(route.href, "_blank");
}
} }
break break
...@@ -89,16 +73,38 @@ const setChart = (option, chartId, allowClick, selectParam) => { ...@@ -89,16 +73,38 @@ const setChart = (option, chartId, allowClick, selectParam) => {
}); });
window.open(route.href, "_blank"); window.open(route.href, "_blank");
} else if (params.componentType === 'series' && params.seriesType === 'bar') { } else if (params.componentType === 'series' && params.seriesType === 'bar') {
const year = params.name.slice(0, 4)
const quatarNum = Number(params.name[params.name.length - 1]) const quatarNum = Number(params.name[params.name.length - 1])
selectParam.selectedDate = JSON.stringify(getQuarterRange(quatarNum)) selectParam.selectedDate = JSON.stringify(getQuarterRange(year, quatarNum))
const route = router.resolve({ const route = router.resolve({
path: "/dataLibrary/dataDecree", path: "/dataLibrary/dataDecree",
query: selectParam query: selectParam
}); });
window.open(route.href, "_blank"); window.open(route.href, "_blank");
} }
break
case '科技智库报告':
if (selectParam.key === 1) {
selectParam.domains = params.seriesName
const year = params.name.slice(0, 4)
const quatarNum = Number(params.name[params.name.length - 1])
selectParam.selectedDate = JSON.stringify(getQuarterRange(year, quatarNum))
const route = router.resolve({
path: "/dataLibrary/dataThinkTank",
query: selectParam
});
window.open(route.href, "_blank");
return
} else if (selectParam.key === 2) {
selectParam.domains = params.name
const route = router.resolve({
path: "/dataLibrary/dataThinkTank",
query: selectParam
});
window.open(route.href, "_blank");
return
}
} }
}); });
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
<el-button :type="box1Btn2Type" plain @click="handleClickBox1Btn(2)">全部背景</el-button> <el-button :type="box1Btn2Type" plain @click="handleClickBox1Btn(2)">全部背景</el-button>
</div> </div>
</template> </template>
<div class="box1-main"> <div class="box1-main" v-loading="backgroundLoading">
<div class="box1-main-center"> <div class="box1-main-center">
<div class="box1-main-item" v-for="item in backgroundDisplayList" :key="item.id"> <div class="box1-main-item" v-for="item in backgroundDisplayList" :key="item.id">
<div class="id">{{ item.displayIndex }}</div> <div class="id">{{ item.displayIndex }}</div>
...@@ -198,6 +198,8 @@ const handleClickBox2Btn = index => { ...@@ -198,6 +198,8 @@ const handleClickBox2Btn = index => {
const aboutUserList = ref([]); const aboutUserList = ref([]);
const backgroundList = ref([]); const backgroundList = ref([]);
const backgroundLoading = ref(false);
let backgroundAbortController = null;
const eventList = ref([]); const eventList = ref([]);
...@@ -244,6 +246,12 @@ const nextIconColor = computed(() => (currentIndex.value < personList.value.leng ...@@ -244,6 +246,12 @@ const nextIconColor = computed(() => (currentIndex.value < personList.value.leng
// 获取立法背景内容 // 获取立法背景内容
const handleGetBillBackground = async () => { const handleGetBillBackground = async () => {
if (backgroundAbortController) {
backgroundAbortController.abort();
}
const controller = new AbortController();
backgroundAbortController = controller;
const cRelated = box1BtnActive.value === 1 ? "Y" : "N"; const cRelated = box1BtnActive.value === 1 ? "Y" : "N";
const params = { const params = {
cRelated: cRelated, cRelated: cRelated,
...@@ -251,11 +259,21 @@ const handleGetBillBackground = async () => { ...@@ -251,11 +259,21 @@ const handleGetBillBackground = async () => {
currentPage: currentPage.value - 1, currentPage: currentPage.value - 1,
pageSize: 10 pageSize: 10
}; };
backgroundLoading.value = true;
try { try {
const res = await getBillBackground(params); const res = await getBillBackground(params, { signal: controller.signal });
backgroundList.value = res.data.content; backgroundList.value = res.data.content;
total.value = res.data.totalElements; // 假设API返回totalElements total.value = res.data.totalElements; // 假设API返回totalElements
} catch (error) { } } catch (error) {
if (error?.name !== "AbortError" && error?.code !== "ERR_CANCELED") {
console.error(error);
}
} finally {
if (backgroundAbortController === controller) {
backgroundLoading.value = false;
backgroundAbortController = null;
}
}
}; };
// 获取相关事件 // 获取相关事件
......
...@@ -49,7 +49,8 @@ const getMultiLineChart = (dataX, proposedData, passData, houseData, senateData, ...@@ -49,7 +49,8 @@ const getMultiLineChart = (dataX, proposedData, passData, houseData, senateData,
containLabel: true containLabel: true
}, },
legend: { legend: {
data: ['提出法案', '通过法案', '众议院通过', '参议院通过', '双院通过'], // 图例顺序:提出法案、众议院通过、参议院通过、解决分歧、完成立法
data: ['提出法案', '众议院通过', '参议院通过', '解决分歧', '完成立法'],
show: true, show: true,
top: 10, top: 10,
icon: 'circle', icon: 'circle',
...@@ -126,7 +127,8 @@ const getMultiLineChart = (dataX, proposedData, passData, houseData, senateData, ...@@ -126,7 +127,8 @@ const getMultiLineChart = (dataX, proposedData, passData, houseData, senateData,
data: proposedData data: proposedData
}, },
{ {
name: '通过法案', // 众议院通过
name: '众议院通过',
type: 'line', type: 'line',
smooth: true, smooth: true,
symbol: 'emptyCircle', symbol: 'emptyCircle',
...@@ -137,10 +139,11 @@ const getMultiLineChart = (dataX, proposedData, passData, houseData, senateData, ...@@ -137,10 +139,11 @@ const getMultiLineChart = (dataX, proposedData, passData, houseData, senateData,
itemStyle: { itemStyle: {
color: lineColors[1] color: lineColors[1]
}, },
data: passData data: houseData
}, },
{ {
name: '众议院通过', // 参议院通过
name: '参议院通过',
type: 'line', type: 'line',
smooth: true, smooth: true,
symbol: 'emptyCircle', symbol: 'emptyCircle',
...@@ -151,10 +154,11 @@ const getMultiLineChart = (dataX, proposedData, passData, houseData, senateData, ...@@ -151,10 +154,11 @@ const getMultiLineChart = (dataX, proposedData, passData, houseData, senateData,
itemStyle: { itemStyle: {
color: lineColors[2] color: lineColors[2]
}, },
data: houseData data: senateData
}, },
{ {
name: '参议院通过', // 解决分歧
name: '解决分歧',
type: 'line', type: 'line',
smooth: true, smooth: true,
symbol: 'emptyCircle', symbol: 'emptyCircle',
...@@ -165,10 +169,11 @@ const getMultiLineChart = (dataX, proposedData, passData, houseData, senateData, ...@@ -165,10 +169,11 @@ const getMultiLineChart = (dataX, proposedData, passData, houseData, senateData,
itemStyle: { itemStyle: {
color: lineColors[3] color: lineColors[3]
}, },
data: senateData data: hsData
}, },
{ {
name: '双院通过', // 完成立法
name: '完成立法',
type: 'line', type: 'line',
smooth: true, smooth: true,
symbol: 'emptyCircle', symbol: 'emptyCircle',
...@@ -179,7 +184,7 @@ const getMultiLineChart = (dataX, proposedData, passData, houseData, senateData, ...@@ -179,7 +184,7 @@ const getMultiLineChart = (dataX, proposedData, passData, houseData, senateData,
itemStyle: { itemStyle: {
color: lineColors[4] color: lineColors[4]
}, },
data: hsData data: passData
} }
] ]
} }
......
...@@ -27,29 +27,17 @@ ...@@ -27,29 +27,17 @@
</div> </div>
<div class="left-box-bottom" v-if="showTabs"> <div class="left-box-bottom" v-if="showTabs">
<template v-if="isLoading"> <div class="left-box-bottom-item"
<div class="left-box-bottom-item is-skeleton" v-for="n in 4" :key="n"> :class="{ leftBoxBottomItemActive: activeTitle === item.name }" v-for="item in tabs"
<div class="icon"> :key="item.path" @click="emit('tab-click', item)">
<el-skeleton-item class="skeleton-tab-icon" variant="text" /> <div class="icon">
</div> <img v-if="activeTitle === item.name" :src="item.activeIcon" alt="" />
<div class="name"> <img v-else :src="item.icon" alt="" />
<el-skeleton-item class="skeleton-tab-text" variant="text" />
</div>
</div> </div>
</template> <div class="name" :class="{ nameActive: activeTitle === item.name }">
<template v-else> {{ item.name }}
<div class="left-box-bottom-item"
:class="{ leftBoxBottomItemActive: activeTitle === item.name }" v-for="item in tabs"
:key="item.path" @click="emit('tab-click', item)">
<div class="icon">
<img v-if="activeTitle === item.name" :src="item.activeIcon" alt="" />
<img v-else :src="item.icon" alt="" />
</div>
<div class="name" :class="{ nameActive: activeTitle === item.name }">
{{ item.name }}
</div>
</div> </div>
</template> </div>
</div> </div>
</div> </div>
...@@ -73,30 +61,18 @@ ...@@ -73,30 +61,18 @@
</div> </div>
<div class="right-box-bottom" v-if="showActions"> <div class="right-box-bottom" v-if="showActions">
<template v-if="isLoading"> <div class="btn2" @click="emit('open-analysis', 'forsee')">
<div class="btn3 is-skeleton"> <div class="icon">
<div class="icon"> <img :src="btnIconForsee" alt="" />
<el-skeleton-item class="skeleton-action-icon" variant="text" />
</div>
<div class="text">
<el-skeleton-item class="skeleton-action-text" variant="text" />
</div>
</div> </div>
</template> <div class="text">{{ "进展预测" }}</div>
<template v-else> </div>
<div class="btn2" @click="emit('open-analysis', 'forsee')"> <div class="btn3" @click="emit('open-analysis', 'analysis')">
<div class="icon"> <div class="icon">
<img :src="btnIconForsee" alt="" /> <img :src="btnIconAnalysis" alt="" />
</div>
<div class="text">{{ "进展预测" }}</div>
</div>
<div class="btn3" @click="emit('open-analysis', 'analysis')">
<div class="icon">
<img :src="btnIconAnalysis" alt="" />
</div>
<div class="text">{{ "分析报告" }}</div>
</div> </div>
</template> <div class="text">{{ "分析报告" }}</div>
</div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -834,6 +834,7 @@ onMounted(() => { ...@@ -834,6 +834,7 @@ onMounted(() => {
flex-shrink: 1; flex-shrink: 1;
max-width: 170px; max-width: 170px;
text-align: center; text-align: center;
margin-bottom: 10px;
} }
.nameItemActive { .nameItemActive {
......
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
<div class="chart-ai-wrap"> <div class="chart-ai-wrap">
<div :class="['right-box2-main', { 'right-box-main--full': !domainFooterText }]" id="chart2"></div> <div :class="['right-box2-main', { 'right-box-main--full': !domainFooterText }]" id="chart2"></div>
<div class="overview-tip-row"> <div class="overview-tip-row">
<TipTab class="overview-tip" /> <TipTab class="overview-tip" :text="'涉华科技法案数量及通过率变化趋势,数据来源:美国国会官网'"/>
<AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('domain')" /> <AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('domain')" />
</div> </div>
<div v-if="aiPaneVisible.domain" class="overview-ai-pane" <div v-if="aiPaneVisible.domain" class="overview-ai-pane"
...@@ -72,7 +72,7 @@ ...@@ -72,7 +72,7 @@
<div class="chart-ai-wrap"> <div class="chart-ai-wrap">
<div :class="['right-box1-main', { 'right-box-main--full': !limitFooterText }]" id="chart1"></div> <div :class="['right-box1-main', { 'right-box-main--full': !limitFooterText }]" id="chart1"></div>
<div class="overview-tip-row"> <div class="overview-tip-row">
<TipTab class="overview-tip" /> <TipTab class="overview-tip" :text="'涉华科技法案数量及通过率变化趋势,数据来源:美国国会官网'"/>
<AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('limit')" /> <AiButton class="overview-tip-action" @mouseenter="handleShowAiPane('limit')" />
</div> </div>
<div v-if="aiPaneVisible.limit" class="overview-ai-pane" <div v-if="aiPaneVisible.limit" class="overview-ai-pane"
...@@ -150,8 +150,8 @@ ...@@ -150,8 +150,8 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, computed, watch } from "vue"; import { ref, onMounted, onBeforeUnmount, computed, watch } from "vue";
import { useRoute } from "vue-router"; import { useRoute, onBeforeRouteLeave } from "vue-router";
import * as echarts from "echarts"; import * as echarts from "echarts";
import { Search } from "@element-plus/icons-vue"; import { Search } from "@element-plus/icons-vue";
import getPieChart from "./utils/piechart"; import getPieChart from "./utils/piechart";
...@@ -165,6 +165,31 @@ import { extractTextEntity } from "@/api/intelligent/index"; ...@@ -165,6 +165,31 @@ import { extractTextEntity } from "@/api/intelligent/index";
const route = useRoute(); const route = useRoute();
const pageAbortController = new AbortController();
const isRequestCanceled = error => {
return (
error?.code === "ERR_CANCELED" ||
error?.name === "CanceledError" ||
error?.name === "AbortError" ||
(typeof error?.message === "string" && /canceled|aborted/i.test(error.message))
);
};
const getPageSignal = () => pageAbortController.signal;
const stopCurrentPageRequests = () => {
pageAbortController.abort();
// 让旧请求回调全部失效,避免离开页面后回写状态
tkRequestToken.value += 1;
xzfsRequestToken.value += 1;
hylyRequestToken.value += 1;
entityRequestToken.value += 1;
termsLoading.value = false;
limitLoading.value = false;
domainLoading.value = false;
aiPaneLoading.value = { domain: false, limit: false };
};
const curBill = ref(""); const curBill = ref("");
const curBillId = ref(null); const curBillId = ref(null);
...@@ -294,7 +319,7 @@ const ensureEntitiesForTerms = async terms => { ...@@ -294,7 +319,7 @@ const ensureEntitiesForTerms = async terms => {
try { try {
const results = await Promise.all( const results = await Promise.all(
tasks.map(async item => { tasks.map(async item => {
const res = await extractTextEntity(item.text); const res = await extractTextEntity(item.text, { signal: getPageSignal() });
const entities = normalizeEntities(res?.result ?? res?.data?.result ?? res?.data ?? res); const entities = normalizeEntities(res?.result ?? res?.data?.result ?? res?.data ?? res);
return { key: item.key, entities }; return { key: item.key, entities };
}) })
...@@ -434,6 +459,7 @@ const requestAiPaneContent = async key => { ...@@ -434,6 +459,7 @@ const requestAiPaneContent = async key => {
const res = await getChartAnalysis( const res = await getChartAnalysis(
{ text: JSON.stringify(payload) }, { text: JSON.stringify(payload) },
{ {
signal: getPageSignal(),
onChunk: chunk => { onChunk: chunk => {
const current = overviewAiContent.value[key]; const current = overviewAiContent.value[key];
const base = current === "智能总结生成中..." ? "" : current; const base = current === "智能总结生成中..." ? "" : current;
...@@ -455,6 +481,7 @@ const requestAiPaneContent = async key => { ...@@ -455,6 +481,7 @@ const requestAiPaneContent = async key => {
} }
aiPaneFetched.value = { ...aiPaneFetched.value, [key]: true }; aiPaneFetched.value = { ...aiPaneFetched.value, [key]: true };
} catch (error) { } catch (error) {
if (isRequestCanceled(error)) return;
console.error("获取图表解读失败", error); console.error("获取图表解读失败", error);
overviewAiContent.value = { ...overviewAiContent.value, [key]: "智能总结生成失败" }; overviewAiContent.value = { ...overviewAiContent.value, [key]: "智能总结生成失败" };
} finally { } finally {
...@@ -518,7 +545,7 @@ const handleGetBillList = async () => { ...@@ -518,7 +545,7 @@ const handleGetBillList = async () => {
id: route.query.billId id: route.query.billId
}; };
try { try {
const res = await getBillContentId(params); const res = await getBillContentId(params, { signal: getPageSignal() });
console.log("法案id列表", res); console.log("法案id列表", res);
const rawList = Array.isArray(res?.data) ? res.data : []; const rawList = Array.isArray(res?.data) ? res.data : [];
const seen = new Set(); const seen = new Set();
...@@ -579,7 +606,7 @@ const handleGetBillContentTk = async cRelated => { ...@@ -579,7 +606,7 @@ const handleGetBillContentTk = async cRelated => {
params.content = searchKeyword.value.trim(); params.content = searchKeyword.value.trim();
} }
try { try {
const res = await getBillContentTk(params); const res = await getBillContentTk(params, { signal: getPageSignal() });
if (currentToken !== tkRequestToken.value) { if (currentToken !== tkRequestToken.value) {
return; return;
} }
...@@ -627,6 +654,9 @@ const handleGetBillContentTk = async cRelated => { ...@@ -627,6 +654,9 @@ const handleGetBillContentTk = async cRelated => {
total.value = 0; total.value = 0;
} }
} catch (error) { } catch (error) {
if (isRequestCanceled(error)) {
return;
}
if (currentToken !== tkRequestToken.value) { if (currentToken !== tkRequestToken.value) {
return; return;
} }
...@@ -652,7 +682,7 @@ const handleGetBillContentXzfs = async () => { ...@@ -652,7 +682,7 @@ const handleGetBillContentXzfs = async () => {
}; };
try { try {
const res = await getBillContentXzfs(params); const res = await getBillContentXzfs(params, { signal: getPageSignal() });
if (currentToken !== xzfsRequestToken.value) { if (currentToken !== xzfsRequestToken.value) {
return; return;
} }
...@@ -679,6 +709,9 @@ const handleGetBillContentXzfs = async () => { ...@@ -679,6 +709,9 @@ const handleGetBillContentXzfs = async () => {
let chart1 = getPieChart(chart1Data.value, chart1ColorList.value); let chart1 = getPieChart(chart1Data.value, chart1ColorList.value);
setChart(chart1, "chart1"); setChart(chart1, "chart1");
} catch (error) { } catch (error) {
if (isRequestCanceled(error)) {
return;
}
if (currentToken !== xzfsRequestToken.value) { if (currentToken !== xzfsRequestToken.value) {
return; return;
} }
...@@ -701,7 +734,7 @@ const handleGetBillHyly = async () => { ...@@ -701,7 +734,7 @@ const handleGetBillHyly = async () => {
}; };
try { try {
const res = await getBillHyly(params); const res = await getBillHyly(params, { signal: getPageSignal() });
if (currentToken !== hylyRequestToken.value) { if (currentToken !== hylyRequestToken.value) {
return; return;
} }
...@@ -729,6 +762,9 @@ const handleGetBillHyly = async () => { ...@@ -729,6 +762,9 @@ const handleGetBillHyly = async () => {
let chart2 = getPieChart(chart2Data.value, chart2ColorList.value); let chart2 = getPieChart(chart2Data.value, chart2ColorList.value);
setChart(chart2, "chart2"); setChart(chart2, "chart2");
} catch (error) { } catch (error) {
if (isRequestCanceled(error)) {
return;
}
if (currentToken !== hylyRequestToken.value) { if (currentToken !== hylyRequestToken.value) {
return; return;
} }
...@@ -745,6 +781,14 @@ onMounted(async () => { ...@@ -745,6 +781,14 @@ onMounted(async () => {
await handleGetBillContentXzfs(); await handleGetBillContentXzfs();
await handleGetBillHyly(); await handleGetBillHyly();
}); });
onBeforeRouteLeave(() => {
stopCurrentPageRequests();
});
onBeforeUnmount(() => {
stopCurrentPageRequests();
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
......
...@@ -316,12 +316,12 @@ const handleSearch = async () => { ...@@ -316,12 +316,12 @@ const handleSearch = async () => {
console.log("综合搜索结果", res); console.log("综合搜索结果", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
if (!selectedDomains.value.length) { if (!selectedDomains.value.length) {
domains.value = Object.entries(res.data.aggregations).map(([name, id]) => ({ domains.value = Object.entries(res.data.aggregationsDomain).map(([name, id]) => ({
name, name,
id, id,
selected: false selected: false
})); }));
} }
searchResults.value = res.data.records; searchResults.value = res.data.records;
searchResults.value.forEach(item => { searchResults.value.forEach(item => {
item.originalTitle = highlightText(item.originalTitle, keyword.value); item.originalTitle = highlightText(item.originalTitle, keyword.value);
......
...@@ -94,11 +94,11 @@ ...@@ -94,11 +94,11 @@
</div> </div>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="header-right-item item1"> <div class="header-right-item item1" @click="handleExport">
<div class="icon"> <div class="icon">
<img src="../../assets/icons/download.svg" alt=""> <img src="../../assets/icons/download.svg" alt="">
</div> </div>
<div class="text text-tip-1" @click="handleExport">{{ '导出' }}</div> <div class="text text-tip-1">{{ '导出' }}</div>
</div> </div>
<div class="header-right-item2 item2"> <div class="header-right-item2 item2">
<el-select v-model="curOperation" placeholder="批量操作" style="width: 120px"> <el-select v-model="curOperation" placeholder="批量操作" style="width: 120px">
...@@ -693,10 +693,6 @@ const statusList = ref([ ...@@ -693,10 +693,6 @@ const statusList = ref([
name: '提出法案', name: '提出法案',
id: '提出法案' id: '提出法案'
}, },
{
name: '通过法案',
id: '通过法案'
},
{ {
name: '众议院通过', name: '众议院通过',
id: '众议院通过' id: '众议院通过'
...@@ -706,8 +702,12 @@ const statusList = ref([ ...@@ -706,8 +702,12 @@ const statusList = ref([
id: '参议院通过' id: '参议院通过'
}, },
{ {
name: '分歧已解决', name: '解决分歧',
id: '分歧已解决' id: '解决分歧'
},
{
name: '完成立法',
id: '完成立法'
}, },
]) ])
...@@ -833,13 +833,12 @@ const fetchTableData = async () => { ...@@ -833,13 +833,12 @@ const fetchTableData = async () => {
})) }))
} }
const curDemensionItem = staticsDemensionList.value.filter(item => { const curDemensionItem = staticsDemensionList.value.filter(item => {
return item.name === curDemension.value return item.name === curDemension.value
})[0] })[0]
activeChart.value = ''
timer3.value = setTimeout(() => { timer3.value = setTimeout(() => {
activeChart.value = curDemensionItem.chartTypeList[0] activeChart.value = curDemensionItem.chartTypeList[0]
curChartData.value = curDemensionItem.data curChartData.value = curDemensionItem.data
...@@ -1040,7 +1039,7 @@ const initParam = () => { ...@@ -1040,7 +1039,7 @@ const initParam = () => {
} }
isInvolveCn.value = route.query.isInvolveCn ? true : false isInvolveCn.value = route.query.isInvolveCn ? true : false
if (route.query.selectedStatus) { if (route.query.selectedStatus) {
selectedStatus.value = route.query.selectedStatus === '1' ? '通过' : '提出' selectedStatus.value = route.query.selectedStatus
} else { } else {
selectedStatus.value = '全部阶段' selectedStatus.value = '全部阶段'
} }
...@@ -1061,7 +1060,7 @@ const initParam = () => { ...@@ -1061,7 +1060,7 @@ const initParam = () => {
} }
isInvolveCn.value = savedQuery.isInvolveCn ? true : false isInvolveCn.value = savedQuery.isInvolveCn ? true : false
if (savedQuery.selectedStatus) { if (savedQuery.selectedStatus) {
selectedStatus.value = savedQuery.selectedStatus === '1' ? '通过' : '提出' selectedStatus.value = savedQuery.selectedStatus
} else { } else {
selectedStatus.value = '全部阶段' selectedStatus.value = '全部阶段'
} }
...@@ -1102,6 +1101,10 @@ const handlePerClick = item => { ...@@ -1102,6 +1101,10 @@ const handlePerClick = item => {
// 导出 // 导出
const handleExport = () => { const handleExport = () => {
if (!selectedCount.value) {
ElMessage.warning('请至少选择一项!')
return
}
console.log(selectedMap.value); console.log(selectedMap.value);
const arr = Array.from(selectedMap.value); const arr = Array.from(selectedMap.value);
...@@ -1111,7 +1114,7 @@ const handleExport = () => { ...@@ -1111,7 +1114,7 @@ const handleExport = () => {
const link = document.createElement('a'); const link = document.createElement('a');
link.href = url; link.href = url;
link.download = 'export.json'; link.download = 'bill.json';
link.click(); link.click();
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
......
<template>
<div class="input-wrapper">
<div class="input-left text-tip-1">{{ inputTitle + ':' }}</div>
<div class="input-right">
<el-input v-model="inputValue" :placeholder="placeholderName" style="width: 240px" />
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
const props = defineProps({
inputTitle: {
type: String,
default: ''
},
placeholderName: {
type: String,
default: ''
},
inputName: {
type: String,
default: ''
},
})
const emit = defineEmits(['update:inputText', 'update:customTime'])
const inputValue = computed({
get: () => props.inputName,
set: (value) => emit('update:inputText', value)
})
</script>
<style lang="scss" scoped>
.input-wrapper {
width: 348px;
height: 28px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
.input-left {
width: 100px;
height: 24px;
color: var(--text-primary-65-color);
}
.input-right {
width: 240px;
display: flex;
gap: 8px;
justify-content: space-between;
:deep(.el-input__wrapper) {
border-radius: 4px;
border: 1px solid var(--bg-black-10);
}
}
}
</style>
\ No newline at end of file
...@@ -101,11 +101,11 @@ ...@@ -101,11 +101,11 @@
</div> </div>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="header-right-item item1"> <div class="header-right-item item1" @click="handleExport">
<div class="icon"> <div class="icon">
<img src="../assets/icons/download.svg" alt=""> <img src="../assets/icons/download.svg" alt="">
</div> </div>
<div class="text text-tip-1" @click="handleExport">{{ '导出' }}</div> <div class="text text-tip-1">{{ '导出' }}</div>
</div> </div>
<div class="header-right-item2 item2"> <div class="header-right-item2 item2">
<el-select v-model="curOperation" placeholder="批量操作" style="width: 120px"> <el-select v-model="curOperation" placeholder="批量操作" style="width: 120px">
...@@ -137,7 +137,7 @@ ...@@ -137,7 +137,7 @@
<el-table-column label="发布时间" width="120" class-name="date-column"> <el-table-column label="发布时间" width="120" class-name="date-column">
<template #default="scope">{{ scope.row.date }}</template> <template #default="scope">{{ scope.row.date }}</template>
</el-table-column> </el-table-column>
<el-table-column label="发布机构" width="180"> <el-table-column label="发布机构">
<template #default="scope"> <template #default="scope">
<span class="person-item text-compact" @click="handlePerClick(scope.row)">{{ scope.row.organizationName <span class="person-item text-compact" @click="handlePerClick(scope.row)">{{ scope.row.organizationName
}}</span> }}</span>
...@@ -396,6 +396,7 @@ const handleCloseCurTag = (tag, index) => { ...@@ -396,6 +396,7 @@ const handleCloseCurTag = (tag, index) => {
break break
case '发布时间': case '发布时间':
selectedDate.value = '' selectedDate.value = ''
customTime.value = []
break break
case '发布机构': case '发布机构':
selectedIns.value = '全部机构' selectedIns.value = '全部机构'
...@@ -765,6 +766,8 @@ const fetchTableData = async () => { ...@@ -765,6 +766,8 @@ const fetchTableData = async () => {
return item.name === curDemension.value return item.name === curDemension.value
})[0] })[0]
activeChart.value = ''
setTimeout(() => { setTimeout(() => {
activeChart.value = curDemensionItem.chartTypeList[0] activeChart.value = curDemensionItem.chartTypeList[0]
curChartData.value = curDemensionItem.data curChartData.value = curDemensionItem.data
...@@ -989,12 +992,12 @@ const initParam = () => { ...@@ -989,12 +992,12 @@ const initParam = () => {
const handleClickToDetail = (curDecree) => { const handleClickToDetail = (curDecree) => {
console.log('curDecree', curDecree); console.log('curDecree', curDecree);
window.sessionStorage.setItem("billId", curDecree.id); window.sessionStorage.setItem("decreeId", curDecree.id);
window.sessionStorage.setItem("curTabName", curDecree.title); window.sessionStorage.setItem("curTabName", curDecree.title);
const route = router.resolve({ const route = router.resolve({
path: "/decreeLayout", path: "/decreeLayout",
query: { query: {
billId: curDecree.id id: curDecree.id
} }
}); });
window.open(route.href, "_blank"); window.open(route.href, "_blank");
...@@ -1340,4 +1343,6 @@ onMounted(async () => { ...@@ -1340,4 +1343,6 @@ onMounted(async () => {
// :deep(.el-table__header th:first-child) { // :deep(.el-table__header th:first-child) {
// background-color: #e6f7ff; // background-color: #e6f7ff;
// color: #1890ff; // color: #1890ff;
// }</style> // }
\ No newline at end of file
</style>
\ No newline at end of file
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
<img :src="tipsTcon" alt=""> <img :src="tipsTcon" alt="">
</div> </div>
<div class="date-text">近期美国各联邦政府机构发布涉华政令数量汇总</div> <div class="date-text">近期美国各联邦政府机构发布涉华政令数量汇总</div>
<TimeTabPane @time-click="handleDateChange" /> <TimeTabPane @time-click="handleDateChange" activeTime="近一年" />
</div> </div>
<div class="organization-list" ref="refOrganization" v-loading="organizationInfo.loading"> <div class="organization-list" ref="refOrganization" v-loading="organizationInfo.loading">
<div class="organization-item" v-for="(item, index) in organizationInfo.list" :key="index" <div class="organization-item" v-for="(item, index) in organizationInfo.list" :key="index"
...@@ -77,7 +77,7 @@ const organizationInfo = reactive({ ...@@ -77,7 +77,7 @@ const organizationInfo = reactive({
total: 0, total: 0,
isSort: 1, isSort: 1,
keyWord: "", keyWord: "",
day: 7, day: 365,
list: [] list: []
}) })
......
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
<img :src="tipsTcon" alt=""> <img :src="tipsTcon" alt="">
</div> </div>
<div class="date-text">近期美国各联邦政府机构发布涉华政令数量汇总</div> <div class="date-text">近期美国各联邦政府机构发布涉华政令数量汇总</div>
<TimeTabPane @time-click="onKeyOrganization" /> <TimeTabPane @time-click="onKeyOrganization" activeTime="近一年" />
</div> </div>
<div class="home-main-header-item-box" v-if="keyOrganizationList.length"> <div class="home-main-header-item-box" v-if="keyOrganizationList.length">
<div class="organization-item" v-for="(item, index) in keyOrganizationList" :key="index" @click="handleToInstitution(item)"> <div class="organization-item" v-for="(item, index) in keyOrganizationList" :key="index" @click="handleToInstitution(item)">
...@@ -767,7 +767,7 @@ const handleGetDecreeYearOrder = async () => { ...@@ -767,7 +767,7 @@ const handleGetDecreeYearOrder = async () => {
chart1Data.value.dataY = res.data.map(item => { chart1Data.value.dataY = res.data.map(item => {
return item.count; return item.count;
}); });
summarize1.value = await onChartInterpretation({type:"柱状图",name:"数量变化趋势",data:res.data}) onChartInterpretation({type:"柱状图",name:"数量变化趋势",data:res.data}, summarize1)
} }
} catch (error) { } catch (error) {
console.error("行政令发布频度error", error); console.error("行政令发布频度error", error);
...@@ -775,38 +775,52 @@ const handleGetDecreeYearOrder = async () => { ...@@ -775,38 +775,52 @@ const handleGetDecreeYearOrder = async () => {
box5Params.loading = false box5Params.loading = false
}; };
// AI智能总结 // AI智能总结
const onChartInterpretation = async (text) => { const onChartInterpretation = async (text, param) => {
const response = await fetch('/aiAnalysis/chart_interpretation', { param.value = "正在生成..."
method: 'POST',
headers: {
"X-API-Key": "aircasKEY19491001",
'Content-Type': 'application/json',
},
body: JSON.stringify({text}) // 把参数转为JSON字符串
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = ''; // 👇 新增:超时 + 终止请求(只加这一段)
let summarize = ''; const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000); // 10秒超时
while (true) {
const { done, value } = await reader.read(); try {
if (done) break; const response = await fetch('/aiAnalysis/chart_interpretation', {
method: 'POST',
buffer += decoder.decode(value, { stream: true }); headers: {
const lines = buffer.split('\n'); "X-API-Key": "aircasKEY19491001",
buffer = lines.pop() || ''; 'Content-Type': 'application/json',
},
body: JSON.stringify({text}),
signal: controller.signal // 👇 新增:绑定中断信号
});
clearTimeout(timeout); // 👇 新增:请求成功清除定时器
if (!response.ok) throw new Error(`HTTP 错误 ${response.status}`);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let summarize = '';
for (const line of lines) { while (true) {
if (line.startsWith('data: ')) { const { done, value } = await reader.read();
const content = line.substring(6); if (done) break;
const textMatch = content.match(/"解读":\s*"([^"]*)"/);
if (textMatch && textMatch[1]) summarize = textMatch[1]; buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.startsWith('data: ')) {
const content = line.substring(6);
const textMatch = content.match(/"解读":\s*"([^"]*)"/);
if (textMatch && textMatch[1]) summarize = textMatch[1];
}
} }
} }
param.value = summarize
} catch (err) {
param.value = "系统异常,生成失败";
} }
return summarize
} }
const handleBox5 = async () => { const handleBox5 = async () => {
...@@ -870,7 +884,7 @@ const handleGetDecreeArea = async () => { ...@@ -870,7 +884,7 @@ const handleGetDecreeArea = async () => {
value: item.count value: item.count
}; };
}); });
summarize2.value = await onChartInterpretation({type:"环形图",name:"领域分布情况",data:res.data}) onChartInterpretation({type:"环形图",name:"领域分布情况",data:res.data}, summarize2)
} }
} catch (error) { } catch (error) {
console.error("政令科技领域error", error); console.error("政令科技领域error", error);
...@@ -1194,7 +1208,7 @@ const handleSearch = () => { ...@@ -1194,7 +1208,7 @@ const handleSearch = () => {
// 关键机构 // 关键机构
const keyOrganizationList = ref([]); const keyOrganizationList = ref([]);
const onKeyOrganization = async (event) => { const onKeyOrganization = async (event) => {
let day = 7 let day = 365
if (event?.time === '近一周') day = 7 if (event?.time === '近一周') day = 7
if (event?.time === '近一月') day = 30 if (event?.time === '近一月') day = 30
if (event?.time === '近一年') day = 365 if (event?.time === '近一年') day = 365
......
...@@ -79,8 +79,9 @@ ...@@ -79,8 +79,9 @@
<div class="graph-box" v-if="contentType==1"> <div class="graph-box" v-if="contentType==1">
<ChartChain :listData="fishbone.list" :baseData="fishbone.base" /> <ChartChain :listData="fishbone.list" :baseData="fishbone.base" />
</div> </div>
<div class="graph-box" v-if="contentType==2 && graphInfo.nodes.length"> <div class="graph-box" v-if="contentType==2">
<GraphChart :nodes="graphInfo.nodes" :links="graphInfo.links" layoutType="force" /> <GraphChart v-if="graphInfo.nodes?.length" :nodes="graphInfo.nodes" :links="graphInfo.links" layoutType="force" />
<el-empty v-else style="padding: 60px 0" description="暂无数据" :image-size="100" />
</div> </div>
</div> </div>
</AnalysisBox> </AnalysisBox>
...@@ -168,6 +169,7 @@ const onDecreeEntities = async (page=1) => { ...@@ -168,6 +169,7 @@ const onDecreeEntities = async (page=1) => {
const contentType = ref(1); const contentType = ref(1);
const headerContentType = (type) => { const headerContentType = (type) => {
contentType.value = type; contentType.value = type;
if (!entityInfo.total) return;
headerChartData(entityInfo.node) headerChartData(entityInfo.node)
}; };
const headerChartData = (row) => { const headerChartData = (row) => {
......
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
<el-col :span="16"> <el-col :span="16">
<custom-container titleType="primary" title="最新出口管制政策" :titleIcon="houseIcon" height="450px"> <custom-container titleType="primary" title="最新出口管制政策" :titleIcon="houseIcon" height="450px">
<template #header-right> <template #header-right>
<el-button type="primary" @click="handleToEntityList()" link> <el-button type="primary" @click="handleToEntityList" link>
{{ "查看详情 >" }} {{ "查看详情 >" }}
</el-button> </el-button>
</template> </template>
...@@ -390,7 +390,7 @@ ...@@ -390,7 +390,7 @@
</custom-container> </custom-container>
</el-col> </el-col>
<el-col :span="16"> <el-col :span="16">
<custom-container title="实体清单数量增长趋势" :titleIcon="qushiIcon" height="540px"> <custom-container title="制裁清单数量增长趋势" :titleIcon="qushiIcon" height="540px">
<template #header-right> <template #header-right>
<div style="display: flex; align-items: center; gap: 16px"> <div style="display: flex; align-items: center; gap: 16px">
<el-checkbox v-model="trendChecked" label="50%规则" size="large" /> <el-checkbox v-model="trendChecked" label="50%规则" size="large" />
...@@ -995,8 +995,6 @@ onMounted(async () => { ...@@ -995,8 +995,6 @@ onMounted(async () => {
const maxCountItem1 = _.maxBy(cclList1, "count"); const maxCountItem1 = _.maxBy(cclList1, "count");
const maxCountForList1 = maxCountItem1 ? maxCountItem1.count : 0; const maxCountForList1 = maxCountItem1 ? maxCountItem1.count : 0;
console.log("shuju list", list);
console.log("shuju total", total);
tableData1.value = _.map(list, item => { tableData1.value = _.map(list, item => {
return { return {
year: item.year, year: item.year,
...@@ -1053,7 +1051,7 @@ const fetchTrendData = async () => { ...@@ -1053,7 +1051,7 @@ const fetchTrendData = async () => {
}); });
if (res && res[0] && res[0].yearDomainCount) { if (res && res[0] && res[0].yearDomainCount) {
trendOption.value = processYearDomainCountData(res[0].yearDomainCount); trendOption.value = processYearDomainCountData(res[0].yearDomainCount);
trendChart.interpret({ type: "柱状图", name: "实体清单数量增长趋势", data: res[0].yearDomainCount }); trendChart.interpret({ type: "柱状图", name: "制裁清单数量增长趋势", data: res[0].yearDomainCount });
} }
} catch (error) { } catch (error) {
console.error("获取趋势图数据失败:", error); console.error("获取趋势图数据失败:", error);
...@@ -1147,10 +1145,13 @@ const handleCarouselChange = index => { ...@@ -1147,10 +1145,13 @@ const handleCarouselChange = index => {
// 跳转到V2.0单次制裁 // 跳转到V2.0单次制裁
const handleToEntityList = item => { const handleToEntityList = item => {
console.log("这是什么数据1 =>", item);
let id = item?.id; let id = item?.id;
let sanTypeId = item?.sanTypeId || 1;
if (!id) { if (!id) {
const currentItem = entitiesDataInfoList.value[currentCarouselIndex.value]; const currentItem = entitiesDataInfoList.value[currentCarouselIndex.value];
id = currentItem?.id; id = currentItem?.id;
sanTypeId = currentItem?.sanTypeId || 1;
} }
window.sessionStorage.setItem( window.sessionStorage.setItem(
"curTabName", "curTabName",
...@@ -1159,7 +1160,8 @@ const handleToEntityList = item => { ...@@ -1159,7 +1160,8 @@ const handleToEntityList = item => {
const routeData = router.resolve({ const routeData = router.resolve({
path: "/exportControl/singleSanction", path: "/exportControl/singleSanction",
query: { query: {
id: id id,
sanTypeId
} }
}); });
// 打开一个新页面 // 打开一个新页面
...@@ -1388,8 +1390,8 @@ const fetchSanctionList = async () => { ...@@ -1388,8 +1390,8 @@ const fetchSanctionList = async () => {
const tags = Array.isArray(item.techDomains) const tags = Array.isArray(item.techDomains)
? item.techDomains ? item.techDomains
: item.techDomain : item.techDomain
? [item.techDomain] ? [item.techDomain]
: item.techDomainList || []; : item.techDomainList || [];
const fullTime = item.startTime const fullTime = item.startTime
? formatAnyDateToChinese(item.startTime) ? formatAnyDateToChinese(item.startTime)
...@@ -1419,8 +1421,8 @@ const fetchSanctionList = async () => { ...@@ -1419,8 +1421,8 @@ const fetchSanctionList = async () => {
countTag: item.cnEntityCount countTag: item.cnEntityCount
? `${item.cnEntityCount}家中国实体` ? `${item.cnEntityCount}家中国实体`
: item.ruleOrgCount : item.ruleOrgCount
? `${item.ruleOrgCount}家关联实体` ? `${item.ruleOrgCount}家关联实体`
: item.countTag || "" : item.countTag || ""
}; };
}); });
totalAll.value = res.totalElements; totalAll.value = res.totalElements;
...@@ -2363,7 +2365,8 @@ const handleMediaClick = item => { ...@@ -2363,7 +2365,8 @@ const handleMediaClick = item => {
overflow-y: auto; overflow-y: auto;
.home-top-bg { .home-top-bg {
background: url("./assets/images/background.png"), background:
url("./assets/images/background.png"),
linear-gradient(180deg, rgba(229, 241, 254, 1) 0%, rgba(246, 251, 255, 0) 30%); linear-gradient(180deg, rgba(229, 241, 254, 1) 0%, rgba(246, 251, 255, 0) 30%);
background-size: 100% 100%; background-size: 100% 100%;
position: absolute; position: absolute;
...@@ -3631,7 +3634,7 @@ const handleMediaClick = item => { ...@@ -3631,7 +3634,7 @@ const handleMediaClick = item => {
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: flex-start;
padding: 22px 0; padding: 22px 0;
.data-origin-icon { .data-origin-icon {
width: 16px; width: 16px;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论