提交 75b87091 authored 作者: 朱政's avatar 朱政

feat:增加政策及多智库分析观点标题跳转详情页功能,基本完成智库调查与国会听证会前端页面

上级 31c4cb68
流水线 #179 已失败 于阶段
......@@ -46,6 +46,7 @@
img {
width: 100%;
height: 100%;
display: block;
}
}
......
......@@ -2,6 +2,8 @@
const thinkTank = () => import('@/views/thinkTank/index.vue')
const ThinkTankDetail = () => import('@/views/thinkTank/ThinkTankDetail/index.vue')
const ReportDetail = () => import('@/views/thinkTank/ReportDetail/index.vue')
const SurveyProjectView = () => import('@/views/thinkTank/SurveyProjectView/index.vue')
const CongressHearingView = () => import('@/views/thinkTank/CongressHearingView/index.vue')
const ReportOriginal = () => import('@/views/thinkTank/reportOriginal/index.vue')
const allThinkTank= () => import('@/views/thinkTank/allThinkTank/index.vue')
const MultiThinkTankViewAnalysis= () => import('@/views/thinkTank/MultiThinkTankViewAnalysis/index.vue')
......@@ -39,6 +41,16 @@ const thinktankRoutes = [
name: "ReportOriginal",
component: ReportOriginal,
},
{
path: "/thinkTank/SurveyProjectView/:id",
name: "SurveyProjectView",
component: SurveyProjectView,
},
{
path: "/thinkTank/CongressHearingView/:id",
name: "CongressHearingView",
component: CongressHearingView,
},
{
path: "/thinkTank/allThinkTank",
......
<template>
<div class="wrap">
<div class="scroll-inner">
<div class="header">
<div class="header-top">
<div class="header-top-left">
<img src="../assets/images/box1-logo.png" alt="" />
<div>
<div class="title">{{ thinkInfo.name }}</div>
<div class="en-title">
{{ thinkInfo.ename }}.{{ thinkInfo.times }}
</div>
<div class="tag-box">
<!-- <div class="tag-box" v-for="value,index in thinkInfo.tags" :key="index">
<div class="tag">{{ value.industryName }}</div>
</div> -->
<AreaTag v-for="(value, index) in thinkInfo.tags" :key="index" :tagName="value.industryName"></AreaTag>
</div>
</div>
</div>
<div class="header-top-right">
<div class="image-name-box">
<div class="image"><img :src="thinkInfo.thinkTankLogoUrl" alt="" /></div>
<div class="name">{{ thinkInfo.thinkTankName }}</div>
</div>
<div class="btn-box">
<!-- <div class="btn">
<div class="icon">
<img src="./images/btn-icon1.png" alt="" />
</div>
<div class="text">{{ "查看官网" }}</div>
</div> -->
<!-- <div class="btn">
<div class="icon">
<img src="./images/btn-icon2.png" alt="" />
</div>
<div class="text" @click="goToOfficialWebsite()">{{ "查看官网" }}</div>
</div> -->
<div class="btn">
<div class="icon">
<img src="./images/pdf-image.png" alt="" />
</div>
<div class="text" @click="toReport()">{{ "文档下载" }}</div>
</div>
<!-- <div class="btn" @click="handleDownloadDocument">
<div class="icon">
<img src="./images/btn-icon3.png" alt="" />
</div>
<div class="text">{{ "文档下载" }}</div>
</div> -->
<div class="btn btn1" @click="handleAnalysisClick">
<div class="icon">
<img src="./images/paper-image.png" alt="" />
</div>
<div class="text">{{ "查看原文" }}</div>
</div>
</div>
</div>
</div>
</div>
<div class="bottom-row">
<div class="left">
<div class="box1">
<!-- <div class="box-header">
<div class="header-left"></div>
<div class="title">内容摘要</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box1-main">
{{ box1Data }}
</div> -->
<AnalysisBox title="基本信息" :showAllBtn="true">
<div class="box1-main">
<div class="text-box">
<div class="time">
<div class="time-title">发布时间:</div>
<div class="time-content">{{ publishTime }}</div>
</div>
<div class="topic">
<div class="topic-title">报告主题:</div>
<div class="topic-content">{{ reportTopic }}</div>
</div>
<div class="author">
<div class="author-title">报告作者:</div>
<div class="author-content">
<template v-if="Array.isArray(reportAuthors) && reportAuthors.length">
<span v-if="reportAuthors.length === 1">
{{ reportAuthors[0].name }}
</span>
<!-- 多个作者:显示第一个 + 等 -->
<span v-else>
{{ reportAuthors[0].name }}{{ reportAuthors.length }}
</span>
</template>
</div>
</div>
</div>
<div class="author-box">
<div class="author-item" v-for="(author, idx) in reportAuthors" :key="idx"
v-if="Array.isArray(reportAuthors) && reportAuthors.length"
@click="handleClickReportAuthor(author)">
<div class="image"><img :src="author.avatar ? author.avatar : DefaultIcon1" alt=""
@error="() => { if (author.avatar) author.avatar = null; }" /></div>
<div class="author-text">
<div class="author-name">{{ author.name }}</div>
</div>
</div>
</div>
</div>
</AnalysisBox>
</div>
<div class="box5">
<AnalysisBox title="关键词云" :showAllBtn="true">
<div class="box5-main">
<template v-if="!hasBox5ChartData">
<el-empty class="box5-el-empty" description="暂无数据" :image-size="100" />
</template>
<template v-else>
<div class="box5Chart">
<!-- 有数据后再挂载子组件:子组件仅在 onMounted 初始化,异步数据到达后需 v-if + key 强制重新挂载 -->
<WordCloudChart v-if="box5Data.length" :key="box5WordCloudKey" :data="box5Data" width="432px"
height="272px" />
</div>
<div class="box5-footer">
<TipTab :text="REPORT_ANALYSIS_TIP_BOX5" />
</div>
<div class="ai-wrap" @mouseenter="handleSwitchAiContentShowBox5(true)">
<AiButton />
</div>
<div class="ai-content" v-if="isShowAiContentBox5" @mouseleave="handleSwitchAiContentShowBox5(false)">
<AiPane :aiContent="aiContentBox5" />
</div>
</template>
</div>
</AnalysisBox>
</div>
</div>
<div class="right">
<div class="box3">
<AnalysisBox title="内容摘要" :showAllBtn="true">
<div class="box3-main">
<AiSummary>
<template #summary-content>
{{ box1Data }}
</template>
</AiSummary>
</div>
</AnalysisBox>
</div>
<div class="box4">
<AnalysisBox title="听证会内容" :showAllBtn="true">
<div class="search-box">
<el-input placeholder="搜索内容" v-model="searchOpinions" style="width: 180px"
@keyup.enter="handleSearchOpinions" />
<div class="icon">
<img src="../assets/images/Line_Search.png" alt="" @click="handleSearchOpinions" />
</div>
</div>
<div class="box4-main">
<div class="box4-main-main">
<div class="box4-item" v-for="(item, index) in filteredOpinions"
:key="item.id != null ? item.id : index">
<div class="top-row">
<div class="left">
{{ index + 1 }}
</div>
<div class="center">
<div class="title" v-html="highlightOpinionText(item.titleZh)"></div>
<div>
<img src="./images/image-open.png" alt="" class="center-image"
@click="handleOpenReportOriginal(item)" />
</div>
<div>
<img v-if="!isOpinionExpanded(item, index)" src="./images/image-down.png" alt=""
class="center-image" @click="toggleOpinion(item, index)" />
<img v-else src="./images/image-up.png" alt="" class="center-image"
@click="toggleOpinion(item, index)" />
</div>
</div>
</div>
<div v-if="isOpinionExpanded(item, index)" class="desc"
v-html="highlightOpinionText(item.contentZh)">
</div>
<!-- <div class="right"> -->
<!-- <div class="tag" v-for="(val, idx) in item.hylyList" :key="idx">
{{ val }}
</div>
<div class="tag" v-for="(val, idx) in item.serialNum" :key="idx">
{{ val }}
</div> -->
<!-- <AreaTag v-for="(val, idx) in item.hylyList" :key="idx" :tagName="val"></AreaTag>
</div> -->
<!-- <div class="more">
<img src="@/assets/icons/open.png" alt="" />
</div> -->
</div>
</div>
<div class="box4-main-footer">
<div class="info">共{{ opinionsTotal }}条听证会提问</div>
<div class="page-box">
<el-pagination :page-size="pageSize" background layout="prev, pager, next" :total="opinionsTotal"
@current-change="handleCurrentChange" :current-page="currentPage" />
</div>
</div>
</div>
</AnalysisBox>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import DefaultIcon1 from '@/assets/icons/default-icon1.png'
import WarningPane from "@/components/base/WarningPane/index.vue"
import WordCloudChart from "@/components/base/WordCloundChart/index.vue"
import SearchContainer from "@/components/SearchContainer.vue";
import { ref, onMounted, computed, defineProps } from "vue";
import { ElMessage } from "element-plus";
import {
getThinkTankReportAbstract,
getThinkTankReportContent,
getThinkTankReportIndustry,
getThinkTankReportIndustryCloud,
getThinkTankReportViewpoint
} from "@/api/thinkTank/overview";
import { getChartAnalysis } from "@/api/aiAnalysis/index";
import { useRouter } from "vue-router";
import "echarts-wordcloud";
import AiSummary from '@/components/base/Ai/AiSummary/index.vue'
import { getPersonSummaryInfo } from "@/api/common/index";
import AiButton from "@/components/base/Ai/AiButton/index.vue";
import AiPane from "@/components/base/Ai/AiPane/index.vue";
import TipTab from "@/views/thinkTank/TipTab/index.vue";
const router = useRouter();
const goToAllThinkTank = () => {
const thinkTankId = props?.thinkInfo?.thinkTankId || props?.thinkInfo?.id;
const route = router.resolve({
name: "MultiThinkTankViewAnalysis",
params: { id: thinkTankId }
});
window.open(route.href, "_blank");
};
const props = defineProps({
reportList: {
type: Object,
default: () => ({})
}
});
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 REPORT_ANALYSIS_TIP_BOX5 =
"智库报告关键词云,数据来源:美国兰德公司官网";
// 刷新后默认展示「报告关键词云」AI 总结
const isShowAiContentBox5 = ref(true);
const aiContentBox5 = ref("");
const isBox5InterpretLoading = ref(false);
const handleSwitchAiContentShowBox5 = (val) => {
isShowAiContentBox5.value = val;
if (val) {
fetchBox5ChartInterpretation();
}
};
const searchOpinions = ref('');
const escapeHtml = (text) => {
return String(text ?? "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
};
const escapeRegExp = (text) => {
return String(text ?? "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
};
const highlightOpinionText = (text) => {
const safeText = escapeHtml(text);
const keyword = (searchOpinions.value || "").trim();
if (!keyword) return safeText;
const pattern = new RegExp(`(${escapeRegExp(keyword)})`, "gi");
return safeText.replace(pattern, `<span class="opinion-keyword-highlight">$1</span>`);
};
const handleSearchOpinions = () => {
currentPage.value = 1;
handleGetThinkTankReportViewpoint();
};
/** 可同时展开多条;用 id 区分项,避免翻页后索引与展开状态错位 */
const expandedOpinionKeys = ref(new Set());
const filteredOpinions = computed(() => majorOpinions.value);
const opinionsTotal = computed(() => total.value);
const getOpinionExpandKey = (item, index) => {
if (item != null && item.id != null && item.id !== "") {
return String(item.id);
}
return `idx-${index}`;
};
const isOpinionExpanded = (item, index) => {
return expandedOpinionKeys.value.has(getOpinionExpandKey(item, index));
};
const toggleOpinion = (item, index) => {
const key = getOpinionExpandKey(item, index);
const next = new Set(expandedOpinionKeys.value);
if (next.has(key)) {
next.delete(key);
} else {
next.add(key);
}
expandedOpinionKeys.value = next;
};
const publishTime = computed(() => {
const info = props.thinkInfo || {};
// 优先用 times,其次用 reportTime 的日期部分
if (info.times) return info.times;
if (info.reportTime && typeof info.reportTime === "string") {
return info.reportTime.split("T")[0];
}
return "";
});
const reportTopic = computed(() => {
const info = props.thinkInfo || {};
return info.summary;
});
const reportAuthors = computed(() => {
const info = props.thinkInfo || {};
if (Array.isArray(info.authors) && info.authors.length) {
return info.authors;
}
return [];
});
// 点击报告作者头像,跳转到人物主页
// 与核心研究人员逻辑一致:核心依赖 personId,本页面依赖作者的 id(作为 personId 传入)
const handleClickReportAuthor = async (author) => {
const personId = author?.id;
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 riskSignal = computed(() => {
const info = props.thinkInfo || {};
return info.riskSignal;
});
// 内容摘要
const box1Data =
ref(`包括经济竞争在内的美中竞争自2017年以来一直在定义美国外交政策。这两个经济体是世界上第一和第二大国家经济体,并且深深交织在一起。改变关系,无论多么必要,可能是昂贵的。因此,美国面临着一项挑战,确保其经济在耦合的战略竞争条件下满足国家的需求。
为了应对这一挑战,兰德大学的研究人员对美中竞争进行了经济和制度分析,进行了参与式的远见练习,以了解确保美国经济健康的长期路径,并创建了两个经济竞争游戏,探索多个国家在相互交流的同时确保经济健康的动态...`);
//获取内容摘要
const handleGetThinkTankReportAbstract = async () => {
try {
const res = await getThinkTankReportAbstract(router.currentRoute._value.params.id);
console.log("内容摘要", res);
if (res.code === 200 && res.data) {
box1Data.value = res.data;
}
} catch (error) {
console.error("获取内容摘要error", error);
}
};
// 涉及科技领域
const areaList = ref([]);
const activeArea = ref(6);
const handleClickArea = area => {
activeArea.value = area;
handleGetThinkTankReportIndustryCloud();
};
const box2Data = ref([
// {
// name: "通用人工智能",
// value: 100
// },
// {
// name: "AI芯片",
// value: 66
// },
// {
// name: "计算能力又是",
// value: 72
// },
// {
// name: "基准测试",
// value: 88
// },
// {
// name: "出口管制",
// value: 78
// },
// {
// name: "军事AI",
// value: 85
// },
// {
// name: "生态系统",
// value: 88
// },
// {
// name: "模型能力",
// value: 89
// }
]);
// 报告关键词云
const box5Data = ref([]);
const hasBox5ChartData = computed(() => Array.isArray(box5Data.value) && box5Data.value.length > 0);
/** 词云子组件不 watch 数据,每次接口成功有数据时递增 key,强制重新挂载以触发 onMounted */
const box5WordCloudKey = ref(0);
//获取科技领域词云
const handleGetThinkTankReportIndustryCloud = async () => {
try {
const params = {
id: router.currentRoute._value.params.id
// industryId: activeArea.value
};
const res = await getThinkTankReportIndustryCloud(params);
console.log("科技领域词云", res);
if (res.code === 200 && res.data) {
const data = (res.data || []).map(item => ({
name: item.clause,
value: item.count
}));
// 该接口数据用于「报告关键词云」
box5Data.value = data;
if (data.length) {
box5WordCloudKey.value += 1;
}
// 刷新后默认展开 AI:数据就绪即触发解读
if (isShowAiContentBox5.value) {
fetchBox5ChartInterpretation();
}
} else {
box5Data.value = [];
}
} catch (error) {
console.error("获取科技领域词云error", error);
box5Data.value = [];
}
};
//涉及科技领域
const handleGetThinkTankReportIndustry = async () => {
try {
const res = await getThinkTankReportIndustry(router.currentRoute._value.params.id);
console.log("涉及科技领域", res);
if (res.code === 200 && res.data) {
areaList.value = res.data;
}
} catch (error) {
console.error("获取涉及科技领域error", error);
}
};
// 主要观点
const majorOpinions = ref([
{
id: 1,
title: "我是示例标题",
desc: "我是示例内容",
tagList: [
{
name: "关税",
status: 2
},
{
name: "跨境电商",
status: 1
}
]
},
{
id: 2,
title: "我是示例标题",
desc: "我是示例内容",
tagList: [
{
name: "私有经济",
status: 2
}
]
}
]);
//处理点击详情页事件
const handleOpenReportOriginal = item => {
const route = router.resolve({
name: "ReportOriginal",
params: {
id: router.currentRoute._value.params.id
},
query: {
currentPage: currentPage.value,
pageSize: pageSize.value,
opinionId: item?.id ?? "",
opinionContent: item?.content ?? ""
}
});
window.open(route.href, "_blank");
};
const tabActiveName = ref("报告分析");
const switchTab = name => {
tabActiveName.value = name;
};
// 处理页码改变事件
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(0);
const handleCurrentChange = page => {
currentPage.value = page;
handleGetThinkTankReportViewpoint();
};
// 获取报告核心论点(支持搜索)
const handleGetThinkTankReportViewpoint = async () => {
try {
const params = {
reportId: router.currentRoute._value.params.id,
currentPage: currentPage.value - 1,
pageSize: pageSize.value,
keyword: (searchOpinions.value || "").trim(),
orgIds: ""
};
const res = await getThinkTankReportViewpoint(params);
console.log("核心论点", res.data);
if (res.code === 200 && res.data) {
const nextOpinions = res.data.content || [];
majorOpinions.value = nextOpinions;
total.value = res.data.totalElements || 0;
// 默认:第一条展开,其余关闭
const nextExpandedKeys = new Set();
if (Array.isArray(nextOpinions) && nextOpinions.length > 0) {
nextExpandedKeys.add(getOpinionExpandKey(nextOpinions[0], 0));
}
expandedOpinionKeys.value = nextExpandedKeys;
}
} catch (error) {
console.error("获取主要观点error", error);
}
};
// 获取图表分析内容
const box3AnalysisContent = ref("");
const handleGetBox3AnalysisContent = async textJson => {
const params = {
text: textJson
};
const res = await getChartAnalysis(params);
console.log("图表解析内容", res);
};
const getInterpretationTextFromChartResponse = (res) => {
const list = res?.data;
const first = Array.isArray(list) ? list[0] : null;
return (
first?.["解读"] ||
first?.["interpretation"] ||
first?.["analysis"] ||
first?.["content"] ||
""
);
};
const appendAiInterpretationChunk = (targetRef, chunk, loadingText = "解读生成中…") => {
if (!chunk) {
return;
}
const current = String(targetRef.value || "");
const base = current === loadingText ? "" : current;
targetRef.value = base + String(chunk);
};
const fetchBox5ChartInterpretation = async () => {
const list = Array.isArray(box5Data.value) ? box5Data.value : [];
if (!list.length) {
aiContentBox5.value = "暂无图表数据";
return;
}
const hasValidContent =
aiContentBox5.value &&
aiContentBox5.value !== "解读生成中…" &&
aiContentBox5.value !== "解读加载失败" &&
aiContentBox5.value !== "暂无图表数据";
if (hasValidContent || isBox5InterpretLoading.value) {
return;
}
isBox5InterpretLoading.value = true;
aiContentBox5.value = "解读生成中…";
const chartPayload = {
type: "词云图",
name: "报告关键词云",
data: list.map((item) => ({
name: item.name,
value: item.value
}))
};
try {
const res = await getChartAnalysis(
{ text: JSON.stringify(chartPayload) },
{
onChunk: chunk => {
appendAiInterpretationChunk(aiContentBox5, chunk);
}
}
);
const text = getInterpretationTextFromChartResponse(res);
aiContentBox5.value = text || aiContentBox5.value || "未返回有效解读内容";
} catch (error) {
console.error("报告关键词云图表解读请求失败", error);
aiContentBox5.value = "解读加载失败";
} finally {
isBox5InterpretLoading.value = false;
}
};
onMounted(() => {
handleGetThinkTankReportAbstract();
handleGetThinkTankReportViewpoint();
handleGetThinkTankReportIndustry();
handleGetThinkTankReportIndustryCloud();
});
</script>
<style lang="scss" scoped>
.wrap {
position: absolute;
inset: 0;
box-sizing: border-box;
width: 100%;
overflow: hidden;
.scroll-inner {
box-sizing: border-box;
width: 100%;
height: 100%;
min-height: 0;
padding-bottom: 16px;
overflow-x: hidden;
overflow-y: auto;
scrollbar-gutter: stable;
scrollbar-width: thin;
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-thumb {
border-radius: 4px;
}
}
.header {
width: 100%;
height: 126px;
box-sizing: border-box;
border-bottom: 1px solid rgba(234, 236, 238, 1);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
position: sticky;
top: 0;
z-index: 99999;
overflow: hidden;
.header-top {
margin: 0 auto;
margin-top: 20px;
width: 1600px;
display: flex;
justify-content: space-between;
.header-top-left {
display: flex;
img {
width: 72px;
height: 88px;
}
.title {
margin-left: 16px;
height: 26px;
color: rgb(59, 65, 75);
font-family: "Source Han Sans CN";
font-weight: 700;
font-size: 20px;
line-height: 26px;
letter-spacing: 0px;
text-align: left;
}
.en-title {
margin-top: 4px;
margin-left: 16px;
height: 24px;
color: rgb(95, 101, 108);
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.tag-box {
margin-top: 12px;
display: flex;
gap: 8px;
margin-left: 16px;
}
}
.header-top-right {
display: flex;
flex-direction: column;
text-align: right;
align-items: flex-end;
.image-name-box {
width: 118px;
height: 24px;
gap: 6px;
text-align: right;
display: flex;
justify-content: flex-end;
.name {
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: right;
}
.image {
width: 16px;
height: 16px;
margin-top: 5px;
img {
width: 100%;
height: 100%;
}
}
}
.btn-box {
display: flex;
gap: 12px;
margin-top: 34px;
.btn {
width: 120px;
height: 36px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 6px;
background: rgba(255, 255, 255, 1);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
.icon {
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
.text {
width: 66px;
height: 22px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
text-align: center;
}
}
.btn1 {
border-radius: 6px;
background: var(--color-main-active);
.text {
color: rgba(255, 255, 255, 1);
}
}
}
.time {
height: 24px;
margin-top: 5px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: right;
}
}
}
}
.bottom-row {
flex-direction: row;
justify-content: center;
display: flex;
gap: 16px;
.left {
gap: 16px;
display: flex;
flex-direction: column;
margin-top: 16px;
.box1 {
width: 480px;
// border: 1px solid rgba(234, 236, 238, 1);
// border-radius: 10px;
// box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
// background: rgba(255, 255, 255, 1);
.box1-main {
width: 480px;
.text-box {
width: 437px;
margin-left: 22px;
margin-top: 8px;
gap: 12px;
display: flex;
flex-direction: column;
.time {
height: 24px;
display: flex;
gap: 4px;
.time-title {
width: 88px;
font-family: "Source Han Sans CN";
font-weight: 700;
font-size: 16px;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
color: rgb(59, 65, 75);
}
.time-content {
width: 345px;
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
color: rgb(59, 65, 75);
}
}
.topic {
display: flex;
gap: 4px;
.topic-title {
width: 88px;
font-family: "Source Han Sans CN";
font-weight: 700;
font-size: 16px;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
color: rgb(59, 65, 75);
}
.topic-content {
width: 345px;
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 16px;
line-height: 30px;
letter-spacing: 0px;
text-align: left;
color: rgb(59, 65, 75);
}
}
.author {
display: flex;
gap: 4px;
.author-title {
width: 88px;
font-family: "Source Han Sans CN";
font-weight: 700;
font-size: 16px;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
color: rgb(59, 65, 75);
}
.author-content {
width: 345px;
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
color: rgb(59, 65, 75);
}
}
}
.author-box {
width: 437px;
height: auto;
margin-top: 34px;
margin-left: 18px;
display: grid;
grid-template-columns: 1fr 1fr;
/* 两列等宽 */
column-gap: 4px;
/* 左右间距(同一行) */
row-gap: 8px;
/* 上下间距(同一列) */
margin-bottom: 38px;
.author-item {
width: 213px;
height: 49px;
display: flex;
gap: 11px;
.image {
width: 42px;
height: 42px;
margin-top: 3px;
margin-left: 3px;
display: inline-block;
cursor: pointer;
img {
width: 100%;
height: 100%;
border-radius: 50%;
}
}
.author-text {
width: 154px;
height: 49px;
.author-name {
width: 154px;
height: 24px;
font-family: "Source Han Sans CN";
font-weight: 700;
font-size: 16px;
line-height: 24px;
letter-spacing: 0;
text-align: left;
color: rgb(59, 65, 75);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.author-position {
width: 154px;
height: 22px;
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 14px;
line-height: 22px;
letter-spacing: 0;
text-align: left;
color: rgb(95, 101, 108);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
}
}
}
.box5 {
width: 480px;
height: 415px;
.box5-main {
width: 480px;
height: 361px;
padding: 24px 24px 65px 24px;
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
position: relative;
.box5Chart {
width: 100%;
height: 100%;
margin: 0 auto;
overflow: hidden;
}
.box5-footer {
display: flex;
justify-content: space-between;
align-items: center;
position: absolute;
bottom: 20px;
left: 32px;
}
.ai-wrap {
position: absolute;
bottom: 18px;
right: 0;
cursor: pointer;
}
.ai-content {
position: absolute;
bottom: 0;
right: 0;
min-width: 480px;
min-height: 156px;
}
}
}
.box2 {
width: 480px;
// border: 1px solid rgba(234, 236, 238, 1);
// border-radius: 10px;
// box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
// background: rgba(255, 255, 255, 1);
.box2-main {
width: 436px;
margin-top: 5px;
margin-left: 23px;
.box2-item {
height: 103px;
width: 100%;
border-bottom: 1px solid rgba(234, 236, 238, 1);
border-top: 1px solid rgba(234, 236, 238, 1);
.box2-item-content {
width: 100%;
height: 90px;
margin-top: 7px;
display: flex;
.left {
width: 56px;
height: 74px;
margin-top: 8px;
img {
width: 100%;
height: 100%;
}
}
.right-content {
margin-left: 13px;
width: 365px;
height: 76px;
margin-top: 7px;
.report-title {
height: 48px;
font-family: "Source Han Sans CN";
font-weight: 700;
font-size: 16px;
line-height: 24px;
letter-spacing: 0;
text-align: left;
/* 👇 下面是 两行文本超出省略 核心代码 */
display: -webkit-box;
-webkit-line-clamp: 2;
/* 限制显示 2 行 */
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
}
.report-footer {
margin-top: 4px;
height: 22px;
justify-content: space-between;
display: flex;
.report-time {
height: 22px;
width: 97px;
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 14px;
line-height: 22px;
letter-spacing: 0;
text-align: left;
color: rgb(95, 101, 108);
}
.report-footer-right {
height: 22px;
display: flex;
gap: 6px;
.footer-image {
width: 16px;
height: 16px;
margin-top: 3px;
img {
width: 100%;
height: 100%;
}
}
.think-name {
height: 22px;
}
}
}
}
}
}
}
.box2-btn {
margin-top: 16px;
margin-bottom: 21px;
margin-left: 23px;
width: 436px;
height: 36px;
background-color: rgb(5, 95, 194);
border-radius: 6px;
display: flex;
.btn-text {
color: rgb(255, 255, 255);
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 16px;
line-height: 22px;
letter-spacing: 0;
margin-left: 120px;
margin-top: 7px;
}
.btn-image {
width: 13px;
height: 8px;
margin-left: 8px;
display: inline-block;
margin-top: 14px;
img {
width: 100%;
height: 100%;
display: block;
}
}
}
}
}
.right {
margin-top: 16px;
gap: 16px;
display: flex;
flex-direction: column;
.box3 {
width: 1103px;
// border: 1px solid rgba(234, 236, 238, 1);
// border-radius: 10px;
// box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
// background: rgba(255, 255, 255, 1);
.box3-main {
width: 1058px;
margin-top: 3px;
margin-left: 22px;
padding-bottom: 22px;
:deep(.summary-main) {
margin-bottom: 25px;
font-family: "Source Han Sans CN";
font-weight: 400;
/* Regular 常规 */
font-size: 16px;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
color: rgb(59, 65, 75);
/* 两端对齐 */
}
.box3-top {
width: 1058px;
height: 48px;
background: linear-gradient(rgb(137, 193, 255, 0.1), rgb(255, 255, 255));
display: flex;
.top-title {
width: 1010px;
height: 32px;
margin-left: 24px;
margin-top: 16px;
.title-image {
width: 199px;
height: 32px;
img {
width: 100%;
height: 100%;
}
}
}
}
.box3-text {
width: 1006px;
margin-top: 24px;
margin-left: 26px;
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 16px;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
text-justify: inter-ideograph;
}
}
}
.box4 {
width: 1103px;
height: auto;
// border: 1px solid rgba(234, 236, 238, 1);
// border-radius: 10px;
// box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
// background: rgba(255, 255, 255, 1);
position: relative;
.search-box {
display: flex;
width: 180px;
height: 32px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
position: relative;
margin-top: 3px;
margin-bottom: 16px;
margin-left: 23px;
.icon {
width: 16px;
height: 16px;
cursor: pointer;
position: absolute;
right: 8px;
top: 8px;
display: flex;
justify-content: flex-end;
z-index: 10000;
img {
width: 100%;
height: 100%;
}
}
}
.box4-main {
width: 1057px;
height: auto;
margin: 0 auto;
.box4-main-main {
height: auto;
overflow: visible;
.box4-item {
width: 1057px;
box-sizing: border-box;
border-radius: 4px;
display: flex;
flex-direction: column;
position: relative;
border-bottom: 1px solid rgba(234, 236, 238, 1);
&:first-child {
border-top: 1px solid rgba(234, 236, 238, 1);
}
.top-row {
display: flex;
align-items: flex-start;
}
.left {
margin-top: 19px;
margin-left: 15px;
width: 24px;
height: 24px;
border-radius: 12px;
line-height: 24px;
text-align: center;
background: rgba(231, 243, 255, 1);
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 12px;
font-weight: 400;
letter-spacing: 0px;
}
.center {
min-height: 62px;
margin-left: 18px;
display: flex;
align-items: center;
// overflow: hidden;
// text-overflow: ellipsis;
// white-space: nowrap;
.title {
width: 918px;
// height: 55px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 30px;
letter-spacing: 0px;
text-align: left;
overflow: hidden;
// text-overflow: ellipsis;
// white-space: nowrap;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.center-image {
width: 16px;
height: 24px;
margin-top: 12px;
margin-left: 18px;
}
}
.right {
margin-top: 26px;
width: 180px;
height: 22px;
display: flex;
margin-top: 26px;
margin-left: 20px;
height: 22px;
display: flex;
gap: 4px;
.tag {
height: 22px;
padding: 0 8px;
box-sizing: border-box;
border: 1px solid rgba(217, 247, 190, 1);
border-radius: 4px;
background: rgba(246, 255, 237, 1);
color: rgba(82, 196, 26, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
}
.more {
width: 16px;
height: 16px;
position: absolute;
top: 28px;
right: 20px;
img {
width: 100%;
height: 100%;
}
}
.desc {
padding-top: 22px;
padding-bottom: 23px;
padding-left: 56px; // 24(left) + 13(center margin) + 一点间距
color: rgb(59, 65, 75);
font-family: "Source Han Sans CN";
font-weight: 400;
/* Regular 常规 */
font-size: 16px;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
/* 两端对齐 */
border-top: 1px solid rgba(234, 236, 238, 1);
}
.title :deep(.opinion-keyword-highlight),
.desc :deep(.opinion-keyword-highlight) {
background-color: #fff59d;
}
}
}
.box4-main-footer {
height: 80px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 30px 5px;
box-sizing: border-box;
overflow: hidden;
.info {
flex: 1 1 auto;
min-width: 0;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 18px;
letter-spacing: 0px;
text-align: left;
}
.page-box {
/* 最大 300px:允许变小,但绝不变大 */
flex: 0 1 300px;
width: 100%;
max-width: 300px;
min-width: 0;
display: flex;
justify-content: flex-end;
overflow: hidden;
}
.page-box :deep(.el-pagination) {
max-width: 100%;
min-width: 0;
overflow: hidden;
}
}
}
.box4-footer {
position: absolute;
left: 22px;
bottom: 19px;
width: 1057px;
height: 64px;
box-sizing: border-box;
border: 1px solid rgba(231, 243, 255, 1);
border-radius: 4px;
background: rgba(246, 250, 255, 1);
display: flex;
align-items: center;
justify-content: center;
gap: 13px;
.footer-left {
width: 19px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.footer-center {
width: 964px;
height: 48px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
.footer-right {
width: 24px;
height: 24px;
img {
width: 100%;
height: 100%;
}
}
}
}
.box5 {
width: 1103px;
height: auto;
// border: 1px solid rgba(234, 236, 238, 1);
// border-radius: 10px;
// box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
// background: rgba(255, 255, 255, 1);
position: relative;
}
}
}
}
:deep(.analysis-box-wrapper .wrapper-header) {
height: 54px !important;
display: flex;
align-items: center;
.header-title>div {
line-height: 54px;
}
}
</style>
......@@ -474,7 +474,7 @@ const handleOpenReportOriginalFromSource = (sv) => {
const id = String(sv?.report_id ?? "").trim()
if (!id) return
const route = router.resolve({
name: "ReportOriginal",
name: "ReportDetail",
params: { id }
})
window.open(route.href, "_blank")
......
<template>
<div class="wrap">
<div class="scroll-inner">
<div class="header">
<div class="header-top">
<div class="header-top-left">
<img src="../assets/images/box1-logo.png" alt="" />
<div>
<div class="title">{{ thinkInfo.name }}</div>
<div class="en-title">
{{ thinkInfo.ename }}
</div>
</div>
</div>
<div class="header-top-right">
<div class="image-name-box">
<div class="image"><img src="../assets/images/box1-logo.png" alt="" /></div>
<div class="name">{{ thinkInfo.thinkTankName }}</div>
</div>
<div class="tag-box">
<AreaTag v-for="(value, index) in thinkInfo.tags" :key="index" :tagName="value.industryName"></AreaTag>
</div>
</div>
</div>
</div>
<div class="bottom-row">
<div class="left">
<div class="box1">
<AnalysisBox title="关键词云" :showAllBtn="true">
<div class="box1-main">
<template v-if="!hasBox5ChartData">
<el-empty class="box5-el-empty" description="暂无数据" :image-size="100" />
</template>
<template v-else>
<div class="box5Chart">
<!-- 有数据后再挂载子组件:子组件仅在 onMounted 初始化,异步数据到达后需 v-if + key 强制重新挂载 -->
<WordCloudChart v-if="box5Data.length" :key="box5WordCloudKey" :data="box5Data" width="432px"
height="272px" />
</div>
<div class="box1-footer">
<TipTab :text="REPORT_ANALYSIS_TIP_BOX5" />
</div>
<div class="ai-wrap" @mouseenter="handleSwitchAiContentShowBox5(true)">
<AiButton />
</div>
<div class="ai-content" v-if="isShowAiContentBox5" @mouseleave="handleSwitchAiContentShowBox5(false)">
<AiPane :aiContent="aiContentBox5" />
</div>
</template>
</div>
</AnalysisBox>
</div>
<div class="box2">
<!-- <div class="box-header">
<div class="header-left"></div>
<div class="title">涉及科技领域</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon2.png" alt="" />
</div>
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box2-main">
<div class="box2-content" id="box2Chart"></div>
</div> -->
<AnalysisBox title="项目报告" :showAllBtn="true">
<div class="box2-main">
<div class="box2-item" v-for="(report, idx) in reportList" :key="idx">
<div class="box2-item-content">
<div class="left"><img :src="report.image" alt="" /></div>
<div class="right-content">
<div class="report-title">{{ report.name }}</div>
<div class="report-footer">
<div class="report-time">{{ report.postDate }}</div>
<div class="report-footer-right">
<div class="footer-image">
<img :src="report.thinktankLogo" alt="" />
</div>
<div class="think-name">{{ report.thinktankName }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</AnalysisBox>
</div>
</div>
<div class="right">
<div class="box3">
<AnalysisBox title="内容摘要" :showAllBtn="true">
<div class="box3-main">
<AiSummary>
<template #summary-content>
{{ box1Data }}
</template>
</AiSummary>
</div>
</AnalysisBox>
</div>
<div class="box4">
<AnalysisBox title="项目背景" :showAllBtn="true">
<div class="box4-main">
<div class="text">
{{
"可再生能源和清洁能源创新为摆脱对化石燃料的依赖提供了机会。然而,可再生能源的间歇性也对电网的实时供需平衡构成挑战。作为解决方案,中国提出开发全球能源互联(GEI)倡议,通过超高压输电线路和智能技术,直接将可再生能源生产者与全球消费者连接起来。北京认识到GEI的潜力,正站在GEI发展的前沿,领导关键推动技术的研究,输出中国技术和标准,支持海外发电和输电基础设施的发展。中国还在联合国和海湾合作委员会等国际组织推动GEI"
}}
</div>
</div>
</AnalysisBox>
</div>
<div class="box5">
<AnalysisBox title="项目团队" :showAllBtn="true">
<div class="box5-main">
<div class="box5-main-item-box">
<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 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>
</AnalysisBox>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import DefaultIcon1 from '@/assets/icons/default-icon1.png'
import WarningPane from "@/components/base/WarningPane/index.vue"
import WordCloudChart from "@/components/base/WordCloundChart/index.vue"
import SearchContainer from "@/components/SearchContainer.vue";
import { ref, onMounted, computed, defineProps } from "vue";
import { ElMessage } from "element-plus";
import {
getThinkTankReportAbstract,
getThinkTankReportContent,
getThinkTankReportIndustry,
getThinkTankReportIndustryCloud,
getThinkTankReportViewpoint
} from "@/api/thinkTank/overview";
import { getChartAnalysis } from "@/api/aiAnalysis/index";
import { useRouter } from "vue-router";
import "echarts-wordcloud";
import AiSummary from '@/components/base/Ai/AiSummary/index.vue'
import { getPersonSummaryInfo } from "@/api/common/index";
import AiButton from "@/components/base/Ai/AiButton/index.vue";
import AiPane from "@/components/base/Ai/AiPane/index.vue";
import TipTab from "@/views/thinkTank/TipTab/index.vue";
const router = useRouter();
const thinkInfo = ref({
name: "探讨中国开发和管理的跨大陆电网的安全影响",
ename: "调查项目",
tags: [{ industryName: "深海" }, { industryName: "人工智能" }],
thinkTankName: "兰德科技智库"
})
const REPORT_ANALYSIS_TIP_BOX5 =
"智库报告关键词云,数据来源:美国兰德公司官网";
// 刷新后默认展示「报告关键词云」AI 总结
const isShowAiContentBox5 = ref(true);
const aiContentBox5 = ref("");
const isBox5InterpretLoading = ref(false);
const handleSwitchAiContentShowBox5 = (val) => {
isShowAiContentBox5.value = val;
if (val) {
fetchBox5ChartInterpretation();
}
};
const searchOpinions = ref('');
const escapeHtml = (text) => {
return String(text ?? "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
};
const escapeRegExp = (text) => {
return String(text ?? "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
};
/** 可同时展开多条;用 id 区分项,避免翻页后索引与展开状态错位 */
const expandedOpinionKeys = ref(new Set());
const getOpinionExpandKey = (item, index) => {
if (item != null && item.id != null && item.id !== "") {
return String(item.id);
}
return `idx-${index}`;
};
// 内容摘要
const box1Data =
ref(`包括经济竞争在内的美中竞争自2017年以来一直在定义美国外交政策。这两个经济体是世界上第一和第二大国家经济体,并且深深交织在一起。改变关系,无论多么必要,可能是昂贵的。因此,美国面临着一项挑战,确保其经济在耦合的战略竞争条件下满足国家的需求。
为了应对这一挑战,兰德大学的研究人员对美中竞争进行了经济和制度分析,进行了参与式的远见练习,以了解确保美国经济健康的长期路径,并创建了两个经济竞争游戏,探索多个国家在相互交流的同时确保经济健康的动态...`);
//获取内容摘要
const handleGetThinkTankReportAbstract = async () => {
try {
const res = await getThinkTankReportAbstract(router.currentRoute._value.params.id);
console.log("内容摘要", res);
if (res.code === 200 && res.data) {
box1Data.value = res.data;
}
} catch (error) {
console.error("获取内容摘要error", error);
}
};
// 涉及科技领域
const areaList = ref([]);
const activeArea = ref(6);
const handleClickArea = area => {
activeArea.value = area;
handleGetThinkTankReportIndustryCloud();
};
const box2Data = ref([
// {
// name: "通用人工智能",
// value: 100
// },
// {
// name: "AI芯片",
// value: 66
// },
// {
// name: "计算能力又是",
// value: 72
// },
// {
// name: "基准测试",
// value: 88
// },
// {
// name: "出口管制",
// value: 78
// },
// {
// name: "军事AI",
// value: 85
// },
// {
// name: "生态系统",
// value: 88
// },
// {
// name: "模型能力",
// value: 89
// }
]);
// 报告关键词云
const box5Data = ref([]);
const hasBox5ChartData = computed(() => Array.isArray(box5Data.value) && box5Data.value.length > 0);
/** 词云子组件不 watch 数据,每次接口成功有数据时递增 key,强制重新挂载以触发 onMounted */
const box5WordCloudKey = ref(0);
//获取科技领域词云
const handleGetThinkTankReportIndustryCloud = async () => {
try {
const params = {
id: router.currentRoute._value.params.id
// industryId: activeArea.value
};
const res = await getThinkTankReportIndustryCloud(params);
console.log("科技领域词云", res);
if (res.code === 200 && res.data) {
const data = (res.data || []).map(item => ({
name: item.clause,
value: item.count
}));
// 该接口数据用于「报告关键词云」
box5Data.value = data;
if (data.length) {
box5WordCloudKey.value += 1;
}
// 刷新后默认展开 AI:数据就绪即触发解读
if (isShowAiContentBox5.value) {
fetchBox5ChartInterpretation();
}
} else {
box5Data.value = [];
}
} catch (error) {
console.error("获取科技领域词云error", error);
box5Data.value = [];
}
};
//涉及科技领域
const handleGetThinkTankReportIndustry = async () => {
try {
const res = await getThinkTankReportIndustry(router.currentRoute._value.params.id);
console.log("涉及科技领域", res);
if (res.code === 200 && res.data) {
areaList.value = res.data;
}
} catch (error) {
console.error("获取涉及科技领域error", error);
}
};
// 主要观点
const majorOpinions = ref([
{
id: 1,
title: "我是示例标题",
desc: "我是示例内容",
tagList: [
{
name: "关税",
status: 2
},
{
name: "跨境电商",
status: 1
}
]
},
{
id: 2,
title: "我是示例标题",
desc: "我是示例内容",
tagList: [
{
name: "私有经济",
status: 2
}
]
}
]);
//处理点击详情页事件
const handleOpenReportOriginal = item => {
const route = router.resolve({
name: "ReportOriginal",
params: {
id: router.currentRoute._value.params.id
},
query: {
currentPage: currentPage.value,
pageSize: pageSize.value,
opinionId: item?.id ?? "",
opinionContent: item?.content ?? ""
}
});
window.open(route.href, "_blank");
};
const tabActiveName = ref("报告分析");
const switchTab = name => {
tabActiveName.value = name;
};
// 处理页码改变事件
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(0);
const handleCurrentChange = page => {
currentPage.value = page;
handleGetThinkTankReportViewpoint();
};
// 获取报告核心论点(支持搜索)
const handleGetThinkTankReportViewpoint = async () => {
try {
const params = {
reportId: router.currentRoute._value.params.id,
currentPage: currentPage.value - 1,
pageSize: pageSize.value,
keyword: (searchOpinions.value || "").trim(),
orgIds: ""
};
const res = await getThinkTankReportViewpoint(params);
console.log("核心论点", res.data);
if (res.code === 200 && res.data) {
const nextOpinions = res.data.content || [];
majorOpinions.value = nextOpinions;
total.value = res.data.totalElements || 0;
// 默认:第一条展开,其余关闭
const nextExpandedKeys = new Set();
if (Array.isArray(nextOpinions) && nextOpinions.length > 0) {
nextExpandedKeys.add(getOpinionExpandKey(nextOpinions[0], 0));
}
expandedOpinionKeys.value = nextExpandedKeys;
}
} catch (error) {
console.error("获取主要观点error", error);
}
};
// 获取图表分析内容
const box3AnalysisContent = ref("");
const handleGetBox3AnalysisContent = async textJson => {
const params = {
text: textJson
};
const res = await getChartAnalysis(params);
console.log("图表解析内容", res);
};
const getInterpretationTextFromChartResponse = (res) => {
const list = res?.data;
const first = Array.isArray(list) ? list[0] : null;
return (
first?.["解读"] ||
first?.["interpretation"] ||
first?.["analysis"] ||
first?.["content"] ||
""
);
};
const appendAiInterpretationChunk = (targetRef, chunk, loadingText = "解读生成中…") => {
if (!chunk) {
return;
}
const current = String(targetRef.value || "");
const base = current === loadingText ? "" : current;
targetRef.value = base + String(chunk);
};
const fetchBox5ChartInterpretation = async () => {
const list = Array.isArray(box5Data.value) ? box5Data.value : [];
if (!list.length) {
aiContentBox5.value = "暂无图表数据";
return;
}
const hasValidContent =
aiContentBox5.value &&
aiContentBox5.value !== "解读生成中…" &&
aiContentBox5.value !== "解读加载失败" &&
aiContentBox5.value !== "暂无图表数据";
if (hasValidContent || isBox5InterpretLoading.value) {
return;
}
isBox5InterpretLoading.value = true;
aiContentBox5.value = "解读生成中…";
const chartPayload = {
type: "词云图",
name: "报告关键词云",
data: list.map((item) => ({
name: item.name,
value: item.value
}))
};
try {
const res = await getChartAnalysis(
{ text: JSON.stringify(chartPayload) },
{
onChunk: chunk => {
appendAiInterpretationChunk(aiContentBox5, chunk);
}
}
);
const text = getInterpretationTextFromChartResponse(res);
aiContentBox5.value = text || aiContentBox5.value || "未返回有效解读内容";
} catch (error) {
console.error("报告关键词云图表解读请求失败", error);
aiContentBox5.value = "解读加载失败";
} finally {
isBox5InterpretLoading.value = false;
}
};
onMounted(() => {
handleGetThinkTankReportAbstract();
handleGetThinkTankReportViewpoint();
handleGetThinkTankReportIndustry();
handleGetThinkTankReportIndustryCloud();
});
</script>
<style lang="scss" scoped>
.wrap {
position: absolute;
inset: 0;
box-sizing: border-box;
width: 100%;
overflow: hidden;
.scroll-inner {
box-sizing: border-box;
width: 100%;
height: 100%;
min-height: 0;
padding-bottom: 16px;
overflow-x: hidden;
overflow-y: auto;
scrollbar-gutter: stable;
scrollbar-width: thin;
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-thumb {
border-radius: 4px;
}
}
.header {
width: 100%;
min-height: 94px;
box-sizing: border-box;
border-bottom: 1px solid rgba(234, 236, 238, 1);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 1);
position: sticky;
top: 0;
z-index: 99999;
overflow: hidden;
.header-top {
margin: 0 auto;
margin-top: 20px;
width: 1600px;
display: flex;
justify-content: space-between;
.header-top-left {
display: flex;
img {
width: 54px;
height: 54px;
}
.title {
margin-left: 20px;
font-family: "Source Han Sans CN";
font-weight: 700;
font-size: 20px;
line-height: 26px;
letter-spacing: 0;
text-align: left;
color: rgb(59, 65, 75);
}
.en-title {
margin-left: 20px;
margin-top: 4px;
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0;
text-align: left;
color: rgb(95, 101, 108);
}
}
.header-top-right {
display: flex;
flex-direction: column;
text-align: right;
align-items: flex-end;
.image-name-box {
width: 118px;
height: 24px;
gap: 6px;
text-align: right;
display: flex;
justify-content: flex-end;
.name {
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: right;
}
.image {
width: 16px;
height: 16px;
margin-top: 5px;
img {
width: 100%;
height: 100%;
}
}
}
.tag-box {
margin-top: 7px;
display: flex;
gap: 8px;
}
}
}
}
.bottom-row {
flex-direction: row;
justify-content: center;
display: flex;
gap: 16px;
margin-bottom: 70px;
.left {
gap: 16px;
display: flex;
flex-direction: column;
.box1 {
width: 480px;
height: 415px;
margin-top: 17px;
.box1-main {
width: 480px;
height: 361px;
padding: 24px 24px 65px 24px;
display: flex;
flex-direction: column;
box-sizing: border-box;
overflow: hidden;
position: relative;
.box5Chart {
width: 100%;
height: 100%;
margin: 0 auto;
overflow: hidden;
}
.box1-footer {
display: flex;
justify-content: space-between;
align-items: center;
position: absolute;
bottom: 20px;
left: 32px;
}
.ai-wrap {
position: absolute;
bottom: 18px;
right: 0;
cursor: pointer;
}
.ai-content {
position: absolute;
bottom: 0;
right: 0;
min-width: 480px;
min-height: 156px;
}
}
}
.box2 {
width: 480px;
// border: 1px solid rgba(234, 236, 238, 1);
// border-radius: 10px;
// box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
// background: rgba(255, 255, 255, 1);
.box2-main {
width: 436px;
margin-top: 5px;
margin-left: 23px;
.box2-item {
height: 103px;
width: 100%;
border-bottom: 1px solid rgba(234, 236, 238, 1);
border-top: 1px solid rgba(234, 236, 238, 1);
.box2-item-content {
width: 100%;
height: 90px;
margin-top: 7px;
display: flex;
.left {
width: 56px;
height: 74px;
margin-top: 8px;
img {
width: 100%;
height: 100%;
}
}
.right-content {
margin-left: 13px;
width: 365px;
height: 76px;
margin-top: 7px;
.report-title {
height: 48px;
font-family: "Source Han Sans CN";
font-weight: 700;
font-size: 16px;
line-height: 24px;
letter-spacing: 0;
text-align: left;
/* 👇 下面是 两行文本超出省略 核心代码 */
display: -webkit-box;
-webkit-line-clamp: 2;
/* 限制显示 2 行 */
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
}
.report-footer {
margin-top: 4px;
height: 22px;
justify-content: space-between;
display: flex;
.report-time {
height: 22px;
width: 97px;
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 14px;
line-height: 22px;
letter-spacing: 0;
text-align: left;
color: rgb(95, 101, 108);
}
.report-footer-right {
height: 22px;
display: flex;
gap: 6px;
.footer-image {
width: 16px;
height: 16px;
margin-top: 3px;
img {
width: 100%;
height: 100%;
}
}
.think-name {
height: 22px;
}
}
}
}
}
}
}
.box2-btn {
margin-top: 16px;
margin-bottom: 21px;
margin-left: 23px;
width: 436px;
height: 36px;
background-color: rgb(5, 95, 194);
border-radius: 6px;
display: flex;
.btn-text {
color: rgb(255, 255, 255);
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 16px;
line-height: 22px;
letter-spacing: 0;
margin-left: 120px;
margin-top: 7px;
}
.btn-image {
width: 13px;
height: 8px;
margin-left: 8px;
display: inline-block;
margin-top: 14px;
img {
width: 100%;
height: 100%;
display: block;
}
}
}
}
}
.right {
margin-top: 17px;
gap: 16px;
display: flex;
flex-direction: column;
.box3 {
width: 1103px;
// border: 1px solid rgba(234, 236, 238, 1);
// border-radius: 10px;
// box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
// background: rgba(255, 255, 255, 1);
.box3-main {
width: 1058px;
margin-top: 3px;
margin-left: 22px;
padding-bottom: 22px;
:deep(.summary-main) {
margin-bottom: 25px;
font-family: "Source Han Sans CN";
font-weight: 400;
/* Regular 常规 */
font-size: 16px;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
color: rgb(59, 65, 75);
/* 两端对齐 */
}
.box3-top {
width: 1058px;
height: 48px;
background: linear-gradient(rgb(137, 193, 255, 0.1), rgb(255, 255, 255));
display: flex;
.top-title {
width: 1010px;
height: 32px;
margin-left: 24px;
margin-top: 16px;
.title-image {
width: 199px;
height: 32px;
img {
width: 100%;
height: 100%;
}
}
}
}
.box3-text {
width: 1006px;
margin-top: 24px;
margin-left: 26px;
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 16px;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
text-justify: inter-ideograph;
}
}
}
.box4 {
width: 1103px;
height: auto;
// border: 1px solid rgba(234, 236, 238, 1);
// border-radius: 10px;
// box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
// background: rgba(255, 255, 255, 1);
position: relative;
.box4-main {
width: 1103px;
padding: 5px 24px 21px 28px;
.text {
font-family: "Source Han Sans CN";
font-weight: 400;
/* Regular */
font-size: 16px;
line-height: 30px;
letter-spacing: 0px;
text-align: left;
color: rgb(59, 65, 75);
}
}
}
.box5 {
width: 1103px;
height: auto;
position: relative;
.box5-main {
width: 1103px;
padding: 3px 18px 21px 20px;
.box5-main-item-box {
width: 1065px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px 0;
.item {
width: 532.5px;
height: 49px;
display: flex;
flex-direction: row;
.item-left {
width: 42px;
height: 42px;
border-radius: 50%;
margin-top: 3px;
margin-left: 3px;
img {
width: 100%;
height: 100%;
display: block;
border-radius: 50%;
/* 图片也要加,不然会漏出直角 */
object-fit: cover;
/* 防止图片变形 */
}
}
.item-right {
height: 49px;
margin-left: 11px;
display: flex;
flex-direction: column;
.item-name {
height: 24px;
margin-top: 1px;
font-family: "Source Han Sans CN";
font-weight: 700;
/* Bold 粗体 */
font-size: 16px;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
color: rgb(59, 65, 75);
}
.item-position {
height: 22px;
font-family: "Source Han Sans CN";
font-weight: 400;
/* Regular */
font-size: 14px;
line-height: 22px;
letter-spacing: 0;
text-align: left;
color: rgb(95, 101, 108);
}
}
}
}
}
}
}
}
}
:deep(.analysis-box-wrapper .wrapper-header) {
height: 54px !important;
display: flex;
align-items: center;
.header-title>div {
line-height: 54px;
}
}
</style>
......@@ -1221,7 +1221,7 @@ function mapPolicyRowToView(row) {
const toDetail = item => {
window.sessionStorage.setItem("curTabName", item.contentZh ?? item.content ?? "");
const route = router.resolve({
name: "ReportOriginal",
name: "ReportDetail",
params: {
id: item.reportId
}
......
......@@ -120,7 +120,7 @@ const handleOpenReportOriginal = (item) => {
const reportId = item?.reportId || item?.report_id || item?.id
if (!reportId) return
const route = router.resolve({
name: "ReportOriginal",
name: "ReportDetail",
params: { id: String(reportId) }
})
window.open(route.href, "_blank")
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论