提交 143ff2ac authored 作者: coderBryanFu's avatar coderBryanFu

feat:数据资源库更新

流水线 #247 已通过 于阶段
in 1 分 48 秒
...@@ -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);
}; };
......
...@@ -107,11 +107,12 @@ export function getBillPersonAnalyzeDy(params) { ...@@ -107,11 +107,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 +121,12 @@ export function getBillContentId(params) { ...@@ -120,11 +121,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 +135,12 @@ export function getBillContentTk(params) { ...@@ -133,11 +135,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 +149,12 @@ export function getBillContentXzfs(params) { ...@@ -146,11 +149,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({
......
差异被折叠。
...@@ -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
......
...@@ -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
......
<template>
<div class="intelligenceLeftTabBar">
<div class="navBox" :class="{navBoxShow:isNavMenuShow}">
<div class="navList" v-for="(item,index) in navList " :key="index" :class="{on:navPath==item.path}" @click="onNavListClick(item.path)">
<div class="icon" :style="{background:`url(${item.img})no-repeat`,backgroundSize:'24px 24px',backgroundPosition:'17px 17px'}"></div>
<span class="text-tip-1" style="white-space: nowrap; ">{{ item.name }}</span>
</div>
</div>
<img class="show" src="@/assets/icons/muenShow.png" :style="isNavMenuShow?'transform: scaleX(1)':''" alt="" @click="()=>{isNavMenuShow=!isNavMenuShow}">
</div>
</template>
<script setup>
import muen1 from '@/assets/icons/tool-item-icon1.png'
import muen2 from '@/assets/icons/tool-item-icon2.png'
import muen3 from '@/assets/icons/tool-item-icon3.png'
import muen4 from '@/assets/icons/tool-item-icon4.png'
import { onMounted, onUnmounted, ref, nextTick } from "vue";
import { useRoute } from "vue-router";
import { ElMessage } from "element-plus";
import { useWrittingAsstaintStore } from "@/stores/writtingAsstaintStore";
const isNavMenuShow=ref(false)
const navList=ref([
{
img:muen1,
path:'/writtingAsstaint',
name:'智能写报'
},
{
img:muen2,
path:'/writtingAsstaint1',
name:'智能翻译'
},
{
img:muen3,
path:'/writtingAsstaint2',
name:'智能查询'
},
{
img:muen4,
path:'/writtingAsstaint3',
name:'智能对话'
},
])
const navPath=ref()
const route=useRoute()
if(route.path){
navPath.value=route.path
}
const onNavListClick=(path)=>{
if(path=='/writtingAsstaint'){
navPath.value=path
}else{
ElMessage.error('正在开发中')
}
}
</script>
<style lang="scss" scoped>
.intelligenceLeftTabBar{
padding: 5px 0;
border-right: 1px solid rgb(234, 236, 238);
position: relative;
.navBox{
height: 100%;
width: 65px;
transition: all 0.3s;
padding: 0 3px;
.navList{
display: flex;
align-items: center;
cursor: pointer;
border-radius: 8px;
overflow: hidden;
height: 60px;
.icon{
width: 60px;
height: 60px;
flex-shrink: 0;
border-radius: 10px;
margin-right: 15px;
}
}
.on{
background-color: var(--color-primary-10);
color: var(--color-primary-100);
font-weight: Bold;
}
}
.navBoxShow{
width: 200px;
transition: all 0.3s;
}
.show{
position: absolute;
width: 24px;
height: 24px;
right: 21px;
bottom: 21px;
cursor: pointer;
transform: scaleX(-1)
}
}
</style>
\ No newline at end of file
...@@ -295,12 +295,15 @@ export function useMarkdownStream() { ...@@ -295,12 +295,15 @@ export function useMarkdownStream() {
// 预处理内容 // 预处理内容
// const processedContent = preprocessMarkdown(rawContent.value) // const processedContent = preprocessMarkdown(rawContent.value)
let content = rawContent.value || '' let content = rawContent.value || ''
// 将 ==n== 转换为按钮样式的 HTML // 将 ==n== 转换为按钮样式的 HTML
// 使用正向预读和反向预读确保只匹配被 == 包裹的数字 // 使用正向预读和反向预读确保只匹配被 == 包裹的数字
content = content.replace(/==(\d+)==/g, (match, p1) => { // content = content.replace(/==(\d+)、==/g, (match, p1) => {
return `<button class="clause-ref-btn" data-clause="${p1}">${p1}</button>` // return `<button class="clause-ref-btn" data-clause="${p1}">${p1}</button>`
}) // })
console.log(content,11223)
content = content.replace(/==\s*(\d+)、.*?==/g, (match, p1) => {
return `<button class="clause-ref-btn" data-clause="${match.replace(/==/g, '') }">${p1}</button>`;
});
return md.render(content) return md.render(content)
}) })
......
...@@ -69,7 +69,8 @@ router.beforeEach((to, from, next) => { ...@@ -69,7 +69,8 @@ router.beforeEach((to, from, next) => {
if (to.meta.title) { if (to.meta.title) {
if (to.meta.dynamicTitle) { if (to.meta.dynamicTitle) {
console.log('to', to); console.log('to', to);
document.title = window.sessionStorage.getItem("curTabName") || to.meta.title; const storageKey = to.meta.titleStorageKey || "curTabName";
document.title = window.sessionStorage.getItem(storageKey) || to.meta.title;
} else { } else {
document.title = to.meta.title document.title = to.meta.title
......
...@@ -20,7 +20,8 @@ const cooperationRestrictionsRoutes = [ ...@@ -20,7 +20,8 @@ const cooperationRestrictionsRoutes = [
component: CooperationRestrictionsDetail, component: CooperationRestrictionsDetail,
meta: { meta: {
title: "合作限制详情", title: "合作限制详情",
dynamicTitle: true dynamicTitle: true,
titleStorageKey: "cooperationRestrictionsTabName"
} }
}, },
......
...@@ -144,7 +144,7 @@ ...@@ -144,7 +144,7 @@
</el-select> </el-select>
</template> </template>
<div class="overview-card-body box5-main"> <div class="overview-card-body box5-main">
<div class="overview-chart-wrap" :class="{ 'is-empty': !box5HasData }"> <div class="overview-chart-wrap" v-loading="chartLoading.box5" :class="{ 'is-empty': !box5HasData }">
<el-empty v-if="!box5HasData" description="暂无数据" :image-size="100" /> <el-empty v-if="!box5HasData" description="暂无数据" :image-size="100" />
<div v-else id="box5Chart" class="overview-chart"></div> <div v-else id="box5Chart" class="overview-chart"></div>
</div> </div>
...@@ -169,7 +169,7 @@ ...@@ -169,7 +169,7 @@
</el-select> </el-select>
</template> </template>
<div class="overview-card-body box6-main"> <div class="overview-card-body box6-main">
<div class="overview-chart-wrap" :class="{ 'is-empty': !box9HasData }"> <div class="overview-chart-wrap" v-loading="chartLoading.box6" :class="{ 'is-empty': !box9HasData }">
<el-empty v-if="!box9HasData" description="暂无数据" :image-size="100" /> <el-empty v-if="!box9HasData" description="暂无数据" :image-size="100" />
<div v-else id="box9Chart" class="overview-chart"></div> <div v-else id="box9Chart" class="overview-chart"></div>
</div> </div>
...@@ -191,7 +191,7 @@ ...@@ -191,7 +191,7 @@
</el-select> </el-select>
</template> </template>
<div class="overview-card-body box7-main"> <div class="overview-card-body box7-main">
<div class="overview-chart-wrap" :class="{ 'is-empty': !box7HasData }"> <div class="overview-chart-wrap" v-loading="chartLoading.box7" :class="{ 'is-empty': !box7HasData }">
<el-empty v-if="!box7HasData" description="暂无数据" :image-size="100" /> <el-empty v-if="!box7HasData" description="暂无数据" :image-size="100" />
<div v-else id="box7Chart" class="overview-chart"></div> <div v-else id="box7Chart" class="overview-chart"></div>
</div> </div>
...@@ -211,10 +211,10 @@ ...@@ -211,10 +211,10 @@
</el-select> </el-select>
</template> </template>
<div class="overview-card-body box8-main"> <div class="overview-card-body box8-main">
<div class="overview-chart-wrap" :class="{ 'is-empty': !box8HasData }"> <div class="overview-chart-wrap" v-loading="chartLoading.box8" :class="{ 'is-empty': !box8HasData }">
<el-empty v-if="!box8HasData" description="暂无数据" :image-size="100" /> <el-empty v-if="!box8HasData" description="暂无数据" :image-size="100" />
<template v-else> <template v-else>
<div class="box8-desc">通过涉华法案{{ box8Summary }}</div> <div class="box8-desc">完成立法涉华法案{{ box8Summary }}</div>
<div id="box8Chart" class="overview-chart box8-chart"></div> <div id="box8Chart" class="overview-chart box8-chart"></div>
</template> </template>
</div> </div>
...@@ -229,7 +229,7 @@ ...@@ -229,7 +229,7 @@
</OverviewCard> </OverviewCard>
<OverviewCard class="overview-card--single box9" title="关键条款词云" :icon="box7HeaderIcon"> <OverviewCard class="overview-card--single box9" title="关键条款词云" :icon="box7HeaderIcon">
<div class="overview-card-body box9-main"> <div class="overview-card-body box9-main">
<div class="overview-chart-wrap"> <div class="overview-chart-wrap" v-loading="chartLoading.box9">
<el-empty v-if="!wordCloudHasData" description="暂无数据" :image-size="100" /> <el-empty v-if="!wordCloudHasData" description="暂无数据" :image-size="100" />
<WordCloundChart v-else class="overview-chart" width="100%" height="100%" :data="wordCloudData" /> <WordCloundChart v-else class="overview-chart" width="100%" height="100%" :data="wordCloudData" />
</div> </div>
...@@ -512,6 +512,14 @@ const aiPaneLoading = ref({ ...@@ -512,6 +512,14 @@ const aiPaneLoading = ref({
box9: false box9: false
}); });
const chartLoading = ref({
box5: false,
box6: false,
box7: false,
box8: false,
box9: false
});
const gotoNewsDetail = useGotoNewsDetail(); const gotoNewsDetail = useGotoNewsDetail();
const handleClickNewsDetail = news => { const handleClickNewsDetail = news => {
const newsId = news?.newsId || news?.id; const newsId = news?.newsId || news?.id;
...@@ -767,7 +775,7 @@ const box5Data = ref({ ...@@ -767,7 +775,7 @@ const box5Data = ref({
value: [145, 52, 84, 99, 71, 96, 128, 144, 140, 168, 188, 172] value: [145, 52, 84, 99, 71, 96, 128, 144, 140, 168, 188, 172]
}, },
{ {
name: "通过法案", name: "完成立法",
value: [6, 3, 4, 6, 11, 5, 2, 14, 16, 27, 28, 44] value: [6, 3, 4, 6, 11, 5, 2, 14, 16, 27, 28, 44]
}, },
{ {
...@@ -779,13 +787,14 @@ const box5Data = ref({ ...@@ -779,13 +787,14 @@ const box5Data = ref({
value: [] value: []
}, },
{ {
name: "双院通过", name: "解决分歧",
value: [] value: []
} }
] ]
}); });
const box5HasData = ref(true); const box5HasData = ref(true);
const handleGetBillCount = async () => { const handleGetBillCount = async () => {
chartLoading.value = { ...chartLoading.value, box5: true };
try { try {
const params = { const params = {
dateDesc: box5ProposalTime.value dateDesc: box5ProposalTime.value
...@@ -806,7 +815,7 @@ const handleGetBillCount = async () => { ...@@ -806,7 +815,7 @@ const handleGetBillCount = async () => {
value: sortedData.map(item => item.proposedCount) value: sortedData.map(item => item.proposedCount)
}, },
{ {
name: "通过法案", name: "完成立法",
value: sortedData.map(item => item.passCount) value: sortedData.map(item => item.passCount)
}, },
{ {
...@@ -818,7 +827,7 @@ const handleGetBillCount = async () => { ...@@ -818,7 +827,7 @@ const handleGetBillCount = async () => {
value: sortedData.map(item => item.senateCount) value: sortedData.map(item => item.senateCount)
}, },
{ {
name: "双院通过", name: "解决分歧",
value: sortedData.map(item => item.hscount) value: sortedData.map(item => item.hscount)
} }
], ],
...@@ -831,10 +840,10 @@ const handleGetBillCount = async () => { ...@@ -831,10 +840,10 @@ const handleGetBillCount = async () => {
title: [], title: [],
data: [ data: [
{ name: "提出法案", value: [] }, { name: "提出法案", value: [] },
{ name: "通过法案", value: [] }, { name: "完成立法", value: [] },
{ name: "众议院通过", value: [] }, { name: "众议院通过", value: [] },
{ name: "参议院通过", value: [] }, { name: "参议院通过", value: [] },
{ name: "双院通过", value: [] } { name: "解决分歧", value: [] }
], ],
percent: [] percent: []
}; };
...@@ -847,13 +856,15 @@ const handleGetBillCount = async () => { ...@@ -847,13 +856,15 @@ const handleGetBillCount = async () => {
title: [], title: [],
data: [ data: [
{ name: "提出法案", value: [] }, { name: "提出法案", value: [] },
{ name: "通过法案", value: [] }, { name: "完成立法", value: [] },
{ name: "众议院通过", value: [] }, { name: "众议院通过", value: [] },
{ name: "参议院通过", value: [] }, { name: "参议院通过", value: [] },
{ name: "双院通过", value: [] } { name: "解决分歧", value: [] }
], ],
percent: [] percent: []
}; };
} finally {
chartLoading.value = { ...chartLoading.value, box5: false };
} }
}; };
...@@ -895,6 +906,7 @@ const handleBox5Change = () => { ...@@ -895,6 +906,7 @@ const handleBox5Change = () => {
const box7HasData = ref(true); const box7HasData = ref(true);
const box7AiData = ref({ inner: [], outer: [] }); const box7AiData = ref({ inner: [], outer: [] });
const handleBox7Data = async () => { const handleBox7Data = async () => {
chartLoading.value = { ...chartLoading.value, box7: true };
try { try {
const res = await getBillPostOrg({ year: box7selectetedTime.value }); const res = await getBillPostOrg({ year: box7selectetedTime.value });
console.log("法案提出部门", res); console.log("法案提出部门", res);
...@@ -949,6 +961,8 @@ const handleBox7Data = async () => { ...@@ -949,6 +961,8 @@ const handleBox7Data = async () => {
console.error("获取法案提出部门数据失败", error); console.error("获取法案提出部门数据失败", error);
box7HasData.value = false; box7HasData.value = false;
box7AiData.value = { inner: [], outer: [] }; box7AiData.value = { inner: [], outer: [] };
} finally {
chartLoading.value = { ...chartLoading.value, box7: false };
} }
}; };
...@@ -975,6 +989,7 @@ const handleToSocialDetail = item => { ...@@ -975,6 +989,7 @@ const handleToSocialDetail = item => {
const wordCloudData = ref([]); const wordCloudData = ref([]);
const wordCloudHasData = computed(() => Array.isArray(wordCloudData.value) && wordCloudData.value.length > 0); const wordCloudHasData = computed(() => Array.isArray(wordCloudData.value) && wordCloudData.value.length > 0);
const handleGetKeyTK = async () => { const handleGetKeyTK = async () => {
chartLoading.value = { ...chartLoading.value, box9: true };
try { try {
const res = await getBillOverviewKeyTK(); const res = await getBillOverviewKeyTK();
console.log("关键条款", res); console.log("关键条款", res);
...@@ -992,6 +1007,8 @@ const handleGetKeyTK = async () => { ...@@ -992,6 +1007,8 @@ const handleGetKeyTK = async () => {
} }
} catch (error) { } catch (error) {
console.error("获取关键条款error", error); console.error("获取关键条款error", error);
} finally {
chartLoading.value = { ...chartLoading.value, box9: false };
} }
}; };
const handleBox6 = async () => { const handleBox6 = async () => {
...@@ -1001,10 +1018,15 @@ const handleBox6 = async () => { ...@@ -1001,10 +1018,15 @@ const handleBox6 = async () => {
// 涉华领域分布 // 涉华领域分布
const box9ChartData = ref([]); const box9ChartData = ref([]);
const box9selectetedTime = ref("2025"); const box9selectetedTime = ref("2025");
const box9LegislativeStatus = ref("提出法案"); // 立法状态下拉:提出法案、众议院通过、参议院通过、解决分歧、完成立法
// v-model 存储的是接口需要的 status 值
const box9LegislativeStatus = ref("提案");
const box9LegislativeStatusList = ref([ const box9LegislativeStatusList = ref([
{ label: "提出法案", value: "提出法案" }, { label: "提出法案", value: "提案" },
{ label: "通过法案", value: "通过法案" } { label: "众议院通过", value: "众议院通过" },
{ label: "参议院通过", value: "参议院通过" },
{ label: "解决分歧", value: "分歧已解决" },
{ label: "完成立法", value: "完成立法" }
]); ]);
const box9YearList = ref([ const box9YearList = ref([
{ {
...@@ -1031,6 +1053,7 @@ const box9YearList = ref([ ...@@ -1031,6 +1053,7 @@ const box9YearList = ref([
const box9HasData = ref(true); const box9HasData = ref(true);
let box9ChartInstance = null; let box9ChartInstance = null;
const getBox9Data = async () => { const getBox9Data = async () => {
chartLoading.value = { ...chartLoading.value, box6: true };
const params = { const params = {
year: box9selectetedTime.value, year: box9selectetedTime.value,
status: box9LegislativeStatus.value status: box9LegislativeStatus.value
...@@ -1048,6 +1071,8 @@ const getBox9Data = async () => { ...@@ -1048,6 +1071,8 @@ const getBox9Data = async () => {
} catch (error) { } catch (error) {
box9HasData.value = false; box9HasData.value = false;
box9ChartData.value = []; box9ChartData.value = [];
} finally {
chartLoading.value = { ...chartLoading.value, box6: false };
} }
}; };
const handleBox9Data = async () => { const handleBox9Data = async () => {
...@@ -1064,6 +1089,10 @@ const handleBox9Data = async () => { ...@@ -1064,6 +1089,10 @@ const handleBox9Data = async () => {
null, null,
{ showCount: false } { showCount: false }
); );
// 记录埋点时,将当前选中的立法状态映射为序号(0-4)
const selectedIndex = box9LegislativeStatusList.value.findIndex(
item => item.value === box9LegislativeStatus.value
);
const selectParam = { const selectParam = {
moduleType: '国会法案', moduleType: '国会法案',
key: 2, key: 2,
...@@ -1091,14 +1120,13 @@ const box8StageList = ref([]); ...@@ -1091,14 +1120,13 @@ const box8StageList = ref([]);
const box8MockDataByYear = { const box8MockDataByYear = {
"2025": { "2025": {
passCount: 19, passCount: 19,
// 从上到下顺序:提出法案、众议院通过、参议院通过、解决分歧、完成立法
stages: [ stages: [
{ name: "已提案", count: 24 }, { name: "提出法案", count: 24 },
{ name: "众议院通过", count: 20 }, { name: "众议院通过", count: 20 },
{ name: "众议院不通过", count: 25 },
{ name: "解决分歧", count: 23 },
{ name: "参议院通过", count: 26 }, { name: "参议院通过", count: 26 },
{ name: "总统否决", count: 48 }, { name: "解决分歧", count: 23 },
{ name: "立法", count: 19 } { name: "完成立法", count: 19 }
] ]
} }
}; };
...@@ -1222,15 +1250,15 @@ const getBox8ChartOption = stageList => { ...@@ -1222,15 +1250,15 @@ const getBox8ChartOption = stageList => {
}; };
const handleBox8Data = async () => { const handleBox8Data = async () => {
const stageOrder = ["提案", "众议院通过", "众议院不通过", "分歧已解决", "参议院通过", "总统否决或未签署", "完成立法"]; chartLoading.value = { ...chartLoading.value, box8: true };
// 进展分布显示顺序:提出法案(对应进度“提案”)、众议院通过、参议院通过、分歧已解决(解决分歧)、完成立法
const stageOrder = ["提案", "众议院通过", "参议院通过", "分歧已解决", "完成立法"];
const stageNameMap = { const stageNameMap = {
提案: "已提案", 提案: "提出法案",
众议院通过: "众议院通过", 众议院通过: "众议院通过",
众议院不通过: "众议院不通过",
分歧已解决: "解决分歧",
参议院通过: "参议院通过", 参议院通过: "参议院通过",
"总统否决或未签署": "总统否决", 分歧已解决: "解决分歧",
完成立法: "立法" 完成立法: "完成立法"
}; };
try { try {
...@@ -1289,6 +1317,8 @@ const handleBox8Data = async () => { ...@@ -1289,6 +1317,8 @@ const handleBox8Data = async () => {
box8StageList.value = []; box8StageList.value = [];
setChart({}, "box8Chart", true, selectParam); setChart({}, "box8Chart", true, selectParam);
} }
} finally {
chartLoading.value = { ...chartLoading.value, box8: false };
} }
}; };
......
...@@ -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
} }
] ]
} }
......
<svg viewBox="0 0 12 13" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12.000000" height="13.000000" fill="none" customFrame="#000000">
<path id="矢量 1651" d="M8.05031 2.15239e-07C8.33835 2.69049e-07 8.61901 0.0965686 8.85205 0.275865C9.08508 0.455161 9.25853 0.70798 9.34755 0.998087C9.43656 1.28819 9.43656 1.6007 9.34755 1.8908C9.25853 2.18091 9.08508 2.43373 8.85205 2.61303C8.61901 2.79232 8.33835 2.88889 8.05031 2.88889L3.9583 2.88889C3.69558 2.88873 3.43848 2.80821 3.21793 2.65703C2.99737 2.50585 2.82274 2.29043 2.71502 2.03667C2.35984 2.08328 2.0252 2.23849 1.752 2.48332C1.4788 2.72814 1.27889 3.05197 1.17673 3.41519C1.07456 3.77842 1.07456 4.1653 1.17673 4.52853C1.27889 4.89176 1.4788 5.21558 1.752 5.46041C2.0252 5.70523 2.35984 5.86044 2.71502 5.90706C2.82284 5.65343 2.99751 5.43816 3.21806 5.28711C3.43861 5.13606 3.69565 5.05566 3.9583 5.05556L8.05031 5.05556C8.31755 5.05563 8.57888 5.13883 8.80185 5.29483C9.02483 5.45084 9.19961 5.67276 9.3045 5.93306C9.88944 5.98317 10.4467 6.21802 10.9042 6.60722C11.3618 6.99642 11.6985 7.52206 11.8708 8.11613C12.0431 8.7102 12.0431 9.34536 11.8708 9.93943C11.6985 10.5335 11.3618 11.0591 10.9042 11.4483C10.4467 11.8375 9.88944 12.0724 9.3045 12.1225C9.19961 12.3828 9.02483 12.6047 8.80185 12.7607C8.57888 12.9167 8.31755 12.9999 8.05031 13L3.9583 13C3.71881 13.0001 3.48352 12.9334 3.27608 12.8067C3.06865 12.6799 2.89638 12.4976 2.77661 12.2779C2.65684 12.0583 2.59378 11.8092 2.59378 11.5556C2.59378 11.3019 2.65684 11.0528 2.77661 10.8332C2.89638 10.6136 3.06865 10.4312 3.27608 10.3044C3.48352 10.1777 3.71881 10.111 3.9583 10.1111L8.05031 10.1111C8.60409 10.1111 9.07944 10.4607 9.29291 10.9626C9.64884 10.9171 9.98445 10.7624 10.2585 10.5177C10.5326 10.273 10.7333 9.94881 10.8358 9.58501C10.9384 9.22121 10.9384 8.83362 10.8358 8.46982C10.7333 8.10603 10.5326 7.78186 10.2585 7.53714C9.98445 7.29241 9.64884 7.13778 9.29291 7.09222C9.18531 7.34593 9.0108 7.56132 8.79036 7.71251C8.56992 7.86369 8.31294 7.94423 8.05031 7.94444L3.9583 7.94444C3.69087 7.94445 3.42933 7.86121 3.20622 7.70506C2.9831 7.54892 2.80826 7.32676 2.70342 7.06622C2.11732 7.01784 1.55849 6.78394 1.09952 6.39491C0.640554 6.00587 0.302655 5.47968 0.129709 4.88468C-0.0432364 4.28967 -0.0432363 3.65334 0.129709 3.05833C0.302654 2.46332 0.640554 1.93713 1.09952 1.54809C1.55849 1.15906 2.11732 0.925163 2.70342 0.876778C2.80847 0.616508 2.9834 0.394654 3.2065 0.238777C3.4296 0.0829001 3.69102 -0.000130273 3.9583 2.15239e-07L8.05031 2.15239e-07Z" fill="rgb(95,101,108)" fill-rule="nonzero" />
</svg>
<svg viewBox="0 0 12 13.3145" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12.000000" height="13.314453" fill="none" customFrame="#000000">
<path id="矢量 52" d="M2.47123 9.11862L8.566 3.02386C8.82631 2.76355 8.82631 2.3415 8.566 2.08119C8.30569 1.82088 7.88364 1.82088 7.62333 2.08119L1.52857 8.17595C1.40356 8.30096 1.33333 8.4705 1.33333 8.64729C1.33333 9.01542 1.63176 9.31385 1.9999 9.31385C2.17668 9.31385 2.34623 9.24363 2.47123 9.11862ZM2.82867 10.6472L1 10.6472C0.447715 10.6472 0 10.1995 0 9.64719L0 8.23273C0 7.96752 0.105357 7.71316 0.292894 7.52563L7.62333 0.195193C7.74833 0.0702133 7.91787 0 8.09467 0C8.27147 0 8.441 0.0702133 8.566 0.195193L10.452 2.08119C10.577 2.20621 10.6472 2.37575 10.6472 2.55253C10.6472 2.7293 10.577 2.89884 10.452 3.02386L2.82867 10.6472L2.82867 10.6472ZM0.666666 11.9805L11.3333 11.9805C11.7015 11.9805 12 12.279 12 12.6472C12 13.0154 11.7015 13.3139 11.3333 13.3139L0.666667 13.3139C0.298477 13.3139 0 13.0154 0 12.6472C0 12.279 0.298477 11.9805 0.666666 11.9805Z" fill="rgb(255,255,255)" fill-rule="evenodd" />
</svg>
...@@ -27,24 +27,9 @@ ...@@ -27,24 +27,9 @@
</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>
<div class="name">
<el-skeleton-item class="skeleton-tab-text" variant="text" />
</div>
</div>
</template>
<template v-else>
<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"> <div class="icon">
<img v-if="activeTitle === item.name" :src="item.activeIcon" alt="" /> <img v-if="activeTitle === item.name" :src="item.activeIcon" alt="" />
<img v-else :src="item.icon" alt="" /> <img v-else :src="item.icon" alt="" />
...@@ -53,7 +38,6 @@ ...@@ -53,7 +38,6 @@
{{ item.name }} {{ item.name }}
</div> </div>
</div> </div>
</template>
</div> </div>
</div> </div>
...@@ -77,24 +61,18 @@ ...@@ -77,24 +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">
<el-skeleton-item class="skeleton-action-icon" variant="text" /> <img :src="btnIconForsee" alt="" />
</div>
<div class="text">
<el-skeleton-item class="skeleton-action-text" variant="text" />
</div> </div>
<div class="text">{{ "进展预测" }}</div>
</div> </div>
</template> <div class="btn3" @click="emit('open-analysis', 'analysis')">
<template v-else>
<div class="btn3" @click="emit('open-analysis')">
<div class="icon"> <div class="icon">
<img :src="btnIconAnalysis" alt="" /> <img :src="btnIconAnalysis" alt="" />
</div> </div>
<div class="text">{{ "分析报告" }}</div> <div class="text">{{ "分析报告" }}</div>
</div> </div>
</template>
</div> </div>
</div> </div>
</div> </div>
...@@ -103,7 +81,8 @@ ...@@ -103,7 +81,8 @@
<script setup> <script setup>
import { computed } from "vue"; import { computed } from "vue";
import btnIconAnalysis from "@/views/thinkTank/ReportDetail/images/btn-icon3.png"; import btnIconAnalysis from "@/views/bill/billLayout/assets/icons/writting-icon.svg";
import btnIconForsee from "@/views/bill/billLayout/assets/icons/forsee-icon.svg";
const props = defineProps({ const props = defineProps({
billInfo: { billInfo: {
...@@ -347,6 +326,29 @@ const emit = defineEmits(["tab-click", "open-analysis"]); ...@@ -347,6 +326,29 @@ const emit = defineEmits(["tab-click", "open-analysis"]);
justify-content: flex-end; justify-content: flex-end;
gap: 8px; gap: 8px;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.btn2 {
cursor: pointer;
width: 120px;
height: 36px;
border-radius: 6px;
background: var(--bg-white-100);
border: 1px solid var(--bg-black-10);
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
.btn3 { .btn3 {
cursor: pointer; cursor: pointer;
width: 120px; width: 120px;
...@@ -358,16 +360,6 @@ const emit = defineEmits(["tab-click", "open-analysis"]); ...@@ -358,16 +360,6 @@ const emit = defineEmits(["tab-click", "open-analysis"]);
align-items: center; align-items: center;
gap: 8px; gap: 8px;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text { .text {
height: 24px; height: 24px;
color: rgba(255, 255, 255, 1); color: rgba(255, 255, 255, 1);
...@@ -387,4 +379,3 @@ const emit = defineEmits(["tab-click", "open-analysis"]); ...@@ -387,4 +379,3 @@ const emit = defineEmits(["tab-click", "open-analysis"]);
} }
} }
</style> </style>
...@@ -2,16 +2,9 @@ ...@@ -2,16 +2,9 @@
<div class="layout-container"> <div class="layout-container">
<!-- 导航菜单 --> <!-- 导航菜单 -->
<div class="layout-main"> <div class="layout-main">
<BillHeader <BillHeader :billInfo="billInfoGlobal" :defaultLogo="USALogo" :tabs="mainHeaderBtnList"
:billInfo="billInfoGlobal" :activeTitle="activeTitle" :showTabs="showHeaderTabs" :showActions="showHeaderActions"
:defaultLogo="USALogo" @tab-click="handleClickMainHeaderBtn" @open-analysis="handleAnalysisClick" />
:tabs="mainHeaderBtnList"
:activeTitle="activeTitle"
:showTabs="showHeaderTabs"
:showActions="showHeaderActions"
@tab-click="handleClickMainHeaderBtn"
@open-analysis="handleAnalysisClick"
/>
<div class="layout-main-center"> <div class="layout-main-center">
<router-view /> <router-view />
...@@ -115,12 +108,24 @@ const handleClickMainHeaderBtn = item => { ...@@ -115,12 +108,24 @@ const handleClickMainHeaderBtn = item => {
}); });
}; };
const handleAnalysisClick = () => { const handleAnalysisClick = analysisType => {
const billId = route.query.billId;
if (!billId) return;
// 进展预测 -> 法案简介页(法案进展)
if (analysisType === "forsee") {
router.push({
path: `/billLayout/ProgressForecast/${billId}`,
});
return;
}
// 分析报告 -> 写作助手
router.push({ router.push({
path: "/writtingAsstaint", path: "/writtingAsstaint",
query: { query: {
topic: "法案", topic: "法案",
fileId: route.query.billId fileId: String(billId)
} }
}); });
}; };
...@@ -149,11 +154,13 @@ watch( ...@@ -149,11 +154,13 @@ watch(
// height: 1016px; // height: 1016px;
background: rgba(249, 250, 252, 1); background: rgba(249, 250, 252, 1);
position: relative; position: relative;
// margin: 0 auto; // margin: 0 auto;
.layout-main { .layout-main {
width: 100%; width: 100%;
height: calc(100vh - 72px); height: 100vh;
overflow-y: auto; overflow-y: auto;
.layout-main-center { .layout-main-center {
// height: calc(100% - 137px); // height: calc(100% - 137px);
width: 1600px; width: 1600px;
......
...@@ -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>
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
<div class="left-top"> <div class="left-top">
<img src="./assets/icon01.png" alt="" /> <img src="./assets/icon01.png" alt="" />
<div class="left-top-title">合作限制动态</div> <div class="left-top-title">合作限制动态</div>
<div class="more" @click="handleClickToDetail">查看详情 ></div> <div class="more" @click="handleClickToDetail">{{ "查看详情 >" }}</div>
</div> </div>
<el-carousel ref="carouselRef" height="412px" direction="horizontal" :autoplay="true" :interval="5000" <el-carousel ref="carouselRef" height="412px" direction="horizontal" :autoplay="true" :interval="5000"
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
</div> </div>
</div> </div>
<div class="left-center-type" v-if="item.type">{{ item.type }}</div> <div class="left-center-type" v-if="item.limitMeans">{{ item.limitMeans }}</div>
<!-- <div class="left-center-title">{{ item.LIMITTYPE }}</div> --> <!-- <div class="left-center-title">{{ item.LIMITTYPE }}</div> -->
</div> </div>
<div class="left-bottom"> <div class="left-bottom">
...@@ -188,12 +188,12 @@ const riskSignals = ref([]); ...@@ -188,12 +188,12 @@ const riskSignals = ref([]);
// 点击查看详情 // 点击查看详情
const handleClickToDetail = item => { const handleClickToDetail = item => {
const activeItem = item && item.ID ? item : mainTrend.value; const activeItem = item && item.ID ? item : mainTrend.value;
const id = activeItem?.ID; const id = activeItem?.ID || activeItem?.id || activeItem?.limitId;
if (!id) return; if (!id) return;
window.sessionStorage.setItem("curTabName", activeItem?.LIMITNAME); window.sessionStorage.setItem("cooperationRestrictionsTabName", activeItem?.LIMITNAME || "");
const curRoute = router.resolve({ const curRoute = router.resolve({
path: "/cooperationRestrictions/detail", name: "CooperationRestrictionsDetail",
query: { id: id } query: { id: id }
}); });
window.open(curRoute.href, "_blank"); window.open(curRoute.href, "_blank");
...@@ -201,9 +201,11 @@ const handleClickToDetail = item => { ...@@ -201,9 +201,11 @@ const handleClickToDetail = item => {
// 点击风险信号详情 // 点击风险信号详情
const handleToRiskDetail = (item) => { const handleToRiskDetail = (item) => {
const id = item?.cooperationId || item?.ID || item?.id || item?.limitId;
if (!id) return;
const curRoute = router.resolve({ const curRoute = router.resolve({
path: "/cooperationRestrictions/detail", name: "CooperationRestrictionsDetail",
query: { id: item.cooperationId }, query: { id },
}); });
window.open(curRoute.href, "_blank"); window.open(curRoute.href, "_blank");
}; };
...@@ -318,7 +320,7 @@ onMounted(() => { ...@@ -318,7 +320,7 @@ onMounted(() => {
width: 967px; width: 967px;
height: 208px; height: 208px;
margin-top: 33px; margin-top: 33px;
margin-left: 62px; margin-left: 57px;
border-bottom: 1px solid rgb(234, 236, 238); border-bottom: 1px solid rgb(234, 236, 238);
position: relative; position: relative;
...@@ -326,6 +328,7 @@ onMounted(() => { ...@@ -326,6 +328,7 @@ onMounted(() => {
width: 148px; width: 148px;
height: 148px; height: 148px;
margin-right: 21px; margin-right: 21px;
margin-left: 5px;
} }
display: flex; display: flex;
......
...@@ -4,7 +4,8 @@ ...@@ -4,7 +4,8 @@
<div class="left-title"> <div class="left-title">
<img src="./assets/icon01.png" alt="" /> <img src="./assets/icon01.png" alt="" />
<div class="tit">各类型合作限制政策对比</div> <div class="tit">各类型合作限制政策对比</div>
<el-select v-model="value" placeholder="Select" class="select" @change="getCoopRestrictionCompareData"> <el-select v-model="value" placeholder="Select" class="select" popper-class="coop-select-dropdown"
@change="getCoopRestrictionCompareData">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
</div> </div>
...@@ -34,7 +35,8 @@ ...@@ -34,7 +35,8 @@
<div class="right-title"> <div class="right-title">
<img src="./assets/icon02.png" alt="" /> <img src="./assets/icon02.png" alt="" />
<div class="tit">各领域规则分布情况</div> <div class="tit">各领域规则分布情况</div>
<el-select v-model="value1" placeholder="Select" class="select" @change="getCoopRestrictionDomainData"> <el-select v-model="value1" placeholder="Select" class="select" popper-class="coop-select-dropdown"
@change="getCoopRestrictionDomainData">
<el-option v-for="item in options1" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in options1" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
</div> </div>
...@@ -75,6 +77,22 @@ import AiPane from "@/components/base/Ai/AiPane/index.vue"; ...@@ -75,6 +77,22 @@ import AiPane from "@/components/base/Ai/AiPane/index.vue";
const COOP_LEFT_TIP_TEXT = "各类型合作限制政策对比,数据来源:美对华科技合作限制信息平台"; const COOP_LEFT_TIP_TEXT = "各类型合作限制政策对比,数据来源:美对华科技合作限制信息平台";
const COOP_RIGHT_TIP_TEXT = "各领域规则分布情况,数据来源:美对华科技合作限制信息平台"; const COOP_RIGHT_TIP_TEXT = "各领域规则分布情况,数据来源:美对华科技合作限制信息平台";
// 临时展示 mock(不改样式):右侧“各领域规则分布情况”
// 用完把这个开关改回 false 即可恢复走接口
const USE_DOMAIN_MOCK = false;
const MOCK_COOP_RESTRICTION_DOMAIN = [
{ COOPERTYPE: "科研合作", COOPERTYPECOUNT: 1, AREA: "人工智能" },
{ COOPERTYPE: "科研合作", COOPERTYPECOUNT: 1, AREA: "生物科技" },
{ COOPERTYPE: "技术合作", COOPERTYPECOUNT: 2, AREA: "大数据" },
{ COOPERTYPE: "产学研合作", COOPERTYPECOUNT: 3, AREA: "新能源" },
{ COOPERTYPE: "项目合作", COOPERTYPECOUNT: 1, AREA: "智能制造" },
{ COOPERTYPE: "人才合作", COOPERTYPECOUNT: 2, AREA: "集成电路" },
{ COOPERTYPE: "科研合作", COOPERTYPECOUNT: 2, AREA: "大数据" },
{ COOPERTYPE: "科研合作", COOPERTYPECOUNT: 3, AREA: "新能源" },
{ COOPERTYPE: "科研合作", COOPERTYPECOUNT: 1, AREA: "智能制造" },
{ COOPERTYPE: "科研合作", COOPERTYPECOUNT: 2, AREA: "集成电路" }
];
const value = ref(10); const value = ref(10);
const value1 = ref("2025"); const value1 = ref("2025");
const options = [ const options = [
...@@ -109,6 +127,10 @@ const options1 = [ ...@@ -109,6 +127,10 @@ const options1 = [
const coopRestrictionDomain = ref([]); const coopRestrictionDomain = ref([]);
const getCoopRestrictionDomainData = async () => { const getCoopRestrictionDomainData = async () => {
if (USE_DOMAIN_MOCK) {
coopRestrictionDomain.value = MOCK_COOP_RESTRICTION_DOMAIN;
return;
}
try { try {
const res = await getCoopRestrictionDomain({ const res = await getCoopRestrictionDomain({
year: value1.value year: value1.value
...@@ -449,10 +471,18 @@ const initLeftChart = () => { ...@@ -449,10 +471,18 @@ const initLeftChart = () => {
const option = { const option = {
color: colorMap.map((c) => c.line), color: colorMap.map((c) => c.line),
grid: { left: 40, right: 24, top: 46, bottom: 36 }, // 与智库概览「数量变化趋势」一致:预留图例空间,并用 containLabel 让轴文字不挤压绘图区
grid: {
top: "34%",
right: "3%",
bottom: "5%",
left: "2%",
containLabel: true
},
tooltip: { trigger: "axis", axisPointer: { type: "line" } }, tooltip: { trigger: "axis", axisPointer: { type: "line" } },
legend: { legend: {
top: 8, top: 8,
left: "center",
icon: "circle", icon: "circle",
itemWidth: 12, itemWidth: 12,
itemHeight: 12, itemHeight: 12,
...@@ -572,6 +602,10 @@ const initRightChart = () => { ...@@ -572,6 +602,10 @@ const initRightChart = () => {
const domains = Array.from(domainsSet); const domains = Array.from(domainsSet);
const types = Array.from(typesSet); const types = Array.from(typesSet);
const legendSplitAt = Math.ceil(types.length / 2);
const legendFirstLine = types.slice(0, legendSplitAt);
const legendSecondLine = types.slice(legendSplitAt);
const indicators = domains.map((domain) => { const indicators = domains.map((domain) => {
const domainData = rawData.filter((item) => item.AREA === domain); const domainData = rawData.filter((item) => item.AREA === domain);
const maxVal = Math.max(...domainData.map((d) => d.COOPERTYPECOUNT), 5); const maxVal = Math.max(...domainData.map((d) => d.COOPERTYPECOUNT), 5);
...@@ -589,15 +623,21 @@ const initRightChart = () => { ...@@ -589,15 +623,21 @@ const initRightChart = () => {
name: type, name: type,
value: dataValues, value: dataValues,
itemStyle: { color: colorMap[index % colorMap.length] }, itemStyle: { color: colorMap[index % colorMap.length] },
// 不要填充多边形:让雷达图“圆里面是空的” // 雷达图围成区域填充:对应颜色 0.1 透明度
// areaStyle 不设置(或设为 0)可避免穿透同心圆的填充效果 areaStyle: { color: colorMap[index % colorMap.length], opacity: 0.1 }
}; };
}); });
const option = { const option = {
color: colorMap, color: colorMap,
legend: { // 避免自动换行导致“第二行不居中”:拆成两行 legend,每行各自居中
legend: [
{
show: true,
type: "plain",
data: legendFirstLine,
top: 8, top: 8,
left: "center",
icon: "circle", icon: "circle",
itemWidth: 12, itemWidth: 12,
itemHeight: 12, itemHeight: 12,
...@@ -608,12 +648,31 @@ const initRightChart = () => { ...@@ -608,12 +648,31 @@ const initRightChart = () => {
fontFamily: "Microsoft YaHei", fontFamily: "Microsoft YaHei",
fontWeight: 400, fontWeight: 400,
lineHeight: 24 lineHeight: 24
}
}, },
data: types {
}, show: legendSecondLine.length > 0,
type: "plain",
data: legendSecondLine,
top: 32,
left: "center",
icon: "circle",
itemWidth: 12,
itemHeight: 12,
itemGap: 24,
textStyle: {
color: "rgb(95, 101, 108)",
fontSize: 16,
fontFamily: "Microsoft YaHei",
fontWeight: 400,
lineHeight: 24
}
}
],
radar: { radar: {
center: ["50%", "55%"], // 对齐左侧折线图(grid top=34%)的“图例到图形”间距:下移雷达中心并略缩半径
radius: "65%", center: ["50%", "62%"],
radius: "60%",
indicator: indicators, indicator: indicators,
axisName: { axisName: {
color: "rgba(132, 136, 142, 1)", color: "rgba(132, 136, 142, 1)",
...@@ -688,6 +747,11 @@ onBeforeUnmount(() => { ...@@ -688,6 +747,11 @@ onBeforeUnmount(() => {
padding: 0; padding: 0;
} }
/* 合作限制:下拉项内边距(teleport 到 body,用 :global 生效) */
:global(.coop-select-dropdown .el-select-dropdown__item) {
padding: 0 20px !important;
}
.datasub { .datasub {
width: 1600px; width: 1600px;
height: 460px; height: 460px;
...@@ -742,7 +806,7 @@ onBeforeUnmount(() => { ...@@ -742,7 +806,7 @@ onBeforeUnmount(() => {
height: 412px; height: 412px;
box-sizing: border-box; box-sizing: border-box;
position: relative; position: relative;
padding: 24px 24px 65px 24px; padding: 0px 24px 64px 24px;
&.left-main--empty { &.left-main--empty {
display: flex; display: flex;
...@@ -770,7 +834,7 @@ onBeforeUnmount(() => { ...@@ -770,7 +834,7 @@ onBeforeUnmount(() => {
.left-main-echarts { .left-main-echarts {
width: 1015px; width: 1015px;
height: 323px; height: 348px;
} }
.source { .source {
...@@ -847,7 +911,7 @@ onBeforeUnmount(() => { ...@@ -847,7 +911,7 @@ onBeforeUnmount(() => {
width: 521px; width: 521px;
height: 412px; height: 412px;
box-sizing: border-box; box-sizing: border-box;
padding: 24px 24px 64px 24px; padding: 0px 24px 64px 24px;
position: relative; position: relative;
&.right-main--empty { &.right-main--empty {
...@@ -874,7 +938,7 @@ onBeforeUnmount(() => { ...@@ -874,7 +938,7 @@ onBeforeUnmount(() => {
.right-main-echarts { .right-main-echarts {
width: 473px; width: 473px;
height: 324px; height: 348px;
} }
.source { .source {
......
...@@ -6,7 +6,8 @@ ...@@ -6,7 +6,8 @@
{{ item.name }} {{ item.name }}
</div> </div>
</div> </div>
<el-select v-model="sortModel" placeholder="发布时间" class="select" :teleported="true" placement="bottom-start" <el-select v-model="sortModel" placeholder="发布时间" class="select" popper-class="coop-select-dropdown"
:teleported="true" placement="bottom-start"
:popper-options="sortPopperOptions" @change="handleSortChange"> :popper-options="sortPopperOptions" @change="handleSortChange">
<template #prefix> <template #prefix>
<img v-if="sortModel !== true" src="@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image down.png" <img v-if="sortModel !== true" src="@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image down.png"
...@@ -120,8 +121,8 @@ const getMainDataList = async () => { ...@@ -120,8 +121,8 @@ const getMainDataList = async () => {
date: item.limitDate, date: item.limitDate,
domain: item.limitArea || [], domain: item.limitArea || [],
type: item.limitMeans, type: item.limitMeans,
// 使用默认图片 // 优先使用接口返回的机构 logo(limitOrgLogo),空则回退默认图
img: defaultImg img: item.limitOrgLogo || defaultImg
})); }));
total.value = res.data.totalElements || 0; total.value = res.data.totalElements || 0;
} else { } else {
...@@ -138,10 +139,16 @@ const getMainDataList = async () => { ...@@ -138,10 +139,16 @@ const getMainDataList = async () => {
const router = useRouter(); const router = useRouter();
const handleClick = item => { const handleClick = item => {
const id = item?.id || item?.limitId || item?.ID;
if (!id) return;
window.sessionStorage.setItem(
"cooperationRestrictionsTabName",
item?.limitName || item?.title || item?.name || ""
);
const routeData = router.resolve({ const routeData = router.resolve({
path: "/cooperationRestrictions/detail", name: "CooperationRestrictionsDetail",
query: { query: {
id: item.id id
} }
}); });
window.open(routeData.href, "_blank"); window.open(routeData.href, "_blank");
...@@ -355,6 +362,11 @@ watch(currentPage, () => { ...@@ -355,6 +362,11 @@ watch(currentPage, () => {
padding: 0; padding: 0;
} }
/* 合作限制:下拉项内边距(teleport 到 body,用 :global 生效) */
:global(.coop-select-dropdown .el-select-dropdown__item) {
padding: 0 20px !important;
}
.reslib-page { .reslib-page {
width: 1600px; width: 1600px;
......
...@@ -45,9 +45,9 @@ ...@@ -45,9 +45,9 @@
<AnalysisBox title="相关实体" :showAllBtn="true"> <AnalysisBox title="相关实体" :showAllBtn="true">
<div class="left-bottom-main"> <div class="left-bottom-main">
<div v-for="item in coopRelatedData" :key="item.id" class="main-box" @click="handleClickOnEntity(item)"> <div v-for="item in coopRelatedData" :key="item.id" class="main-box" @click="handleClickOnEntity(item)">
<img :src="item.img || defaultCom" alt="" /> <img :src="item.img || defaultCom" alt="" class="img-left-item" />
<div class="name">{{ item.ENTITYNAME }}</div> <div class="name">{{ item.ENTITYNAME }}</div>
<div class="type">{{ item.type }}</div> <div class="type">{{ item.position }}</div>
</div> </div>
</div> </div>
</AnalysisBox> </AnalysisBox>
...@@ -80,8 +80,15 @@ ...@@ -80,8 +80,15 @@
<span>{{ chineseNumbers[index] }}{{ item.TITLE }} </span> <span>{{ chineseNumbers[index] }}{{ item.TITLE }} </span>
<img src="./assets/打开按钮.png" alt=""> <img src="./assets/打开按钮.png" alt="">
</div> </div>
<div class="clause-item-content"> <!-- contentList:单条按原样式展示;多条则逐条展示并加 1.2.3. 前缀 -->
{{ item.CONTENT }} <div v-if="Array.isArray(item.contentList) && item.contentList.length > 1" class="clause-item-content-list">
<div v-for="(row, i) in item.contentList" :key="i" class="clause-item-content-row">
<span class="row-index">{{ i + 1 }}.</span>
<span class="row-text">{{ row.CONTENT }}</span>
</div>
</div>
<div v-else class="clause-item-content-row">
<span class="row-text">{{ item.contentList?.[0]?.CONTENT || "" }}</span>
</div> </div>
</div> </div>
</div> </div>
...@@ -163,19 +170,34 @@ const getcoopRelatedData = async () => { ...@@ -163,19 +170,34 @@ const getcoopRelatedData = async () => {
limitId: route.query.id limitId: route.query.id
}); });
if (res && res.code === 200) { if (res && res.code === 200) {
coopRelatedData.value = res.data || {}; // 展示图片:优先后端返回的 imageUrl,其次用已有 img 字段,再兜底默认图
coopRelatedData.value = (Array.isArray(res.data) ? res.data : []).map((row) => ({
...row,
img: row?.imageUrl || row?.img || row?.IMAGEURL || row?.image || ""
}));
} else { } else {
coopRelatedData.value = {}; coopRelatedData.value = [];
} }
} catch (error) { } catch (error) {
console.error("获取合作限制相关实体数据失败:", error); console.error("获取合作限制相关实体数据失败:", error);
coopRelatedData.value = {}; coopRelatedData.value = [];
} }
}; };
// 点击跳转关联实体详情 // 点击跳转关联实体详情
const handleClickOnEntity = (item) => { const handleClickOnEntity = (item) => {
if (!item.ENTITYID) return; const entityType = item?.ENTITYTYPE;
const path = `/companyPages/${item.ENTITYID}`; // ENTITYTYPE: 'O' 机构/公司;'P' 人物
if (entityType === "P") {
const personId = item?.PERSONID || item?.ENTITYID || item?.id;
if (!personId) return;
const url = `http://localhost:3000/characterPage?type=2&personId=${encodeURIComponent(personId)}`;
window.open(url, "_blank");
return;
}
// 默认按公司/机构跳转(含 ENTITYTYPE === 'O' 或字段缺失)
const companyId = item?.ENTITYID || item?.id;
if (!companyId) return;
const path = `/companyPages/${companyId}`;
const { href } = router.resolve({ path }); const { href } = router.resolve({ path });
window.open(href, "_blank"); window.open(href, "_blank");
}; };
...@@ -260,11 +282,21 @@ const filteredBackgroundList = computed(() => { ...@@ -260,11 +282,21 @@ const filteredBackgroundList = computed(() => {
const active2 = ref("涉华条款"); const active2 = ref("涉华条款");
const chineseNumbers = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十"]; const chineseNumbers = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十"];
const filteredClauseList = computed(() => { const filteredClauseList = computed(() => {
const list = Array.isArray(limitClauseData.value) ? limitClauseData.value : [];
if (active2.value === "全部条款") { if (active2.value === "全部条款") {
return limitClauseData.value; // 展示全部条款及全部段落
} else { return list.map((item) => ({
return limitClauseData.value.filter(item => item.ISCN === "Y"); ...item,
} contentList: Array.isArray(item?.contentList) ? item.contentList : []
}));
}
// 涉华条款:仅展示 contentList 中 ISCN=Y 的段落;若过滤后为空则不展示该条款
return list
.map((item) => {
const contentList = (Array.isArray(item?.contentList) ? item.contentList : []).filter((row) => row?.ISCN === "Y");
return { ...item, contentList };
})
.filter((item) => Array.isArray(item?.contentList) && item.contentList.length > 0);
}); });
const dataList = ref([ const dataList = ref([
...@@ -660,6 +692,12 @@ const dataList3 = ref([ ...@@ -660,6 +692,12 @@ const dataList3 = ref([
img { img {
width: 24px; width: 24px;
height: 24px; height: 24px;
border-radius: 50%;
display: inline-block;
object-fit: cover;
object-position: center;
} }
.name { .name {
...@@ -923,7 +961,12 @@ const dataList3 = ref([ ...@@ -923,7 +961,12 @@ const dataList3 = ref([
} }
} }
.clause-item-content { .clause-item-content-list {
width: 1022px;
}
/* 每条段落的展示:padding / 描边 / 间距保持与旧版单条 CONTENT 一致 */
.clause-item-content-row {
width: 1022px; width: 1022px;
padding: 12px 24px 12px 54px; padding: 12px 24px 12px 54px;
font-size: 16px; font-size: 16px;
...@@ -933,6 +976,17 @@ const dataList3 = ref([ ...@@ -933,6 +976,17 @@ const dataList3 = ref([
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
border-bottom: 1px solid rgb(234, 236, 238); border-bottom: 1px solid rgb(234, 236, 238);
white-space: pre-line; white-space: pre-line;
display: flex;
gap: 8px;
.row-index {
flex: 0 0 auto;
}
.row-text {
flex: 1;
min-width: 0;
}
} }
} }
} }
......
...@@ -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,15 +775,26 @@ const handleGetDecreeYearOrder = async () => { ...@@ -775,15 +775,26 @@ const handleGetDecreeYearOrder = async () => {
box5Params.loading = false box5Params.loading = false
}; };
// AI智能总结 // AI智能总结
const onChartInterpretation = async (text) => { const onChartInterpretation = async (text, param) => {
param.value = "正在生成..."
// 👇 新增:超时 + 终止请求(只加这一段)
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000); // 10秒超时
try {
const response = await fetch('/aiAnalysis/chart_interpretation', { const response = await fetch('/aiAnalysis/chart_interpretation', {
method: 'POST', method: 'POST',
headers: { headers: {
"X-API-Key": "aircasKEY19491001", "X-API-Key": "aircasKEY19491001",
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify({text}) // 把参数转为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 reader = response.body.getReader();
const decoder = new TextDecoder(); const decoder = new TextDecoder();
...@@ -806,7 +817,10 @@ const onChartInterpretation = async (text) => { ...@@ -806,7 +817,10 @@ const onChartInterpretation = async (text) => {
} }
} }
} }
return summarize param.value = summarize
} catch (err) {
param.value = "系统异常,生成失败";
}
} }
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) => {
......
...@@ -84,15 +84,12 @@ const router = useRouter(); ...@@ -84,15 +84,12 @@ const router = useRouter();
const route = useRoute(); const route = useRoute();
const isShowToolBox = computed(() => { const isShowToolBox = computed(() => {
const isShow = route.fullPath.includes('dataLibrary') ? false : true const isDataLibrary = route.fullPath.includes("dataLibrary");
return isShow const isWrittingAsstaint = route.path === "/writtingAsstaint";
}) return !isDataLibrary && !isWrittingAsstaint;
const isShowHeader = computed(() => {
const isShow = route.meta.isShowHeader
return isShow? true : false
}) })
const isShowHeader = computed(() => !!route.meta.isShowHeader);
const isShowAiBox = ref(false); const isShowAiBox = ref(false);
...@@ -808,4 +805,7 @@ body { ...@@ -808,4 +805,7 @@ body {
cursor: not-allowed; cursor: not-allowed;
pointer-events: none; pointer-events: none;
} }
</style> </style>
<template>
<div class="view-box">
<div class="icon-left">
<img src="@/assets/icons/box-footer-left-icon.png" alt="">
</div>
<div class="tips-content">{{ props.tips }}</div>
<div class="icon-right">
<img src="@/assets/icons/box-footer-right-icon.png" alt="">
</div>
</div>
</template>
<script setup lang="ts" name="AiTips">
const props = defineProps({
tips: {
type: String,
default: ''
}
});
</script>
<style scoped lang="scss">
.view-box {
width: 100%;
display: flex;
align-items: center;
padding: 7px 12px;
border: 1px solid rgba(231, 243, 255, 1);
border-radius: 4px;
background: rgba(246, 250, 255, 1);
.icon-left {
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.tips-content {
color: rgb(5, 95, 194);
font-size: 16px;
font-weight: 400;
line-height: 24px;
margin-left: 13px;
flex: 1;
}
.icon-right {
width: 24px;
height: 24px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
}
</style>
\ No newline at end of file
<template>
<AnalysisBox :title="props.title" :showAllBtn="false" height="auto">
<div class="box-main">
<div v-for="(item, index) in props.listData" :key="index" class="box-item">
<div class="item-tag">行政令</div>
<div class="item-right">
<div class="item-head">
<div class="item-name one-line-ellipsis">{{ item.name }}</div>
<div class="item-time">{{ item.time }}</div>
</div>
<div class="item-text one-line-ellipsis">{{ item.text }}</div>
</div>
</div>
</div>
</AnalysisBox>
</template>
<script setup lang="ts" name="RelatedEvent">
const props = defineProps({
listData: {
type: Array as any,
default: () => ([])
},
title: {
type: String,
default: ""
}
})
</script>
<style scoped lang="scss">
.box-main {
padding: 0 16px 16px;
.box-item {
border-top: 1px solid var(--bg-black-5);
padding: 6px;
display: flex;
font-size: 16px;
font-family: Source Han Sans CN;
.item-tag {
width: 80px;
height: 28px;
line-height: 28px;
border-radius: 14px;
text-align: center;
margin-right: 16px;
margin-top: 7px;
color: var(--color-yellow-100);
background-color: var(--color-yellow-10);
}
.item-right {
width: 20px;
flex: auto;
line-height: 30px;
.item-head {
display: flex;
.item-name {
width: 20px;
flex: auto;
font-weight: bold;
color: var(--text-primary-80-color);
}
.item-time {
margin-left: 100px;
flex: none;
color: var(--text-primary-65-color);
}
}
.item-text {
color: var(--text-primary-65-color);
}
}
}
.box-item:last-child {
border-bottom: 1px solid var(--bg-black-5);
}
}
</style>
\ No newline at end of file
<template>
<AnalysisBox :title="title" :showAllBtn="false" height="auto">
<el-empty v-if="!props.listData?.length" description="暂无数据" :image-size="200" />
<div v-else class="box-main">
<div class="data-list">
<div class="data-item" v-for="(item, index) in props.listData" :key="index">
<div class="item-head">
<div class="item-name">{{ item.title }}</div>
<div class="button-box">
<div class="button-icon">
<img src="../assets/icons/open.png" alt="" />
</div>
<div class="button-text">跳转原文</div>
</div>
</div>
<div class="item-down">
<div class="item-text" v-for="(text, num) in item.data" :key="num">{{ text }}</div>
</div>
</div>
</div>
<AiTips :tips="tips"></AiTips>
</div>
</AnalysisBox>
</template>
<script setup lang="ts" name="SurveyConclusion">
import AiTips from "@/views/marketAccessRestrictions/com/AiTips.vue";
const props = defineProps({
listData: {
type: Array as any,
default: () => ([])
},
title: {
type: String,
default: ""
},
tips: {
type: String,
default: ""
}
})
</script>
<style scoped lang="scss">
.box-main {
padding: 0 22px 20px;
.data-list {
margin-bottom: 16px;
border-top: 1px solid rgba(234, 236, 238, 1);
.data-item {
.item-head {
padding: 0 20px;
height: 48px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
background: rgba(247, 248, 249, 1);
display: flex;
align-items: center;
.item-name {
width: 20px;
flex: auto;
font-family: Source Han Sans CN;
font-size: 18px;
font-weight: bold;
line-height: 30px;
color: var(--text-primary-80-color);
}
.button-box {
display: flex;
align-items: center;
margin-left: 50px;
.button-icon {
width: 16px;
height: 16px;
font-size: 0;
margin-right: 4px;
img {
width: 100%;
height: 100%;
}
}
.button-text {
color: var(--color-primary-100);
font-family: Microsoft YaHei;
font-size: 12px;
font-weight: 400;
line-height: 12px;
}
}
}
.item-text {
letter-spacing: 1px;
padding: 12px 20px 12px 40px;
color: rgba(59, 65, 75, 1);
font-family: Source Han Sans CN;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
border-bottom: 1px solid rgba(234, 236, 238, 1);
}
}
}
}
</style>
\ No newline at end of file
<template>
<div class="view-box">
<el-empty v-if="!props.surveyList?.length" description="当前条件下暂无数据" :image-size="200" />
<div class="timeline-item" v-for="(item, index) in props.surveyList" :key="item.searchid" @click="onNavigateToDetail(item)">
<div class="timeline-date">
<div class="date-text">{{ item.searchdatezh.slice(0,4) }}</div>
<div class="date-text">{{ item.searchdatezh.slice(5) }}</div>
</div>
<div class="timeline-line-box">
<div class="timeline-icon">
<img v-if="item.sortimageurl" :src="item.sortimageurl" alt="" />
<div v-else class="default-dot"></div>
</div>
<div class="timeline-line" v-if="index !== props.surveyList.length - 1"></div>
</div>
<div class="timeline-content-card">
<div class="item-head">
<div :class="`item-tag tag-${item.sortcode}`">{{ item.sortcode }}</div>
<div class="item-name">{{ item.searchname }}</div>
<div class="item-state">
<span class="dot"></span> {{ item.casestatus }}
</div>
</div>
<div class="card-body">
{{ item.content }}
</div>
<div class="card-footer">
<div class="footer-left-tags">
<AreaTag v-for="(name, num) in item.searchArea" :key="num" :tagName="name"></AreaTag>
</div>
<div class="footer-right-flags">
<div class="flag-icon" v-for="(name, num) in item.countryImage" :key="num">
<img :src="name" alt="" />
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup name="SurveyHistory">
import router from "@/router";
const props = defineProps({
surveyList: {
type: Array,
default: () => ([]),
},
})
const onNavigateToDetail = item => {
window.sessionStorage.setItem("curTabName", item.searchname);
const curRoute = router.resolve({
path: "/marketSingleCaseLayout/overview",
query: { id: item.sortcode, searchId: item.searchid }
});
window.open(curRoute.href, "_blank");
};
</script>
<style scoped lang="scss">
.view-box {
min-height: 600px;
width: 100%;
padding: 18px 27px 0 24px;
border-top: 1px solid rgba(234, 236, 238, 1);
border-bottom: 1px solid rgba(234, 236, 238, 1);
}
.timeline-item {
display: flex;
cursor: pointer;
.timeline-date {
width: 80px;
text-align: right;
margin-right: 16px;
.date-text {
height: 24px;
font-size: 16px;
font-weight: 700;
color: var(--color-main-active);
line-height: 24px;
letter-spacing: 1px;
}
}
.timeline-line-box {
width: 40px;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
.timeline-icon {
width: 32px;
height: 32px;
z-index: 2;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 0;
img {
width: 100%;
height: 100%;
}
.default-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: orange;
}
}
.timeline-line {
width: 2px;
flex: 1;
background: #eaeeef;
margin: 4px 0;
}
}
.timeline-content-card {
width: 20px;
flex: auto;
padding: 2px 16px 0;
margin-bottom: 30px;
&:hover .item-head .item-name {
color: var(--color-main-active);
text-decoration: underline;
}
.item-head {
display: flex;
align-items: center;
margin-bottom: 10px;
.item-tag {
width: 48px;
height: 24px;
line-height: 24px;
text-align: center;
border-radius: 4px;
font-weight: bold;
font-size: 16px;
margin-right: 12px;
}
.tag-337 {
border: 1px solid #91caff;
background: #e6f4ff;
color: #055fc2;
}
.tag-232 {
border: 1px solid #b37feb;
background: #f9f0ff;
color: #722ed1;
}
.tag-301 {
border: 1px solid #ffd591;
background: #fff7e6;
color: #fa8c16;
}
.item-name {
font-size: 18px;
line-height: 18px;
font-weight: bold;
color: #3b414b;
width: 20px;
flex: auto;
}
.item-state {
font-size: 16px;
color: #84888e;
color: var(--color-main-active);
margin-left: 100px;
}
}
.card-body {
font-size: 16px;
color: #5f656c;
line-height: 30px;
margin-bottom: 8px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
}
.card-footer {
display: flex;
gap: 8px;
align-items: center;
.footer-left-tags {
display: flex;
gap: 8px;
.area-tag {
padding: 2px 12px;
background: #e6f7ff;
border: 1px solid #91d5ff;
color: #1890ff;
border-radius: 4px;
font-size: 14px;
}
}
.footer-right-flags {
display: flex;
gap: 4px;
.flag-icon {
width: 12px;
height: 24px;
img {
border-radius: 50%;
border: 1px solid #eee;
width: 24px;
height: 100%;
object-fit: cover;
}
}
}
}
}
}
</style>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论