提交 43550109 authored 作者: 张伊明's avatar 张伊明

合并分支 'zz-dev' 到 'pre'

feat:调查项目及国会听证会的详情页面及列表的样式与功能的开发 查看合并请求 !294
流水线 #279 已通过 于阶段
in 1 分 25 秒
...@@ -91,7 +91,7 @@ export function getHylyList() { ...@@ -91,7 +91,7 @@ export function getHylyList() {
/** /**
* 智库概览/智库动态-智库报告、调查项目 * 智库概览/智库动态-智库报告
* GET /api/thinkTankOverview/report * GET /api/thinkTankOverview/report
* 常用 query:pageNum, pageSize, sortFun, domainIds, startDate, endDate, category(调查项目), thinkTankId(详情页), keyword(动态搜索) * 常用 query:pageNum, pageSize, sortFun, domainIds, startDate, endDate, category(调查项目), thinkTankId(详情页), keyword(动态搜索)
*/ */
...@@ -103,6 +103,68 @@ export function getThinkTankReport(params) { ...@@ -103,6 +103,68 @@ export function getThinkTankReport(params) {
}) })
} }
//智库概览调查项目
export function getThinkTankProjects(params) {
return request({
method: 'GET',
url: `/api/think-tank/projects`,
params
})
}
//智库概览页国会听证会
export function getThinkTankTestimonies() {
return request({
method: 'GET',
url: `/api/think-tank/testimonies`,
})
}
//智库调查项目详情主页
export function getThinkTankProjectsInfo(params) {
return request({
method: 'GET',
url: `/api/think-tank/projects/${params.id}`,
})
}
//智库国会听证会详情主页
export function getThinkTankHearingInfo(params) {
return request({
method: 'GET',
url: `/api/think-tank/testimonies/${params.id}`,
})
}
// 智库详情-调查项目(按智库 id)
export function getThinkTankProjectsByThinkTankId(params) {
return request({
method: 'GET',
url: `/api/think-tank/${params.thinkTankId}/projects`,
params: {
pageNum: params.pageNum,
pageSize: params.pageSize,
}
})
}
// 智库动态-国会听证会(按智库 id)
export function getThinkTankTestimoniesByThinkTankId(params) {
return request({
method: 'GET',
url: `/api/think-tank/${params.thinkTankId}/testimonies`,
params: {
pageNum: params.pageNum,
pageSize: params.pageSize,
}
})
}
//智库调查项目详情作者
export function getThinkTankProjectsAuthors(params) {
return request({
method: 'GET',
url: `/api/think-tank/projects/${params.id}/team`,
})
}
// 智库概览:政策建议(资源库-政策建议) // 智库概览:政策建议(资源库-政策建议)
export function getThinkTankOverviewPolicy(params) { export function getThinkTankOverviewPolicy(params) {
return request({ return request({
...@@ -378,6 +440,15 @@ export const getThinkTankReportRelated = (params) => { ...@@ -378,6 +440,15 @@ export const getThinkTankReportRelated = (params) => {
} }
); );
} }
//调查项目:获取项目报告
export const getThinkTankProjectRelated = (params) => {
return request(
{
method: 'GET',
url: `/api/think-tank/projects/${params}/reports`,
}
);
}
//获取报告原文 //获取报告原文
export const getThinkTankReportcontentUrl = (params) => { export const getThinkTankReportcontentUrl = (params) => {
...@@ -424,7 +495,21 @@ export function getThinkTankReportViewpoint(params) { ...@@ -424,7 +495,21 @@ export function getThinkTankReportViewpoint(params) {
} }
}) })
} }
// 获取报告核心论点(支持关键字搜索)
export function getThinkTankHearingViewpoint(params) {
const { testimonyId, pageSize, keyword = '',pageNum } = params
return request({
method: 'GET',
url: `/api/think-tank/testimonies/qa`,
params: {
pageSize,
keyword,
pageNum,
testimonyId
}
})
}
//获取涉及科技领域 //获取涉及科技领域
export function getThinkTankReportIndustry(params) { export function getThinkTankReportIndustry(params) {
return request({ return request({
...@@ -440,6 +525,20 @@ export function getThinkTankReportIndustryCloud(params) { ...@@ -440,6 +525,20 @@ export function getThinkTankReportIndustryCloud(params) {
url: `/api/thinkTankReport/keyword/${params.id}`, url: `/api/thinkTankReport/keyword/${params.id}`,
}) })
} }
//获取调查项目词云
export function getThinkTankProjectCloud(params) {
return request({
method: 'GET',
url: `/api/think-tank/projects/${params.id}/word-cloud`,
})
}
//获取国会听证会词云
export function getThinkTankHearingCloud(params) {
return request({
method: 'GET',
url: `/api/think-tank/testimonies/${params.id}/wordcloud`,
})
}
//获取政策建议落实情况 //获取政策建议落实情况
export function getThinkTankReportPolicy(params) { export function getThinkTankReportPolicy(params) {
......
...@@ -41,17 +41,31 @@ const thinktankRoutes = [ ...@@ -41,17 +41,31 @@ const thinktankRoutes = [
path: "/thinkTank/reportOriginal/:id", path: "/thinkTank/reportOriginal/:id",
name: "ReportOriginal", name: "ReportOriginal",
component: ReportOriginal, component: ReportOriginal,
meta: {
title: "报告原文",
dynamicTitle: true,
titleStorageKey: "reportOriginalTabName"
}
}, },
{ {
path: "/thinkTank/SurveyProjectView/:id", path: "/thinkTank/SurveyProjectView/:id",
name: "SurveyProjectView", name: "SurveyProjectView",
component: SurveyProjectView, component: SurveyProjectView,
meta: {
title: "调查项目",
dynamicTitle: true,
titleStorageKey: "surveyProjectTabName"
}
}, },
{ {
path: "/thinkTank/CongressHearingView/:id", path: "/thinkTank/CongressHearingView/:id",
name: "CongressHearingView", name: "CongressHearingView",
component: CongressHearingView, component: CongressHearingView,
meta: {
title: "国会听证会",
dynamicTitle: true,
titleStorageKey: "congressHearingTabName"
}
}, },
{ {
path: "/thinkTank/allThinkTank", path: "/thinkTank/allThinkTank",
......
...@@ -4,18 +4,18 @@ ...@@ -4,18 +4,18 @@
<div class="header"> <div class="header">
<div class="header-top"> <div class="header-top">
<div class="header-top-left"> <div class="header-top-left">
<img src="../assets/images/box1-logo.png" alt="" /> <img :src=thinkInfo.coverImgUrl alt="" />
<div> <div>
<div class="title">{{ thinkInfo.name }}</div> <div class="title">{{ thinkInfo.titleZh }}</div>
<div class="en-title"> <div class="en-title">
{{ thinkInfo.ename }}.{{ thinkInfo.times }} {{ thinkInfo.testimonyDate }}·{{ "国会听证会" }}·{{ thinkInfo.committeeZh }}
</div> </div>
<div class="tag-box"> <div class="tag-box">
<!-- <div class="tag-box" v-for="value,index in thinkInfo.tags" :key="index"> <!-- <div class="tag-box" v-for="value,index in thinkInfo.tags" :key="index">
<div class="tag">{{ value.industryName }}</div> <div class="tag">{{ value.industryName }}</div>
</div> --> </div> -->
<AreaTag v-for="(value, index) in thinkInfo.tags" :key="index" :tagName="value.industryName"></AreaTag> <AreaTag v-for="(value, index) in thinkInfo.tags" :key="index" :tagName="value.domainName"></AreaTag>
</div> </div>
</div> </div>
</div> </div>
...@@ -155,7 +155,7 @@ ...@@ -155,7 +155,7 @@
<div class="box3-main"> <div class="box3-main">
<AiSummary> <AiSummary>
<template #summary-content> <template #summary-content>
{{ box1Data }} {{ thinkInfo.descriptionZh }}
</template> </template>
</AiSummary> </AiSummary>
...@@ -180,10 +180,9 @@ ...@@ -180,10 +180,9 @@
{{ index + 1 }} {{ index + 1 }}
</div> </div>
<div class="center"> <div class="center">
<div class="title" v-html="highlightOpinionText(item.titleZh)"></div> <div class="title" v-html="highlightOpinionText(item.questionZh)"></div>
<div> <div>
<img src="./images/image-open.png" alt="" class="center-image" <img src="./images/image-open.png" alt="" class="center-image" />
@click="handleOpenReportOriginal(item)" />
</div> </div>
<div> <div>
<img v-if="!isOpinionExpanded(item, index)" src="./images/image-down.png" alt="" <img v-if="!isOpinionExpanded(item, index)" src="./images/image-down.png" alt=""
...@@ -194,7 +193,7 @@ ...@@ -194,7 +193,7 @@
</div> </div>
</div> </div>
<div v-if="isOpinionExpanded(item, index)" class="desc" <div v-if="isOpinionExpanded(item, index)" class="desc"
v-html="highlightOpinionText(item.contentZh)"> v-html="highlightOpinionText(item.answerZh)">
</div> </div>
<!-- <div class="right"> --> <!-- <div class="right"> -->
<!-- <div class="tag" v-for="(val, idx) in item.hylyList" :key="idx"> <!-- <div class="tag" v-for="(val, idx) in item.hylyList" :key="idx">
...@@ -239,7 +238,10 @@ import { ...@@ -239,7 +238,10 @@ import {
getThinkTankReportContent, getThinkTankReportContent,
getThinkTankReportIndustry, getThinkTankReportIndustry,
getThinkTankReportIndustryCloud, getThinkTankReportIndustryCloud,
getThinkTankReportViewpoint getThinkTankReportViewpoint,
getThinkTankHearingInfo,
getThinkTankHearingCloud,
getThinkTankHearingViewpoint
} from "@/api/thinkTank/overview"; } from "@/api/thinkTank/overview";
import { getChartAnalysis } from "@/api/aiAnalysis/index"; import { getChartAnalysis } from "@/api/aiAnalysis/index";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
...@@ -270,15 +272,39 @@ const props = defineProps({ ...@@ -270,15 +272,39 @@ const props = defineProps({
} }
}); });
const thinkInfo = ref({ const thinkInfo = ref({
name: "探讨中国开发和管理的跨大陆电网的安全影响",
ename: "调查项目",
tags: [{ industryName: "深海" }, { industryName: "人工智能" }],
thinkTankName: "兰德科技智库",
thinkTankLogoUrl: "http://8.140.26.4:10010/kjb-files/images/org/land.webp",
times: "2024-05-28"
}) })
const applyCongressHearingDocumentTitle = (title) => {
const text = String(title || "").trim();
if (!text) return;
window.sessionStorage.setItem("congressHearingTabName", text);
document.title = text;
};
const handleGetThinkTankHearingInfo = async () => {
try {
const id = router.currentRoute._value.params.id;
if (!id) return;
const res = await getThinkTankHearingInfo({ id });
if (res?.code === 200 && res?.data) {
const info = res.data;
thinkInfo.value = {
...info,
// 保持模板现有字段:titleZh / testimonyDate / committeeZh / coverImgUrl ...
tags: Array.isArray(info.domains) ? info.domains : thinkInfo.value.tags,
};
console.log("thinkInfo", thinkInfo.value);
applyCongressHearingDocumentTitle(info.titleZh || info.title || "");
if (info.summaryZh || info.summary) {
box1Data.value = info.summaryZh || info.summary;
}
projectBackground.value = info.backgroundZh || info.background || projectBackground.value;
}
} catch (error) {
console.error("获取调查项目详情error", error);
}
};
const REPORT_ANALYSIS_TIP_BOX5 = const REPORT_ANALYSIS_TIP_BOX5 =
"智库报告关键词云,数据来源:美国兰德公司官网"; "国会听证会关键词云,数据来源:美国兰德公司官网";
// 刷新后默认展示「报告关键词云」AI 总结 // 刷新后默认展示「报告关键词云」AI 总结
const isShowAiContentBox5 = ref(true); const isShowAiContentBox5 = ref(true);
const aiContentBox5 = ref(""); const aiContentBox5 = ref("");
...@@ -464,19 +490,19 @@ const hasBox5ChartData = computed(() => Array.isArray(box5Data.value) && box5Dat ...@@ -464,19 +490,19 @@ const hasBox5ChartData = computed(() => Array.isArray(box5Data.value) && box5Dat
/** 词云子组件不 watch 数据,每次接口成功有数据时递增 key,强制重新挂载以触发 onMounted */ /** 词云子组件不 watch 数据,每次接口成功有数据时递增 key,强制重新挂载以触发 onMounted */
const box5WordCloudKey = ref(0); const box5WordCloudKey = ref(0);
//获取科技领域词云 //获取国会听证会词云
const handleGetThinkTankReportIndustryCloud = async () => { const handleGetThinkTankReportIndustryCloud = async () => {
try { try {
const params = { const params = {
id: router.currentRoute._value.params.id id: router.currentRoute._value.params.id
// industryId: activeArea.value // industryId: activeArea.value
}; };
const res = await getThinkTankReportIndustryCloud(params); const res = await getThinkTankHearingCloud(params);
console.log("科技领域词云", res); console.log("科技领域词云", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const data = (res.data || []).map(item => ({ const data = (res.data || []).map(item => ({
name: item.clause, name: item.name,
value: item.count value: item.value
})); }));
// 该接口数据用于「报告关键词云」 // 该接口数据用于「报告关键词云」
box5Data.value = data; box5Data.value = data;
...@@ -566,18 +592,17 @@ const handleCurrentChange = page => { ...@@ -566,18 +592,17 @@ const handleCurrentChange = page => {
handleGetThinkTankReportViewpoint(); handleGetThinkTankReportViewpoint();
}; };
// 获取报告核心论点(支持搜索) // 获取报告听证会(支持搜索)
const handleGetThinkTankReportViewpoint = async () => { const handleGetThinkTankReportViewpoint = async () => {
try { try {
const params = { const params = {
reportId: router.currentRoute._value.params.id, testimonyId: router.currentRoute._value.params.id,
currentPage: currentPage.value - 1, pageNum: currentPage.value,
pageSize: pageSize.value, pageSize: pageSize.value,
keyword: (searchOpinions.value || "").trim(), keyword: (searchOpinions.value || "").trim(),
orgIds: ""
}; };
const res = await getThinkTankReportViewpoint(params); const res = await getThinkTankHearingViewpoint(params);
console.log("核心论点", res.data); console.log("听证会内容", res.data);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const nextOpinions = res.data.content || []; const nextOpinions = res.data.content || [];
majorOpinions.value = nextOpinions; majorOpinions.value = nextOpinions;
...@@ -672,6 +697,7 @@ onMounted(() => { ...@@ -672,6 +697,7 @@ onMounted(() => {
handleGetThinkTankReportIndustry(); handleGetThinkTankReportIndustry();
handleGetThinkTankReportIndustryCloud(); handleGetThinkTankReportIndustryCloud();
handleGetThinkTankHearingInfo();
}); });
</script> </script>
...@@ -1484,6 +1510,7 @@ onMounted(() => { ...@@ -1484,6 +1510,7 @@ onMounted(() => {
height: 24px; height: 24px;
margin-top: 12px; margin-top: 12px;
margin-left: 18px; margin-left: 18px;
cursor: pointer;
} }
...@@ -1533,8 +1560,8 @@ onMounted(() => { ...@@ -1533,8 +1560,8 @@ onMounted(() => {
padding-top: 22px; padding-top: 22px;
padding-bottom: 23px; padding-bottom: 23px;
padding-left: 56px; // 24(left) + 13(center margin) + 一点间距 padding-left: 56px;
padding-right: 56px;
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
font-family: "Source Han Sans CN"; font-family: "Source Han Sans CN";
font-weight: 400; font-weight: 400;
......
...@@ -4,22 +4,22 @@ ...@@ -4,22 +4,22 @@
<div class="header"> <div class="header">
<div class="header-top"> <div class="header-top">
<div class="header-top-left"> <div class="header-top-left">
<img src="../assets/images/box1-logo.png" alt="" /> <img :src=thinkInfo.projectCoverImgUrl alt="" />
<div> <div>
<div class="title">{{ thinkInfo.name }}</div> <div class="title">{{ thinkInfo.name }}</div>
<div class="en-title"> <div class="en-title">
{{ thinkInfo.ename }} {{ "调查项目" }}
</div> </div>
</div> </div>
</div> </div>
<div class="header-top-right"> <div class="header-top-right">
<div class="image-name-box"> <div class="image-name-box">
<div class="image"><img src="../assets/images/box1-logo.png" alt="" /></div> <div class="image"><img :src="thinkInfo.orgLogo" alt="" /></div>
<div class="name">{{ thinkInfo.thinkTankName }}</div> <div class="name">{{ thinkInfo.orgName }}</div>
</div> </div>
<div class="tag-box"> <div class="tag-box">
<AreaTag v-for="(value, index) in thinkInfo.tags" :key="index" :tagName="value.industryName"></AreaTag> <AreaTag v-for="(value, index) in thinkInfo.tags" :key="index" :tagName="value"></AreaTag>
</div> </div>
</div> </div>
</div> </div>
...@@ -74,16 +74,16 @@ ...@@ -74,16 +74,16 @@
<div class="box2-main"> <div class="box2-main">
<div class="box2-item" v-for="(report, idx) in reportList" :key="idx"> <div class="box2-item" v-for="(report, idx) in reportList" :key="idx">
<div class="box2-item-content"> <div class="box2-item-content">
<div class="left"><img :src="report.image" alt="" /></div> <div class="left"><img :src="report.imgUrl" alt="" /></div>
<div class="right-content"> <div class="right-content">
<div class="report-title">{{ report.name }}</div> <div class="report-title">{{ report.nameZh }}</div>
<div class="report-footer"> <div class="report-footer">
<div class="report-time">{{ report.postDate }}</div> <div class="report-time">{{ formatDate(report.reportDate) }}</div>
<div class="report-footer-right"> <div class="report-footer-right">
<div class="footer-image"> <div class="footer-image">
<img :src="report.thinktankLogo" alt="" /> <img :src="report.reportOrgLogo" alt="" />
</div> </div>
<div class="think-name">{{ report.thinktankName }}</div> <div class="think-name">{{ report.reportOrg }}</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -93,6 +93,7 @@ ...@@ -93,6 +93,7 @@
</AnalysisBox> </AnalysisBox>
</div> </div>
</div> </div>
<div class="right"> <div class="right">
<div class="box3"> <div class="box3">
...@@ -110,10 +111,8 @@ ...@@ -110,10 +111,8 @@
<div class="box4"> <div class="box4">
<AnalysisBox title="项目背景" :showAllBtn="true"> <AnalysisBox title="项目背景" :showAllBtn="true">
<div class="box4-main"> <div class="box4-main">
<div class="text"> <div class="text" v-html="formatParagraph(projectBackground)">
{{
"可再生能源和清洁能源创新为摆脱对化石燃料的依赖提供了机会。然而,可再生能源的间歇性也对电网的实时供需平衡构成挑战。作为解决方案,中国提出开发全球能源互联(GEI)倡议,通过超高压输电线路和智能技术,直接将可再生能源生产者与全球消费者连接起来。北京认识到GEI的潜力,正站在GEI发展的前沿,领导关键推动技术的研究,输出中国技术和标准,支持海外发电和输电基础设施的发展。中国还在联合国和海湾合作委员会等国际组织推动GEI"
}}
</div> </div>
</div> </div>
</AnalysisBox> </AnalysisBox>
...@@ -123,58 +122,15 @@ ...@@ -123,58 +122,15 @@
<div class="box5-main"> <div class="box5-main">
<div class="box5-main-item-box"> <div class="box5-main-item-box">
<div class="item"> <div class="item" v-for="(author, idx) in reportAuthors" :key="idx">
<div class="item-left">
<img src="../assets/images/rand-image.png" alt="" />
</div>
<div class="item-right">
<div class="item-name">{{ "纳迪娅·阿尔马萨尔基" }} </div>
<div class="item-position">{{ "副秘书" }}</div>
</div>
</div>
<div class="item">
<div class="item-left">
<img src="../assets/images/rand-image.png" alt="" />
</div>
<div class="item-right">
<div class="item-name">{{ "纳迪娅·阿尔马萨尔基" }} </div>
<div class="item-position">{{ "副秘书" }}</div>
</div>
</div>
<div class="item">
<div class="item-left"> <div class="item-left">
<img src="../assets/images/rand-image.png" alt="" /> <img :src="author.avatarUrl ? author.avatarUrl : DefaultIcon1" alt=""
@error="() => { if (author.avatarUrl) author.avatarUrl = null; }"
@click="handleClickReportAuthor(author)" />
</div> </div>
<div class="item-right"> <div class="item-right">
<div class="item-name">{{ "纳迪娅·阿尔马萨尔基" }} </div> <div class="item-name">{{ author.name }} </div>
<div class="item-position">{{ "副秘书" }}</div>
</div>
</div>
<div class="item">
<div class="item-left">
<img src="../assets/images/rand-image.png" alt="" />
</div>
<div class="item-right">
<div class="item-name">{{ "纳迪娅·阿尔马萨尔基" }} </div>
<div class="item-position">{{ "副秘书" }}</div>
</div>
</div>
<div class="item">
<div class="item-left">
<img src="../assets/images/rand-image.png" alt="" />
</div>
<div class="item-right">
<div class="item-name">{{ "纳迪娅·阿尔马萨尔基" }} </div>
<div class="item-position">{{ "副秘书" }}</div>
</div>
</div>
<div class="item">
<div class="item-left">
<img src="../assets/images/rand-image.png" alt="" />
</div>
<div class="item-right">
<div class="item-name">{{ "纳迪娅·阿尔马萨尔基" }} </div>
<div class="item-position">{{ "副秘书" }}</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -199,7 +155,12 @@ import { ...@@ -199,7 +155,12 @@ import {
getThinkTankReportContent, getThinkTankReportContent,
getThinkTankReportIndustry, getThinkTankReportIndustry,
getThinkTankReportIndustryCloud, getThinkTankReportIndustryCloud,
getThinkTankReportViewpoint getThinkTankReportViewpoint,
getThinkTankProjectsInfo,
getThinkTankProjectCloud,
getThinkTankProjectsAuthors,
getThinkTankReportRelated,
getThinkTankProjectRelated
} from "@/api/thinkTank/overview"; } from "@/api/thinkTank/overview";
import { getChartAnalysis } from "@/api/aiAnalysis/index"; import { getChartAnalysis } from "@/api/aiAnalysis/index";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
...@@ -209,22 +170,21 @@ import { getPersonSummaryInfo } from "@/api/common/index"; ...@@ -209,22 +170,21 @@ import { getPersonSummaryInfo } from "@/api/common/index";
import AiButton from "@/components/base/Ai/AiButton/index.vue"; import AiButton from "@/components/base/Ai/AiButton/index.vue";
import AiPane from "@/components/base/Ai/AiPane/index.vue"; import AiPane from "@/components/base/Ai/AiPane/index.vue";
import TipTab from "@/views/thinkTank/TipTab/index.vue"; import TipTab from "@/views/thinkTank/TipTab/index.vue";
const router = useRouter(); const router = useRouter();
const thinkInfo = ref({ const thinkInfo = ref({
name: "探讨中国开发和管理的跨大陆电网的安全影响",
ename: "调查项目",
tags: [{ industryName: "深海" }, { industryName: "人工智能" }],
thinkTankName: "兰德科技智库"
}) })
const reportAuthors = ref([]);
const projectBackground = ref("");
const applySurveyProjectDocumentTitle = (title) => {
const text = String(title || "").trim();
if (!text) return;
window.sessionStorage.setItem("surveyProjectTabName", text);
document.title = text;
};
const REPORT_ANALYSIS_TIP_BOX5 = const REPORT_ANALYSIS_TIP_BOX5 =
"智库报告关键词云,数据来源:美国兰德公司官网"; "调查项目关键词云,数据来源:美国兰德公司官网";
// 刷新后默认展示「报告关键词云」AI 总结 // 刷新后默认展示「报告关键词云」AI 总结
const isShowAiContentBox5 = ref(true); const isShowAiContentBox5 = ref(true);
const aiContentBox5 = ref(""); const aiContentBox5 = ref("");
...@@ -235,9 +195,14 @@ const handleSwitchAiContentShowBox5 = (val) => { ...@@ -235,9 +195,14 @@ const handleSwitchAiContentShowBox5 = (val) => {
fetchBox5ChartInterpretation(); fetchBox5ChartInterpretation();
} }
}; };
const searchOpinions = ref(''); const searchOpinions = ref('');
const formatParagraph = (text) => {
if (!text) return ''
// 把换行分割成数组 → 每一段都包 <p>
return text.split('\n').map(item => {
return `<p>${item}</p>`
}).join('')
}
const escapeHtml = (text) => { const escapeHtml = (text) => {
return String(text ?? "") return String(text ?? "")
.replace(/&/g, "&amp;") .replace(/&/g, "&amp;")
...@@ -246,36 +211,102 @@ const escapeHtml = (text) => { ...@@ -246,36 +211,102 @@ const escapeHtml = (text) => {
.replace(/"/g, "&quot;") .replace(/"/g, "&quot;")
.replace(/'/g, "&#39;"); .replace(/'/g, "&#39;");
}; };
const escapeRegExp = (text) => { const escapeRegExp = (text) => {
return String(text ?? "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); return String(text ?? "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}; };
/** 可同时展开多条;用 id 区分项,避免翻页后索引与展开状态错位 */ /** 可同时展开多条;用 id 区分项,避免翻页后索引与展开状态错位 */
const expandedOpinionKeys = ref(new Set()); const expandedOpinionKeys = ref(new Set());
const getOpinionExpandKey = (item, index) => { const getOpinionExpandKey = (item, index) => {
if (item != null && item.id != null && item.id !== "") { if (item != null && item.id != null && item.id !== "") {
return String(item.id); return String(item.id);
} }
return `idx-${index}`; return `idx-${index}`;
}; };
const reportList = ref({});
const formatDate = (dateStr) => {
if (!dateStr) return "";
const date = new Date(dateStr)
const y = date.getFullYear()
const m = date.getMonth() + 1
const d = date.getDate()
return `${y}${m}${d}日`
};
// 获取项目报告信息
const handleGetThinkTankReport = async () => {
try {
const res = await getThinkTankProjectRelated(router.currentRoute._value.params.id);
console.log("报告全局信息", res);
if (res.code === 200 && res.data) {
reportList.value = res.data;
}
} catch (error) {
console.error("获取相关报告error", error);
}
};
const handleClickReportAuthor = async (author) => {
const personId = author?.personId;
if (!personId) return;
const params = { personId };
const res = await getPersonSummaryInfo(params);
if (res.code !== 200 || !res.data) return;
window.sessionStorage.setItem("curTabName", author?.name || "");
const route = router.resolve({
path: "/characterPage",
query: {
personId
}
});
window.open(route.href, "_blank");
};
// 内容摘要 // 内容摘要
const box1Data = const box1Data =
ref(`包括经济竞争在内的美中竞争自2017年以来一直在定义美国外交政策。这两个经济体是世界上第一和第二大国家经济体,并且深深交织在一起。改变关系,无论多么必要,可能是昂贵的。因此,美国面临着一项挑战,确保其经济在耦合的战略竞争条件下满足国家的需求。 ref(`包括经济竞争在内的美中竞争自2017年以来一直在定义美国外交政策。这两个经济体是世界上第一和第二大国家经济体,并且深深交织在一起。改变关系,无论多么必要,可能是昂贵的。因此,美国面临着一项挑战,确保其经济在耦合的战略竞争条件下满足国家的需求。
为了应对这一挑战,兰德大学的研究人员对美中竞争进行了经济和制度分析,进行了参与式的远见练习,以了解确保美国经济健康的长期路径,并创建了两个经济竞争游戏,探索多个国家在相互交流的同时确保经济健康的动态...`); 为了应对这一挑战,兰德大学的研究人员对美中竞争进行了经济和制度分析,进行了参与式的远见练习,以了解确保美国经济健康的长期路径,并创建了两个经济竞争游戏,探索多个国家在相互交流的同时确保经济健康的动态...`);
// 获取调查项目详情(header/摘要/背景)
const handleGetThinkTankProjectsInfo = async () => {
try {
const id = router.currentRoute._value.params.id;
if (!id) return;
const res = await getThinkTankProjectsInfo({ id });
if (res?.code === 200 && res?.data) {
const info = res.data;
thinkInfo.value = {
...info,
name: info.projectNameZh,
ename: info.projectName,
tags: Array.isArray(info.domains) ? info.domains : thinkInfo.value.tags,
};
console.log("thinkInfo", thinkInfo.value);
applySurveyProjectDocumentTitle(info.projectNameZh || info.projectName || "");
if (info.summaryZh || info.summary) {
box1Data.value = info.summaryZh || info.summary;
}
projectBackground.value = info.backgroundZh || info.background || projectBackground.value;
}
} catch (error) {
console.error("获取调查项目详情error", error);
}
};
const handleGetThinkTankProjectsAuthor = async () => {
try {
const id = router.currentRoute._value.params.id;
if (!id) return;
const res = await getThinkTankProjectsAuthors({ id });
if (res?.code === 200 && res?.data) {
reportAuthors.value = res.data;
console.log("reportAuthors.value", reportAuthors.value);
}
} catch (error) {
console.error("获取调查项目作者error", error);
}
};
//获取内容摘要 //获取内容摘要
const handleGetThinkTankReportAbstract = async () => { const handleGetThinkTankReportAbstract = async () => {
try { try {
...@@ -343,12 +374,12 @@ const handleGetThinkTankReportIndustryCloud = async () => { ...@@ -343,12 +374,12 @@ const handleGetThinkTankReportIndustryCloud = async () => {
id: router.currentRoute._value.params.id id: router.currentRoute._value.params.id
// industryId: activeArea.value // industryId: activeArea.value
}; };
const res = await getThinkTankReportIndustryCloud(params); const res = await getThinkTankProjectCloud(params);
console.log("科技领域词云", res); console.log("科技领域词云", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const data = (res.data || []).map(item => ({ const data = (res.data || []).map(item => ({
name: item.clause, name: item.name,
value: item.count value: item.value
})); }));
// 该接口数据用于「报告关键词云」 // 该接口数据用于「报告关键词云」
box5Data.value = data; box5Data.value = data;
...@@ -433,10 +464,7 @@ const switchTab = name => { ...@@ -433,10 +464,7 @@ const switchTab = name => {
const currentPage = ref(1); const currentPage = ref(1);
const pageSize = ref(10); const pageSize = ref(10);
const total = ref(0); const total = ref(0);
const handleCurrentChange = page => {
currentPage.value = page;
handleGetThinkTankReportViewpoint();
};
// 获取报告核心论点(支持搜索) // 获取报告核心论点(支持搜索)
const handleGetThinkTankReportViewpoint = async () => { const handleGetThinkTankReportViewpoint = async () => {
...@@ -469,13 +497,7 @@ const handleGetThinkTankReportViewpoint = async () => { ...@@ -469,13 +497,7 @@ const handleGetThinkTankReportViewpoint = async () => {
// 获取图表分析内容 // 获取图表分析内容
const box3AnalysisContent = ref(""); const box3AnalysisContent = ref("");
const handleGetBox3AnalysisContent = async textJson => {
const params = {
text: textJson
};
const res = await getChartAnalysis(params);
console.log("图表解析内容", res);
};
const getInterpretationTextFromChartResponse = (res) => { const getInterpretationTextFromChartResponse = (res) => {
const list = res?.data; const list = res?.data;
const first = Array.isArray(list) ? list[0] : null; const first = Array.isArray(list) ? list[0] : null;
...@@ -539,11 +561,14 @@ const fetchBox5ChartInterpretation = async () => { ...@@ -539,11 +561,14 @@ const fetchBox5ChartInterpretation = async () => {
}; };
onMounted(() => { onMounted(() => {
handleGetThinkTankProjectsInfo();
handleGetThinkTankReportAbstract(); handleGetThinkTankReportAbstract();
handleGetThinkTankReportViewpoint(); handleGetThinkTankReportViewpoint();
handleGetThinkTankReportIndustry(); handleGetThinkTankReportIndustry();
handleGetThinkTankReportIndustryCloud(); handleGetThinkTankReportIndustryCloud();
handleGetThinkTankProjectsAuthor();
handleGetThinkTankReport();
}); });
</script> </script>
...@@ -718,6 +743,11 @@ onMounted(() => { ...@@ -718,6 +743,11 @@ onMounted(() => {
overflow: hidden; overflow: hidden;
} }
.box5-el-empty {
margin: 0 auto;
height: 100%;
}
.box1-footer { .box1-footer {
display: flex; display: flex;
...@@ -1004,6 +1034,9 @@ onMounted(() => { ...@@ -1004,6 +1034,9 @@ onMounted(() => {
letter-spacing: 0px; letter-spacing: 0px;
text-align: left; text-align: left;
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
white-space: pre-line;
text-indent: 2em;
margin: 0;
} }
...@@ -1041,6 +1074,7 @@ onMounted(() => { ...@@ -1041,6 +1074,7 @@ onMounted(() => {
border-radius: 50%; border-radius: 50%;
margin-top: 3px; margin-top: 3px;
margin-left: 3px; margin-left: 3px;
cursor: pointer;
img { img {
width: 100%; width: 100%;
......
...@@ -32,7 +32,7 @@ const getPieChart = (data) => { ...@@ -32,7 +32,7 @@ const getPieChart = (data) => {
const value = params.value ?? ""; const value = params.value ?? "";
const percent = params.percent != null ? Math.round(params.percent) : 0; const percent = params.percent != null ? Math.round(params.percent) : 0;
return `{name|${name}}\n{time|${value} ${percent}%}`; return `{name|${name}}\n{time| ${percent}%}`;
}, },
minMargin: 10, minMargin: 10,
edgeDistance: 20, edgeDistance: 20,
......
...@@ -21,10 +21,7 @@ ...@@ -21,10 +21,7 @@
<div class="title">{{ "科技领域" }}</div> <div class="title">{{ "科技领域" }}</div>
</div> </div>
<div class="select-main"> <div class="select-main">
<el-checkbox-group <el-checkbox-group class="checkbox-group" :model-value="selectedResearchIds" @change="handleAreaGroupChange">
class="checkbox-group"
:model-value="selectedResearchIds"
@change="handleAreaGroupChange">
<el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_AREA"> <el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_AREA">
{{ RESOURCE_FILTER_ALL_AREA }} {{ RESOURCE_FILTER_ALL_AREA }}
</el-checkbox> </el-checkbox>
...@@ -40,9 +37,7 @@ ...@@ -40,9 +37,7 @@
<div class="title">{{ "发布时间" }}</div> <div class="title">{{ "发布时间" }}</div>
</div> </div>
<div class="select-main"> <div class="select-main">
<el-checkbox-group <el-checkbox-group class="checkbox-group" :model-value="selectedResearchTimeIds"
class="checkbox-group"
:model-value="selectedResearchTimeIds"
@change="handleTimeGroupChange"> @change="handleTimeGroupChange">
<el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_TIME"> <el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_TIME">
{{ RESOURCE_FILTER_ALL_TIME }} {{ RESOURCE_FILTER_ALL_TIME }}
...@@ -62,9 +57,7 @@ ...@@ -62,9 +57,7 @@
<div class="title">{{ "听证会部门" }}</div> <div class="title">{{ "听证会部门" }}</div>
</div> </div>
<div class="select-main"> <div class="select-main">
<el-checkbox-group <el-checkbox-group class="checkbox-group" :model-value="selectedResearchHearingIds"
class="checkbox-group"
:model-value="selectedResearchHearingIds"
@change="handleDeptGroupChange"> @change="handleDeptGroupChange">
<el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_DEPT"> <el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_DEPT">
{{ RESOURCE_FILTER_ALL_DEPT }} {{ RESOURCE_FILTER_ALL_DEPT }}
...@@ -82,31 +75,33 @@ ...@@ -82,31 +75,33 @@
<div class="right"> <div class="right">
<div class="card-box"> <div class="card-box">
<div class="card-content"> <div class="card-content">
<div v-for="(item, index) in displayList" :key="item.id"> <div v-for="(item, index) in hearingData" :key="item.id">
<div class="card-item"> <div class="card-item" @click="handleToReportDetail(item)">
<img class="card-item-img" src="../images/img congress.png" alt="report image" />
<img class="card-item-img" :src="item.coverImgUrl" alt="report image" />
<div class="card-item-text"> <div class="card-item-text">
<div class="card-item-title"> <div class="card-item-title">
<span v-html="highlightText(item.title)"></span> <span v-html="highlightText(item.title)"></span>
</div> </div>
<div class="card-item-time"> <div class="card-item-time">
<span v-html="highlightText(item.time + ' · ' + item.content)"></span> <span v-html="highlightText(item.time + ' · ' + item.content)"></span>
<img src="../images/image open.png" alt="open icon" class="card-open-image" /> <img src="../images/image open.png" alt="open icon" class="card-open-image"
@click.stop="handleToReportDetail(item)" />
</div> </div>
<div class="card-item-category"> {{ item.category }}</div> <div class="card-item-category" v-if="item.category"> {{ item.category }}</div>
</div> </div>
</div> </div>
<div class="divider" v-if="index !== displayList.length - 1"></div> <div class="divider" v-if="index !== hearingData.length - 1"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="right-footer"> <div class="right-footer">
<div class="info"> <div class="info">
{{ filteredHearingData.length }} 篇智库报告 {{ total }} 篇智库报告
</div> </div>
<div class="page-box"> <div class="page-box">
<el-pagination :page-size="10" background layout="prev, pager, next" :total="filteredHearingData.length" <el-pagination :page-size="10" background layout="prev, pager, next" :total="total"
@current-change="handleCurrentChange" :current-page="currentPage" /> @current-change="handleCurrentChange" :current-page="currentPage" />
</div> </div>
</div> </div>
...@@ -262,42 +257,9 @@ const handleDeptGroupChange = (val) => { ...@@ -262,42 +257,9 @@ const handleDeptGroupChange = (val) => {
handleGetThinkDynamicsReport(); handleGetThinkDynamicsReport();
}; };
const filteredHearingData = computed(() => {
const areaSel = stripAllAreaForRequest(selectedResearchIds.value);
const timeSel = stripAllTimeForRequest(selectedResearchTimeIds.value);
const deptSel = stripAllDeptForRequest(selectedResearchHearingIds.value);
const rangeStart = getDateYearsAgo(selectedYear.value || 1);
return (hearingData.value || []).filter(item => {
const itemDate = parseChineseDate(item.time);
const matchTopRange = itemDate ? itemDate >= rangeStart : true;
const matchYear =
timeSel.length === 0 ||
timeSel.some(sel => {
if (sel === RESOURCE_FILTER_EARLIER) {
return matchesEarlierChineseDate(item.time);
}
return String(item.time || "").startsWith(String(sel));
});
const matchDepartment =
deptSel.length === 0 ||
deptSel.some(department =>
String(item.content || "").includes(department) || String(item.title || "").includes(department)
);
const matchType =
areaSel.length === 0 ||
areaSel.some(typeId =>
String(item.category || "").includes(String(typeId)) || String(item.title || "").includes(String(typeId))
);
return matchTopRange && matchYear && matchDepartment && matchType;
});
});
// 只展示当前页的数据
const displayList = computed(() => {
const list = filteredHearingData.value || [];
const start = (currentPage.value - 1) * pageSize;
return list.slice(start, start + pageSize);
});
// 保持模板里的方法名不变,但改成通知父组件,直接传入当前选中值避免时序问题 // 保持模板里的方法名不变,但改成通知父组件,直接传入当前选中值避免时序问题
const handleGetThinkDynamicsReport = () => { const handleGetThinkDynamicsReport = () => {
...@@ -542,7 +504,7 @@ const handleToReportDetail = item => { ...@@ -542,7 +504,7 @@ const handleToReportDetail = item => {
width: 100%; width: 100%;
height: 77px; height: 77px;
display: flex; display: flex;
align-items: center;
...@@ -555,11 +517,9 @@ const handleToReportDetail = item => { ...@@ -555,11 +517,9 @@ const handleToReportDetail = item => {
} }
.card-item-text { .card-item-text {
flex: 1;
min-width: 0;
flex-direction: column; flex-direction: column;
justify-content: space-between;
display: flex; display: flex;
...@@ -592,6 +552,7 @@ const handleToReportDetail = item => { ...@@ -592,6 +552,7 @@ const handleToReportDetail = item => {
height: 16px; height: 16px;
margin-left: 9px; margin-left: 9px;
margin-top: 3px; margin-top: 3px;
cursor: pointer;
} }
} }
......
...@@ -21,10 +21,7 @@ ...@@ -21,10 +21,7 @@
<div class="title">{{ "科技领域" }}</div> <div class="title">{{ "科技领域" }}</div>
</div> </div>
<div class="select-main"> <div class="select-main">
<el-checkbox-group <el-checkbox-group class="checkbox-group" :model-value="selectedResearchIds" @change="handleAreaGroupChange">
class="checkbox-group"
:model-value="selectedResearchIds"
@change="handleAreaGroupChange">
<el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_AREA"> <el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_AREA">
{{ RESOURCE_FILTER_ALL_AREA }} {{ RESOURCE_FILTER_ALL_AREA }}
</el-checkbox> </el-checkbox>
...@@ -40,9 +37,7 @@ ...@@ -40,9 +37,7 @@
<div class="title">{{ "发布时间" }}</div> <div class="title">{{ "发布时间" }}</div>
</div> </div>
<div class="select-main"> <div class="select-main">
<el-checkbox-group <el-checkbox-group class="checkbox-group" :model-value="selectedResearchTimeIds"
class="checkbox-group"
:model-value="selectedResearchTimeIds"
@change="handleTimeGroupChange"> @change="handleTimeGroupChange">
<el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_TIME"> <el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_TIME">
{{ RESOURCE_FILTER_ALL_TIME }} {{ RESOURCE_FILTER_ALL_TIME }}
...@@ -62,14 +57,14 @@ ...@@ -62,14 +57,14 @@
<div class="footer-card" v-for="(item, index) in curFooterList" :key="index" <div class="footer-card" v-for="(item, index) in curFooterList" :key="index"
@click="handleToReportDetail(item)"> @click="handleToReportDetail(item)">
<div class="footer-card-top"> <div class="footer-card-top">
<img :src="item.imageUrl" alt="" /> <img :src=item.projectCoverImgUrl alt="" />
</div> </div>
<div class="footer-card-title"> <div class="footer-card-title">
<span v-html="highlightText(item.name)"></span> <span v-html="highlightText(item.projectNameZh)"></span>
</div> </div>
<div class="footer-card-footer"> <div class="footer-card-footer">
<div class="time">{{ item.times }}</div> <div class="time">{{ formatDate(item.startDate) }}</div>
<div class="from">{{ item.thinkTankName }}</div> <div class="from">{{ item.thinktankName }}</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -94,7 +89,11 @@ import { ...@@ -94,7 +89,11 @@ import {
stripAllAreaForRequest, stripAllAreaForRequest,
stripAllTimeForRequest stripAllTimeForRequest
} from "../../../utils/resourceLibraryFilters"; } from "../../../utils/resourceLibraryFilters";
const formatDate = (str) => {
if (!str) return ''
const [y, m, d] = str.split('T')[0].split('-')
return `${y}${+m}${+d}日`
};
const props = defineProps({ const props = defineProps({
researchTypeList: { researchTypeList: {
type: Array, type: Array,
......
...@@ -21,10 +21,7 @@ ...@@ -21,10 +21,7 @@
<div class="title">{{ "科技领域" }}</div> <div class="title">{{ "科技领域" }}</div>
</div> </div>
<div class="select-main"> <div class="select-main">
<el-checkbox-group <el-checkbox-group class="checkbox-group" :model-value="selectedResearchIds" @change="handleAreaGroupChange">
class="checkbox-group"
:model-value="selectedResearchIds"
@change="handleAreaGroupChange">
<el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_AREA"> <el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_AREA">
{{ RESOURCE_FILTER_ALL_AREA }} {{ RESOURCE_FILTER_ALL_AREA }}
</el-checkbox> </el-checkbox>
...@@ -40,9 +37,7 @@ ...@@ -40,9 +37,7 @@
<div class="title">{{ "发布时间" }}</div> <div class="title">{{ "发布时间" }}</div>
</div> </div>
<div class="select-main"> <div class="select-main">
<el-checkbox-group <el-checkbox-group class="checkbox-group" :model-value="selectedResearchTimeIds"
class="checkbox-group"
:model-value="selectedResearchTimeIds"
@change="handleTimeGroupChange"> @change="handleTimeGroupChange">
<el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_TIME"> <el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_TIME">
{{ RESOURCE_FILTER_ALL_TIME }} {{ RESOURCE_FILTER_ALL_TIME }}
...@@ -68,7 +63,7 @@ ...@@ -68,7 +63,7 @@
<span v-html="highlightText(item.name)"></span> <span v-html="highlightText(item.name)"></span>
</div> </div>
<div class="footer-card-footer"> <div class="footer-card-footer">
<div class="time">{{ item.times }}</div> <div class="time">{{ formatDate(item.times) }}</div>
<div class="from">{{ item.thinkTankName }}</div> <div class="from">{{ item.thinkTankName }}</div>
</div> </div>
</div> </div>
...@@ -94,7 +89,11 @@ import { ...@@ -94,7 +89,11 @@ import {
stripAllAreaForRequest, stripAllAreaForRequest,
stripAllTimeForRequest stripAllTimeForRequest
} from "../../../utils/resourceLibraryFilters"; } from "../../../utils/resourceLibraryFilters";
const formatDate = (str) => {
if (!str) return ''
const [y, m, d] = str.split('T')[0].split('-')
return `${y}${+m}${+d}日`
};
const props = defineProps({ const props = defineProps({
researchTypeList: { researchTypeList: {
type: Array, type: Array,
......
...@@ -38,43 +38,41 @@ ...@@ -38,43 +38,41 @@
<template #prefix> <template #prefix>
<img v-if="sort !== true" src="./images/image down.png" class="select-prefix-img" alt="" <img v-if="sort !== true" src="./images/image down.png" class="select-prefix-img" alt=""
@click.stop="toggleSortAndFetch()" /> @click.stop="toggleSortAndFetch()" />
<img v-else src="./images/image up.png" class="select-prefix-img" alt="" @click.stop="toggleSortAndFetch()" /> <img v-else src="./images/image up.png" class="select-prefix-img" alt=""
@click.stop="toggleSortAndFetch()" />
</template> </template>
<el-option @click="handleGetThinkDynamicsReport()" :key="true" label="正序" :value="true" /> <el-option :key="'think-dynamics-sort-asc'" label="正序" :value="true"
<el-option @click="handleGetThinkDynamicsReport()" :key="false" label="倒序" :value="false" /> @click="handleGetThinkDynamicsReport()" />
<el-option :key="'think-dynamics-sort-desc'" label="倒序" :value="false"
@click="handleGetThinkDynamicsReport()" />
</el-select> </el-select>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div v-if="isThinkTankReport"> <ThinkTankReport v-if="isThinkTankReport" :research-type-list="researchTypeList"
<ThinkTankReport :research-type-list="researchTypeList" :research-time-list="researchTimeList" :research-time-list="researchTimeList" :key="`智库报告-${tabResetKey}`" :selected-filters="selectedFilters"
:key="`智库报告-${tabResetKey}`" :selected-filters="selectedFilters" :cur-footer-list="curFooterList" :total="total" :cur-footer-list="curFooterList" :total="total" :current-page="currentPage" :search-keyword="searchReport"
:current-page="currentPage" :search-keyword="searchReport" @update:selected-filters="handleSelectedFiltersUpdate" @update:selected-filters="handleSelectedFiltersUpdate"
@filter-change="(payload) => handleGetThinkDynamicsReport(payload)" @page-change="handleCurrentChange" @filter-change="(payload) => handleGetThinkDynamicsReport(payload)" @page-change="handleCurrentChange"
@report-click="handleToReportDetail" /> @report-click="handleToReportDetail" />
</div> <CongressHearing v-else-if="isCongressHearing" :research-type-list="researchTypeList"
<div v-if="isCongressHearing"> :research-time-list="researchTimeList" :key="`国会听证会-${tabResetKey}`" :research-hearing-list="researchHearingList"
<CongressHearing :research-type-list="researchTypeList" :research-time-list="researchTimeList" :selected-filters="selectedFilters" :selected-year="selectedYear" :total="total" :current-page="currentPage"
:key="`国会听证会-${tabResetKey}`" :research-hearing-list="researchHearingList" :selected-filters="selectedFilters" :search-keyword="searchReport" :hearing-data="hearingData" @update:selected-filters="handleSelectedFiltersUpdate"
:selected-year="selectedYear" :cur-footer-list="curFooterList" :total="total" :current-page="currentPage"
:search-keyword="searchReport"
:hearing-data="hearingData" @update:selected-filters="handleSelectedFiltersUpdate"
@filter-change="(payload) => handleGetThinkDynamicsReport(payload)" @page-change="handleCurrentChange" @filter-change="(payload) => handleGetThinkDynamicsReport(payload)" @page-change="handleCurrentChange"
@report-click="handleToReportDetail" /> @report-click="handleToHearingDetail" />
</div> <SurveyForm v-else-if="isSurveyForm" :research-type-list="researchTypeList" :research-time-list="researchTimeList"
<div> :key="`调查项目-${tabResetKey}`" :selected-filters="selectedFilters" :cur-footer-list="curFooterProjectList"
<SurveyForm v-if="isSurveyForm" :research-type-list="researchTypeList" :research-time-list="researchTimeList" :total="total" :current-page="currentPage" :search-keyword="searchReport"
:key="`调查项目-${tabResetKey}`" :selected-filters="selectedFilters" :cur-footer-list="curFooterList" :total="total" @update:selected-filters="handleSelectedFiltersUpdate"
:current-page="currentPage" :search-keyword="searchReport" @update:selected-filters="handleSelectedFiltersUpdate"
@filter-change="(payload) => handleGetThinkDynamicsReport(payload)" @page-change="handleCurrentChange" @filter-change="(payload) => handleGetThinkDynamicsReport(payload)" @page-change="handleCurrentChange"
@report-click="handleToReportDetail" /> @report-click="handleToProjectDetail" />
</div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted } from "vue"; import { ref, reactive, onMounted, nextTick } from "vue";
import SurveyForm from "./SurveyForm/index.vue" import SurveyForm from "./SurveyForm/index.vue"
// import Img1 from "./images/img1.png"; // import Img1 from "./images/img1.png";
// import Img2 from "./images/img2.png"; // import Img2 from "./images/img2.png";
...@@ -92,7 +90,9 @@ import SurveyForm from "./SurveyForm/index.vue" ...@@ -92,7 +90,9 @@ import SurveyForm from "./SurveyForm/index.vue"
import { import {
getThinkDynamicsReportType, getThinkDynamicsReportType,
getHylyList, getHylyList,
getThinkTankReport getThinkTankReport,
getThinkTankProjectsByThinkTankId,
getThinkTankTestimoniesByThinkTankId
} from "@/api/thinkTank/overview"; } from "@/api/thinkTank/overview";
import { import {
RESOURCE_FILTER_EARLIER, RESOURCE_FILTER_EARLIER,
...@@ -120,129 +120,33 @@ const handleToReportDetail = (item) => { ...@@ -120,129 +120,33 @@ const handleToReportDetail = (item) => {
window.open(route.href, "_blank"); window.open(route.href, "_blank");
} }
const hearingData = ref([ const handleToHearingDetail = item => {
// 军事类(10条) const id = item?.id;
{ if (!id) return;
title: "2025财年美国国防部国防授权法案听证会:军事预算与战略部署", window.sessionStorage.setItem('curTabName', item?.title || item?.name || "");
content: "参与美中经济与安全审查委员会听证会",
category: "军事", const route = router.resolve({
time: "2025年7月8日" name: "CongressHearingView",
}, params: {
{ id
title: "美国国会众议院听证会:美军网络安全与军事信息系统防护", }
content: "参与美中经济与安全审查委员会听证会", });
category: "军事", window.open(route.href, "_blank");
time: "2025年6月15日" };
}, const handleToProjectDetail = (item) => {
{ window.sessionStorage.setItem('curTabName', item.name)
title: "俄乌冲突背景下美国对乌军事援助听证会:援助规模与后续规划", const route = router.resolve({
content: "参与美中经济与安全审查委员会听证会", name: 'SurveyProjectView',
category: "军事", params: {
time: "2025年5月22日" id: item.id
},
{
title: "美国海军舰艇建造与维护听证会:舰队规模目标与工业基础支撑",
content: "参与美中经济与安全审查委员会听证会",
category: "军事",
time: "2025年4月10日"
},
{
title: "美军太空军事能力建设听证会:太空军发展与反卫星武器管控",
content: "参与美中经济与安全审查委员会听证会",
category: "军事",
time: "2025年3月5日"
},
{
title: "美国国防工业基础听证会:军工供应链韧性与本土制造能力",
content: "参与美中经济与安全审查委员会听证会",
category: "军事",
time: "2025年2月18日"
},
{
title: "美军人工智能军事应用听证会:伦理规范与作战效能评估",
content: "参与美中经济与安全审查委员会听证会",
category: "军事",
time: "2025年1月30日"
},
{
title: "美国陆军未来作战系统听证会:下一代战车与单兵装备研发",
content: "参与美中经济与安全审查委员会听证会",
category: "军事",
time: "2025年7月2日"
},
{
title: "北约军事合作听证会:美国对北约防务开支承诺与兵力部署",
content: "参与美中经济与安全审查委员会听证会",
category: "军事",
time: "2025年6月8日"
},
{
title: "美军核力量现代化听证会:洲际导弹更新与核不扩散政策",
content: "参与美中经济与安全审查委员会听证会",
category: "军事",
time: "2025年5月1日"
},
// 先进制造类(10条)
{
title: "美国国会听证会:先进制造业供应链重塑与本土产业回流",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年7月15日"
},
{
title: "先进制造中的人工智能应用听证会:智能制造与产业升级",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年6月20日"
},
{
title: "美国先进制造业研发法案听证会:资金分配与技术突破方向",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年4月25日"
},
{
title: "新能源装备先进制造听证会:动力电池与氢能装备产业化",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年3月12日"
},
{
title: "美国半导体先进制造听证会:芯片制造本土化与技术封锁",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年2月8日"
},
{
title: "先进制造劳动力培训听证会:技能匹配与产业人才供给",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年1月15日"
},
{
title: "航空航天先进制造听证会:民用飞机轻量化材料与智能制造",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年7月20日"
},
{
title: "先进制造中的可持续发展听证会:绿色制造与碳中和目标",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年6月1日"
},
{
title: "美国国防先进制造听证会:军工制造数字化与快速响应能力",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年5月18日"
},
{
title: "先进制造标准制定听证会:美国主导的全球制造标准体系构建",
content: "参与美中经济与安全审查委员会听证会",
category: "先进制造",
time: "2025年4月8日"
} }
});
window.open(route.href, "_blank");
}
const hearingData = ref([
]) ])
const reportTypeList = ref([ const reportTypeList = ref([
{ {
...@@ -314,11 +218,13 @@ const resetThinkDynamicsState = () => { ...@@ -314,11 +218,13 @@ const resetThinkDynamicsState = () => {
currentPage.value = 1 currentPage.value = 1
selectedFilters.value = createDefaultSelectedFilters() selectedFilters.value = createDefaultSelectedFilters()
curFooterList.value = [] curFooterList.value = []
curFooterProjectList.value = []
hearingData.value = []
total.value = 0 total.value = 0
tabResetKey.value += 1 tabResetKey.value += 1
} }
const handleChooseType = (type) => { const handleChooseType = async (type) => {
if (activeTypeId.value === type.id) { if (activeTypeId.value === type.id) {
return return
} }
...@@ -336,8 +242,10 @@ const handleChooseType = (type) => { ...@@ -336,8 +242,10 @@ const handleChooseType = (type) => {
isCongressHearing.value = false isCongressHearing.value = false
isSurveyForm.value = true isSurveyForm.value = true
} }
// 等当前 tick 完成卸载/挂载后再重置 key 与拉数,避免 patch 阶段子组件 vnode 为 null(emitsOptions 报错)
await nextTick()
resetThinkDynamicsState() resetThinkDynamicsState()
handleGetThinkDynamicsReport() await handleGetThinkDynamicsReport()
} }
const researchTimeList = ref([ const researchTimeList = ref([
{ {
...@@ -366,34 +274,7 @@ const researchTimeList = ref([ ...@@ -366,34 +274,7 @@ const researchTimeList = ref([
} }
]) ])
const researchHearingList = ref([ const researchHearingList = ref([
{
id: '美中经济与安全审查委员会',
name: '美中经济与安全审查委员会',
},
{
id: '国会-行政部门中国委员会',
name: '国会-行政部门中国委员会',
},
{
id: '美中战略竞争特设委员会',
name: '美中战略竞争特设委员会',
},
{
id: '众议院小企业委员会',
name: '众议院小企业委员会',
},
{
id: '众议院国土安全委员会',
name: '众议院国土安全委员会',
},
{
id: '参议院能源与自然资源委员会',
name: '参议院能源与自然资源委员会',
},
{
id: '新材料',
name: '新材料',
}
]) ])
const selectedFilters = ref(createDefaultSelectedFilters()) const selectedFilters = ref(createDefaultSelectedFilters())
...@@ -403,9 +284,9 @@ const handleSelectedFiltersUpdate = val => { ...@@ -403,9 +284,9 @@ const handleSelectedFiltersUpdate = val => {
const author = ref('') // 作者 const author = ref('') // 作者
/** 智库报告 / 调查项目共用列表:与资源库一致,初始为空,接口非成功时清空 */ /** 智库报告 */
const curFooterList = ref([]); const curFooterList = ref([]);
const curFooterProjectList = ref([]);
const selectedYear = ref(1); const selectedYear = ref(1);
...@@ -459,6 +340,43 @@ function arrayToString(arr) { ...@@ -459,6 +340,43 @@ function arrayToString(arr) {
}, ""); }, "");
} }
/** 国会听证会列表筛选用「xxxx年x月x日」格式 */
const formatHearingTimeForFilter = (value) => {
if (value === null || value === undefined || value === "") return "";
const s = String(value).trim();
if (/年/.test(s)) return s;
const d = new Date(s);
if (!Number.isNaN(d.getTime())) {
return `${d.getFullYear()}${d.getMonth() + 1}${d.getDate()}日`;
}
return s;
};
/** 后端证词/听证会条目 → CongressHearing 组件所需字段(与 /think-tank/{id}/testimonies 返回对齐) */
const mapTestimonyToHearingItem = (raw, index) => {
const item = raw || {};
const title = item.titleZh || item.title || item.hearingTitle || item.name || "";
return {
id: item.id ?? item.testimonyId ?? `hearing-${index}`,
name: title,
title,
coverImgUrl: item.coverImgUrl || "",
thinkTankLogoUrl: item.thinkTankLogoUrl || "",
time: formatHearingTimeForFilter(
item.testimonyDate || item.hearingDate || item.date || item.startDate || item.publishTime
),
content:
item.committeeZh ||
item.committee ||
item.organization ||
item.summaryZh ||
item.summary ||
item.subtitle ||
"",
category: item.category || item.domain || item.area || item.field || ""
};
};
// 获取智库动态报告,payload 为子组件筛选变更时传入的当前选中值,避免时序导致拿到旧值 // 获取智库动态报告,payload 为子组件筛选变更时传入的当前选中值,避免时序导致拿到旧值
const handleGetThinkDynamicsReport = async (payload) => { const handleGetThinkDynamicsReport = async (payload) => {
const nextFilters = payload && typeof payload === "object" const nextFilters = payload && typeof payload === "object"
...@@ -472,8 +390,8 @@ const handleGetThinkDynamicsReport = async (payload) => { ...@@ -472,8 +390,8 @@ const handleGetThinkDynamicsReport = async (payload) => {
selectedFilters.value = nextFilters; selectedFilters.value = nextFilters;
currentPage.value = 1; currentPage.value = 1;
} }
// 国会听证会走本地数据,与资源库 /thinkTankOverview/report 无关
if (!isThinkTankReport.value && !isSurveyForm.value) { if (!isThinkTankReport.value && !isSurveyForm.value && !isCongressHearing.value) {
return; return;
} }
try { try {
...@@ -490,6 +408,43 @@ const handleGetThinkDynamicsReport = async (payload) => { ...@@ -490,6 +408,43 @@ const handleGetThinkDynamicsReport = async (payload) => {
: arrayToString(areas); : arrayToString(areas);
const keyword = (searchReport.value || "").trim(); const keyword = (searchReport.value || "").trim();
const thinkTankId = router.currentRoute._value.params?.id; const thinkTankId = router.currentRoute._value.params?.id;
if (isCongressHearing.value) {
curFooterList.value = [];
curFooterProjectList.value = [];
const res = await getThinkTankTestimoniesByThinkTankId({
thinkTankId,
pageNum: Math.max(0, (currentPage.value || 1)),
pageSize: 10
});
if (res.code === 200 && res.data) {
const list = Array.isArray(res.data.content) ? res.data.content : Array.isArray(res.data) ? res.data : [];
hearingData.value = list.map((row, idx) => mapTestimonyToHearingItem(row, idx));
total.value = res.data.totalElements ?? hearingData.value.length;
} else {
hearingData.value = [];
total.value = 0;
}
return;
}
if (isSurveyForm.value) {
// 调查项目:用新接口 /think-tank/{thinkTankId}/projects
curFooterList.value = [];
const res = await getThinkTankProjectsByThinkTankId({
thinkTankId,
pageNum: currentPage.value,
pageSize: 12
});
if (res.code === 200 && res.data) {
curFooterProjectList.value = res.data.content || [];
total.value = res.data.totalElements || 0;
} else {
curFooterProjectList.value = [];
total.value = 0;
}
return;
}
// 智库报告:保持原逻辑
const params = { const params = {
pageNum: currentPage.value, pageNum: currentPage.value,
pageSize: 12, pageSize: 12,
...@@ -498,9 +453,6 @@ const handleGetThinkDynamicsReport = async (payload) => { ...@@ -498,9 +453,6 @@ const handleGetThinkDynamicsReport = async (payload) => {
startDate, startDate,
endDate endDate
}; };
if (isSurveyForm.value) {
params.category = "调查项目";
}
if (thinkTankId) { if (thinkTankId) {
params.thinkTankId = thinkTankId; params.thinkTankId = thinkTankId;
} }
...@@ -588,6 +540,7 @@ onMounted(async () => { ...@@ -588,6 +540,7 @@ onMounted(async () => {
// 图层模式:穿透(即允许点击事件穿透,如需的话) // 图层模式:穿透(即允许点击事件穿透,如需的话)
box-sizing: border-box; // 确保描边/内边距不影响尺寸 box-sizing: border-box; // 确保描边/内边距不影响尺寸
cursor: pointer; // 鼠标悬停时显示为可点击
&.active { &.active {
background: rgb(5, 95, 194); // 蓝色背景 background: rgb(5, 95, 194); // 蓝色背景
......
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
{{ item.name }} {{ item.name }}
</div> </div>
<div class="footer-card-footer"> <div class="footer-card-footer">
<div class="time">{{ item.times }}</div> <div class="time">{{ formatDate(item.times) }}</div>
<div class="from">{{ item.thinkTankName }}</div> <div class="from">{{ item.thinkTankName }}</div>
</div> </div>
</div> </div>
...@@ -97,6 +97,11 @@ const handleTimeGroupChange = (val) => { ...@@ -97,6 +97,11 @@ const handleTimeGroupChange = (val) => {
emit("update:selectedPubTimeList", normalizeExclusiveAllOption(val, RESOURCE_FILTER_ALL_TIME)); emit("update:selectedPubTimeList", normalizeExclusiveAllOption(val, RESOURCE_FILTER_ALL_TIME));
emit("filter-change"); emit("filter-change");
}; };
const formatDate = (str) => {
if (!str) return ''
const [y, m, d] = str.split('T')[0].split('-')
return `${y}${+m}${+d}日`
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
......
...@@ -8,18 +8,11 @@ ...@@ -8,18 +8,11 @@
<div class="title">{{ "科技领域" }}</div> <div class="title">{{ "科技领域" }}</div>
</div> </div>
<div class="select-main"> <div class="select-main">
<el-checkbox-group <el-checkbox-group class="checkbox-group" :model-value="selectedAreaList" @change="handleAreaGroupChange">
class="checkbox-group"
:model-value="selectedAreaList"
@change="handleAreaGroupChange">
<el-checkbox class="filter-checkbox all-checkbox" :label="RESOURCE_FILTER_ALL_AREA"> <el-checkbox class="filter-checkbox all-checkbox" :label="RESOURCE_FILTER_ALL_AREA">
{{ RESOURCE_FILTER_ALL_AREA }} {{ RESOURCE_FILTER_ALL_AREA }}
</el-checkbox> </el-checkbox>
<el-checkbox <el-checkbox v-for="research in areaList" :key="research.id" class="filter-checkbox" :label="research.id">
v-for="research in areaList"
:key="research.id"
class="filter-checkbox"
:label="research.id">
{{ research.name }} {{ research.name }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
...@@ -32,18 +25,11 @@ ...@@ -32,18 +25,11 @@
<div class="title">{{ "发布时间" }}</div> <div class="title">{{ "发布时间" }}</div>
</div> </div>
<div class="select-main"> <div class="select-main">
<el-checkbox-group <el-checkbox-group class="checkbox-group" :model-value="selectedPubTimeList" @change="handleTimeGroupChange">
class="checkbox-group"
:model-value="selectedPubTimeList"
@change="handleTimeGroupChange">
<el-checkbox class="filter-checkbox all-checkbox" :label="RESOURCE_FILTER_ALL_TIME"> <el-checkbox class="filter-checkbox all-checkbox" :label="RESOURCE_FILTER_ALL_TIME">
{{ RESOURCE_FILTER_ALL_TIME }} {{ RESOURCE_FILTER_ALL_TIME }}
</el-checkbox> </el-checkbox>
<el-checkbox <el-checkbox v-for="time in pubTimeList" :key="time.id" class="filter-checkbox" :label="time.id">
v-for="time in pubTimeList"
:key="time.id"
class="filter-checkbox"
:label="time.id">
{{ time.name }} {{ time.name }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
...@@ -56,14 +42,14 @@ ...@@ -56,14 +42,14 @@
<div class="footer-card" v-for="(item, index) in curFooterList" :key="index" <div class="footer-card" v-for="(item, index) in curFooterList" :key="index"
@click="emit('report-click', item)"> @click="emit('report-click', item)">
<div class="footer-card-top"> <div class="footer-card-top">
<img :src="item.imageUrl" alt="" /> <img :src="item.projectCoverImgUrl" alt="" />
</div> </div>
<div class="footer-card-title"> <div class="footer-card-title">
{{ item.name }} {{ item.projectNameZh }}
</div> </div>
<div class="footer-card-footer"> <div class="footer-card-footer">
<div class="time">{{ item.times }}</div> <div class="time">{{ formatDate(item.startDate) }}</div>
<div class="from">{{ item.thinkTankName }}</div> <div class="from">{{ item.thinktankName }}</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -94,7 +80,11 @@ defineProps({ ...@@ -94,7 +80,11 @@ defineProps({
total: { type: Number, default: 0 }, total: { type: Number, default: 0 },
currentPage: { type: Number, default: 1 } currentPage: { type: Number, default: 1 }
}); });
const formatDate = (str) => {
if (!str) return ''
const [y, m, d] = str.split('T')[0].split('-')
return `${y}${+m}${+d}日`
};
const emit = defineEmits([ const emit = defineEmits([
"update:selectedAreaList", "update:selectedAreaList",
"update:selectedPubTimeList", "update:selectedPubTimeList",
...@@ -190,12 +180,14 @@ const handleTimeGroupChange = (val) => { ...@@ -190,12 +180,14 @@ const handleTimeGroupChange = (val) => {
flex-wrap: wrap; flex-wrap: wrap;
gap: 16px 16px; gap: 16px 16px;
.footer-card { .footer-card {
width: 398px; width: 398px;
height: 300px; height: 300px;
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1); background: rgba(255, 255, 255, 1);
cursor: pointer;
.footer-card-top { .footer-card-top {
width: 364px; width: 364px;
......
...@@ -7,18 +7,11 @@ ...@@ -7,18 +7,11 @@
<div class="title">{{ "科技领域" }}</div> <div class="title">{{ "科技领域" }}</div>
</div> </div>
<div class="select-main"> <div class="select-main">
<el-checkbox-group <el-checkbox-group class="checkbox-group" :model-value="selectedResearchIds" @change="handleAreaGroupChange">
class="checkbox-group"
:model-value="selectedResearchIds"
@change="handleAreaGroupChange">
<el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_AREA"> <el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_AREA">
{{ RESOURCE_FILTER_ALL_AREA }} {{ RESOURCE_FILTER_ALL_AREA }}
</el-checkbox> </el-checkbox>
<el-checkbox <el-checkbox v-for="type in researchTypeList" :key="type.id" class="filter-checkbox" :label="type.id">
v-for="type in researchTypeList"
:key="type.id"
class="filter-checkbox"
:label="type.id">
{{ type.name }} {{ type.name }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
...@@ -31,18 +24,12 @@ ...@@ -31,18 +24,12 @@
<div class="title">{{ "发布时间" }}</div> <div class="title">{{ "发布时间" }}</div>
</div> </div>
<div class="select-main"> <div class="select-main">
<el-checkbox-group <el-checkbox-group class="checkbox-group" :model-value="selectedResearchTimeIds"
class="checkbox-group"
:model-value="selectedResearchTimeIds"
@change="handleTimeGroupChange"> @change="handleTimeGroupChange">
<el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_TIME"> <el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_TIME">
{{ RESOURCE_FILTER_ALL_TIME }} {{ RESOURCE_FILTER_ALL_TIME }}
</el-checkbox> </el-checkbox>
<el-checkbox <el-checkbox v-for="type in researchTimeList" :key="type.id" class="filter-checkbox" :label="type.id">
v-for="type in researchTimeList"
:key="type.id"
class="filter-checkbox"
:label="type.id">
{{ type.name }} {{ type.name }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
...@@ -55,18 +42,12 @@ ...@@ -55,18 +42,12 @@
<div class="title">{{ "听证会部门" }}</div> <div class="title">{{ "听证会部门" }}</div>
</div> </div>
<div class="select-main"> <div class="select-main">
<el-checkbox-group <el-checkbox-group class="checkbox-group hearing-grid" :model-value="selectedResearchHearingIds"
class="checkbox-group hearing-grid"
:model-value="selectedResearchHearingIds"
@change="handleDeptGroupChange"> @change="handleDeptGroupChange">
<el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_DEPT"> <el-checkbox class="filter-checkbox" :label="RESOURCE_FILTER_ALL_DEPT">
{{ RESOURCE_FILTER_ALL_DEPT }} {{ RESOURCE_FILTER_ALL_DEPT }}
</el-checkbox> </el-checkbox>
<el-checkbox <el-checkbox v-for="type in researchHearingList" :key="type.id" class="filter-checkbox" :label="type.id">
v-for="type in researchHearingList"
:key="type.id"
class="filter-checkbox"
:label="type.id">
{{ type.name }} {{ type.name }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
...@@ -77,35 +58,34 @@ ...@@ -77,35 +58,34 @@
<div class="right"> <div class="right">
<div class="card-box"> <div class="card-box">
<div class="card-content"> <div class="card-content">
<div v-for="(item, index) in displayList" :key="item.id ?? index"> <div v-for="(item, index) in hearingData" :key="item.id ?? index">
<div class="card-item" @click="emit('report-click', item)"> <div class="card-item">
<img class="card-item-img" src="../ThinkTankDetail/thinkDynamics/images/img congress.png" <img class="card-item-img" :src=item.coverImgUrl alt="report image" />
alt="report image" />
<div class="card-item-text"> <div class="card-item-text">
<div class="card-item-title"> <div class="card-item-title">
{{ item.title }} {{ item.titleZh }}
</div> </div>
<div class="card-item-time"> <div class="card-item-time">
{{ item.time + ' · ' + item.content }} {{ item.testimonyDate + ' · ' + item.committeeZh }}
<img src="../ThinkTankDetail/thinkDynamics/images/image open.png" alt="open icon" <img src="../ThinkTankDetail/thinkDynamics/images/image open.png" alt="open icon"
class="card-open-image" /> class="card-open-image" @click="emit('report-click', item)" />
</div> </div>
<div class="card-item-category"> <div class="card-item-category" v-if="item.category">
<AreaTag :key="`cat-${item.id}`" :tagName="item.category" /> <AreaTag :key="`cat-${item.id}`" :tagName="item.category" />
</div> </div>
</div> </div>
</div> </div>
<div class="divider" v-if="index !== displayList.length - 1"></div> <div class="divider" v-if="index !== hearingData.length - 1"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="right-footer"> <div class="right-footer">
<div class="info"> <div class="info">
{{ filteredHearingData.length }} 篇智库报告 {{ hearingData.length }} 篇智库报告
</div> </div>
<div class="page-box"> <div class="page-box">
<el-pagination :page-size="pageSize" background layout="prev, pager, next" :total="filteredHearingData.length" <el-pagination :page-size="pageSize" background layout="prev, pager, next" :total="total"
@current-change="handlePageChange" :current-page="currentPage" /> @current-change="handlePageChange" :current-page="currentPage" />
</div> </div>
</div> </div>
...@@ -131,6 +111,8 @@ import { ...@@ -131,6 +111,8 @@ import {
const props = defineProps({ const props = defineProps({
researchTypeList: { type: Array, default: () => [] }, researchTypeList: { type: Array, default: () => [] },
researchTimeList: { type: Array, default: () => [] }, researchTimeList: { type: Array, default: () => [] },
hearingData: { type: Array, default: () => [] },
total: { type: Number, default: 0 }
}); });
const emit = defineEmits(["report-click"]); const emit = defineEmits(["report-click"]);
...@@ -142,25 +124,10 @@ const selectedResearchIds = ref([RESOURCE_FILTER_ALL_AREA]); ...@@ -142,25 +124,10 @@ const selectedResearchIds = ref([RESOURCE_FILTER_ALL_AREA]);
const selectedResearchTimeIds = ref([RESOURCE_FILTER_ALL_TIME]); const selectedResearchTimeIds = ref([RESOURCE_FILTER_ALL_TIME]);
const selectedResearchHearingIds = ref([RESOURCE_FILTER_ALL_DEPT]); const selectedResearchHearingIds = ref([RESOURCE_FILTER_ALL_DEPT]);
const hearingData = ref([
{ id: 1, title: "美国国会听证会:人工智能与国家安全", content: "美中经济与安全审查委员会", category: "人工智能", time: "2025年7月8日" },
{ id: 2, title: "美国国会听证会:先进制造供应链韧性", content: "国会-行政部门中国委员会", category: "先进制造", time: "2025年6月15日" },
{ id: 3, title: "美国国会听证会:半导体出口管制与产业政策", content: "美国商务部", category: "半导体", time: "2025年5月22日" },
{ id: 4, title: "美国国会听证会:人工智能与国家安全", content: "美中经济与安全审查委员会", category: "人工智能", time: "2025年7月8日" },
{ id: 5, title: "美国国会听证会:先进制造供应链韧性", content: "国会-行政部门中国委员会", category: "先进制造", time: "2025年6月15日" },
{ id: 6, title: "美国国会听证会:半导体出口管制与产业政策", content: "美国商务部", category: "半导体", time: "2025年5月22日" },
{ id: 7, title: "美国国会听证会:人工智能与国家安全", content: "美中经济与安全审查委员会", category: "人工智能", time: "2025年7月8日" },
{ id: 8, title: "美国国会听证会:先进制造供应链韧性", content: "国会-行政部门中国委员会", category: "先进制造", time: "2025年6月15日" },
{ id: 9, title: "美国国会听证会:半导体出口管制与产业政策", content: "美国商务部", category: "半导体", time: "2025年5月22日" },
{ id: 10, title: "美国国会听证会:人工智能与国家安全", content: "美中经济与安全审查委员会", category: "人工智能", time: "2025年7月8日" },
{ id: 11, title: "美国国会听证会:先进制造供应链韧性", content: "国会-行政部门中国委员会", category: "先进制造", time: "2025年6月15日" },
{ id: 12, title: "美国国会听证会:半导体出口管制与产业政策", content: "美国商务部", category: "半导体", time: "2025年5月22日" },
]);
const researchHearingList = ref([ const researchHearingList = ref([
{ id: "美中经济与安全审查委员会", name: "美中经济与安全审查委员会" },
{ id: "国会-行政部门中国委员会", name: "国会-行政部门中国委员会" },
{ id: "美国商务部", name: "美国商务部" },
]); ]);
const handleAreaGroupChange = (val) => { const handleAreaGroupChange = (val) => {
...@@ -178,39 +145,9 @@ const handleDeptGroupChange = (val) => { ...@@ -178,39 +145,9 @@ const handleDeptGroupChange = (val) => {
currentPage.value = 1; currentPage.value = 1;
}; };
const filteredHearingData = computed(() => {
const areaSel = stripAllAreaForRequest(selectedResearchIds.value);
const timeSel = stripAllTimeForRequest(selectedResearchTimeIds.value);
const deptSel = stripAllDeptForRequest(selectedResearchHearingIds.value);
return (hearingData.value || []).filter(item => {
const matchYear =
timeSel.length === 0 ||
timeSel.some(sel => {
if (sel === RESOURCE_FILTER_EARLIER) {
return matchesEarlierChineseDate(item.time);
}
return String(item.time || "").startsWith(String(sel));
});
const matchDepartment =
deptSel.length === 0 ||
deptSel.some(department =>
String(item.content || "").includes(department) || String(item.title || "").includes(department)
);
const matchType =
areaSel.length === 0 ||
areaSel.some(typeId =>
String(item.category || "").includes(String(typeId)) || String(item.title || "").includes(String(typeId))
);
return matchYear && matchDepartment && matchType;
});
});
const displayList = computed(() => {
const list = filteredHearingData.value || [];
const start = (currentPage.value - 1) * pageSize;
return list.slice(start, start + pageSize);
});
const handlePageChange = page => { const handlePageChange = page => {
currentPage.value = page; currentPage.value = page;
...@@ -354,7 +291,7 @@ const handlePageChange = page => { ...@@ -354,7 +291,7 @@ const handlePageChange = page => {
width: 100%; width: 100%;
height: 77px; height: 77px;
display: flex; display: flex;
align-items: center;
.card-item-img { .card-item-img {
width: 56px; width: 56px;
...@@ -364,8 +301,8 @@ const handlePageChange = page => { ...@@ -364,8 +301,8 @@ const handlePageChange = page => {
} }
.card-item-text { .card-item-text {
flex: 1;
min-width: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
...@@ -394,6 +331,7 @@ const handlePageChange = page => { ...@@ -394,6 +331,7 @@ const handlePageChange = page => {
height: 16px; height: 16px;
margin-left: 9px; margin-left: 9px;
margin-top: 3px; margin-top: 3px;
cursor: pointer;
} }
} }
......
...@@ -47,7 +47,8 @@ ...@@ -47,7 +47,8 @@
<div class="card-item-text"> <div class="card-item-text">
<div class="card-item-title">{{ item.name }}</div> <div class="card-item-title">{{ item.name }}</div>
<div class="card-item-time"> <div class="card-item-time">
<span class="info-text">{{ item.times }} · {{ item.thinkTankName }} · {{ item.reportName }}</span> <span class="info-text">{{ formatDate(item.times) }} · {{ item.thinkTankName }} · {{ item.reportName
}}</span>
<div class="card-open-image" @click.stop="handleOpenReportOriginal(item)"> <div class="card-open-image" @click.stop="handleOpenReportOriginal(item)">
<img src="@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image open.png" alt="" /> <img src="@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image open.png" alt="" />
</div> </div>
...@@ -125,7 +126,11 @@ const handleOpenReportOriginal = (item) => { ...@@ -125,7 +126,11 @@ const handleOpenReportOriginal = (item) => {
}) })
window.open(route.href, "_blank") window.open(route.href, "_blank")
} }
const formatDate = (str) => {
if (!str) return ''
const [y, m, d] = str.split('T')[0].split('-')
return `${y}${+m}${+d}日`
};
/** 政策建议关联法案:新标签页打开法案介绍页,billId 随接口 id 变化 */ /** 政策建议关联法案:新标签页打开法案介绍页,billId 随接口 id 变化 */
const handleBillMoreClick = (bill) => { const handleBillMoreClick = (bill) => {
const billId = bill?.id; const billId = bill?.id;
......
...@@ -365,15 +365,17 @@ ...@@ -365,15 +365,17 @@
v-model:selectedAreaList="surveySelectedAreaList" :pub-time-list="pubTimeList" v-model:selectedAreaList="surveySelectedAreaList" :pub-time-list="pubTimeList"
v-model:selectedPubTimeList="surveySelectedPubTimeList" @filter-change="handleSurveyFilterChange" v-model:selectedPubTimeList="surveySelectedPubTimeList" @filter-change="handleSurveyFilterChange"
:cur-footer-list="surveyFooterList" :total="surveyTotal" :current-page="surveyCurrentPage" :cur-footer-list="surveyFooterList" :total="surveyTotal" :current-page="surveyCurrentPage"
@report-click="handleToReportDetail" @page-change="handleSurveyCurrentChange" /> @report-click="handleToSurveyProjectView" @page-change="handleSurveyCurrentChange" />
<ThinkTankCongressHearingOverview v-else-if="activeCate === '国会听证会'" :key="`congress-${resourceTabResetKey}`" <ThinkTankCongressHearingOverview v-else-if="activeCate === '国会听证会'" :key="`congress-${resourceTabResetKey}`"
:research-type-list="areaList" :research-time-list="pubTimeList" @report-click="handleToReportDetail" /> :hearing-data="hearingData" :research-type-list="areaList" :research-time-list="pubTimeList"
:total="projectTotal" @report-click="handleToHearingDetail" />
<ThinkTankPolicyAdviceOverview v-else :key="`policy-${resourceTabResetKey}`" :research-type-list="areaList" <ThinkTankPolicyAdviceOverview v-else :key="`policy-${resourceTabResetKey}`" :research-type-list="areaList"
:research-time-list="pubTimeList" :list="policyFooterList" :total="policyTotal" :research-time-list="pubTimeList" :list="policyFooterList" :total="policyTotal"
:current-page="policyCurrentPage" :page-size="7" @filter-change="handlePolicyFilterChange" :current-page="policyCurrentPage" :page-size="7" @filter-change="handlePolicyFilterChange"
@page-change="handlePolicyCurrentChange" /> @page-change="handlePolicyCurrentChange" />
</div> </div>
</div> </div>
</div> </div>
...@@ -407,7 +409,9 @@ import { ...@@ -407,7 +409,9 @@ import {
getThinkTankReport, getThinkTankReport,
getThinkTankOverviewPolicy, getThinkTankOverviewPolicy,
getThinkTankReportNews, getThinkTankReportNews,
getThinkTankReportRemarks getThinkTankReportRemarks,
getThinkTankProjects,
getThinkTankTestimonies
} from "@/api/thinkTank/overview"; } from "@/api/thinkTank/overview";
import { getPersonSummaryInfo } from "@/api/common/index"; import { getPersonSummaryInfo } from "@/api/common/index";
import getMultiLineChart from "./utils/multiLineChart"; import getMultiLineChart from "./utils/multiLineChart";
...@@ -432,7 +436,8 @@ import { ...@@ -432,7 +436,8 @@ import {
stripAllTimeForRequest, stripAllTimeForRequest,
buildNumericYearsQueryString, buildNumericYearsQueryString,
isSelectionCoveringAllOptions, isSelectionCoveringAllOptions,
getResourceLibraryReportDateRangeFromTimeSelection getResourceLibraryReportDateRangeFromTimeSelection,
} from "./utils/resourceLibraryFilters"; } from "./utils/resourceLibraryFilters";
import Message1 from "./assets/images/message-icon1.png"; import Message1 from "./assets/images/message-icon1.png";
...@@ -552,6 +557,8 @@ const getStatCountInfo = async () => { ...@@ -552,6 +557,8 @@ const getStatCountInfo = async () => {
}; };
//国会听证会数据
const hearingData = ref([]);
const searchThinktankText = ref(""); //搜索科技人物及观点 const searchThinktankText = ref(""); //搜索科技人物及观点
// 智库列表 // 智库列表
const cardList = ref([ const cardList = ref([
...@@ -1662,6 +1669,7 @@ const handleClickCate = cate => { ...@@ -1662,6 +1669,7 @@ const handleClickCate = cate => {
handleGetThinkTankPolicyAdvice(); handleGetThinkTankPolicyAdvice();
} else if (cate === "国会听证会") { } else if (cate === "国会听证会") {
resetResourceTabCommon() resetResourceTabCommon()
handleGetThinkTankHearings();
} }
}; };
...@@ -1811,6 +1819,8 @@ const surveySort = ref(null); ...@@ -1811,6 +1819,8 @@ const surveySort = ref(null);
const surveyFooterList = ref([]); const surveyFooterList = ref([]);
const surveyCurrentPage = ref(1); const surveyCurrentPage = ref(1);
const surveyTotal = ref(0); const surveyTotal = ref(0);
//国会听证会总数
const projectTotal = ref(0);
const handleSurveyFilterChange = () => { const handleSurveyFilterChange = () => {
surveyCurrentPage.value = 1; surveyCurrentPage.value = 1;
...@@ -1827,24 +1837,9 @@ const handleGetThinkTankSurvey = async () => { ...@@ -1827,24 +1837,9 @@ const handleGetThinkTankSurvey = async () => {
stripAllTimeForRequest(surveySelectedPubTimeList.value), stripAllTimeForRequest(surveySelectedPubTimeList.value),
(pubTimeList.value || []).map((x) => x.id) (pubTimeList.value || []).map((x) => x.id)
); );
const params = {
pageNum: surveyCurrentPage.value,
pageSize: 12,
sortFun: surveySort.value === true,
domainIds: (() => {
const areas = stripAllAreaForRequest(surveySelectedAreaList.value);
const allAreaIds = (areaList.value || []).map((a) => a.id);
if (isSelectionCoveringAllOptions(areas, allAreaIds)) {
return "";
}
return arrayToString(areas);
})(),
startDate,
endDate,
category: "调查项目"
};
try { try {
const res = await getThinkTankReport(params); const res = await getThinkTankProjects();
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
surveyFooterList.value = res.data.content; surveyFooterList.value = res.data.content;
surveyTotal.value = res.data.totalElements; surveyTotal.value = res.data.totalElements;
...@@ -1856,7 +1851,22 @@ const handleGetThinkTankSurvey = async () => { ...@@ -1856,7 +1851,22 @@ const handleGetThinkTankSurvey = async () => {
console.error("获取调查项目 error", error); console.error("获取调查项目 error", error);
} }
}; };
const handleGetThinkTankHearings = async () => {
try {
const res = await getThinkTankTestimonies();
if (res.code === 200 && res.data) {
hearingData.value = res.data.content;
projectTotal.value = res.data.totalElements;
} else {
hearingData.value = [];
projectTotal.value = 0;
}
} catch (error) {
console.error("获取调查项目 error", error);
}
};
// ===== 政策建议:独立状态(不影响智库报告/调查项目)===== // ===== 政策建议:独立状态(不影响智库报告/调查项目)=====
const policyFooterList = ref([]); const policyFooterList = ref([]);
const policyCurrentPage = ref(1); const policyCurrentPage = ref(1);
...@@ -2097,7 +2107,26 @@ const handleToReportDetail = item => { ...@@ -2097,7 +2107,26 @@ const handleToReportDetail = item => {
}); });
window.open(route.href, "_blank"); window.open(route.href, "_blank");
}; };
const handleToHearingDetail = item => {
window.sessionStorage.setItem("curTabName", item.titleZh);
const route = router.resolve({
name: "CongressHearingView",
params: {
id: item.id
}
});
window.open(route.href, "_blank");
};
const handleToSurveyProjectView = item => {
window.sessionStorage.setItem("curTabName", item.name);
const route = router.resolve({
name: "SurveyProjectView",
params: {
id: item.id
}
});
window.open(route.href, "_blank");
};
const handleSearch = () => { const handleSearch = () => {
window.sessionStorage.setItem("curTabName", `搜索-${searchThinktankText.value}`); window.sessionStorage.setItem("curTabName", `搜索-${searchThinktankText.value}`);
const curRoute = router.resolve({ const curRoute = router.resolve({
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
</div> </div>
<div class="header-top-right"> <div class="header-top-right">
<div class="image-name-box"> <div class="image-name-box">
<div class="image"> <img :src=thinkInfo.thinkTankLogoUrl alt="" /></div> <div class="image"><img :src="thinkInfo.thinkTankLogoUrl" alt="" /></div>
<div class="name">{{ thinkInfo.thinkTankName }}</div> <div class="name">{{ thinkInfo.thinkTankName }}</div>
</div> </div>
<div class="time">{{ thinkInfo.times }}</div> <div class="time">{{ thinkInfo.times }}</div>
...@@ -242,14 +242,38 @@ const handleDownload = async () => { ...@@ -242,14 +242,38 @@ const handleDownload = async () => {
} }
} }
/** 接口可能返回对象或数组;字段名兼容 snake/camel */
const normalizeReportSummaryRow = (row) => {
if (!row || typeof row !== "object") return {};
return {
...row,
name: row.name || "",
imageUrl: row.imageUrl || row.image || "",
ename: row.ename || row.nameEn || row.englishName || "",
times: row.times || row.postDate || "",
thinkTankName: row.thinkTankName || row.thinktankName || "",
thinkTankLogoUrl: row.thinkTankLogoUrl || row.thinktankLogo || ""
};
};
const applyReportOriginalDocumentTitle = (title) => {
const text = String(title || "").trim();
if (!text) return;
window.sessionStorage.setItem("reportOriginalTabName", text);
document.title = text;
};
// 获取报告全局信息 // 获取报告全局信息
const handleGetThinkTankReportSummary = async () => { const handleGetThinkTankReportSummary = async () => {
try { try {
const res = await getThinkTankReportSummary(router.currentRoute._value.params.id); const res = await getThinkTankReportSummary(router.currentRoute._value.params.id);
console.log("报告全局信息", res); console.log("报告全局信息", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
const raw = res.data;
thinkInfo.value = res.data const first = Array.isArray(raw) ? raw[0] : raw;
const normalized = normalizeReportSummaryRow(first);
thinkInfo.value = normalized;
applyReportOriginalDocumentTitle(normalized.name);
} }
} catch (error) { } catch (error) {
console.error("获取报告全局信息error", error); console.error("获取报告全局信息error", error);
......
...@@ -40,7 +40,7 @@ const getPieChart = (data) => { ...@@ -40,7 +40,7 @@ const getPieChart = (data) => {
const value = params.value ?? ""; const value = params.value ?? "";
const percent = params.percent != null ? Math.round(params.percent) : 0; const percent = params.percent != null ? Math.round(params.percent) : 0;
return `{name|${name}}\n{time|${value} ${percent}%}`; return `{name|${name}}\n{time| ${percent}%}`;
}, },
minMargin: 5, minMargin: 5,
edgeDistance: 10, edgeDistance: 10,
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论