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

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

feat:合作限制概览页详情页功能以及样式开发,智库折线图横坐标样式修改,增加政策及多智库分析观点标题跳转详情页功能,基本完成智库调查与国会听证会前端页面 查看合并请求 !278
流水线 #209 已通过 于阶段
in 1 分 25 秒
......@@ -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')
......@@ -40,6 +42,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",
......
......@@ -25,8 +25,8 @@
</div>
</div>
</div> -->
<NewsList :newsList="leftList" @item-click="handleToNewsDetail" @more-click="handleToMoreNews" img="image"
title="title" content="content" from="from" />
<NewsList :newsList="leftList" @item-click="item => gotoNewsDetail(item.id)" @more-click="handleToMoreNews"
img="image" title="title" content="content" from="from" />
<MessageBubble :messageList="rightList" imageUrl="personImage" @more-click="handleToSocialDetail"
@person-click="handleToSocialDetail" name="name" content="content" source="orgName" image-url="image" />
<!-- <div class="right">
......@@ -60,6 +60,8 @@ import title03 from './assets/title03.png'
import title01bg from './assets/title01bg.png'
import title02bg from './assets/title02bg.png'
import title03bg from './assets/title03bg.png'
import { useGotoNewsDetail } from '@/router/modules/news';
const gotoNewsDetail = useGotoNewsDetail()
// 合作限制-查询社交媒体接口
const getCoopRestrictionSocialData = async () => {
......
......@@ -12,7 +12,7 @@
<el-carousel ref="carouselRef" height="412px" direction="horizontal" :autoplay="true" :interval="5000"
arrow="never" indicator-position="none" @change="handleCarouselChange">
<el-carousel-item v-for="(item, index) in coopRestrictionTrends" :key="item.ID || index">
<div class="carousel-item-content">
<div class="left-center">
<img :src="item.IMAGEURL || defaultImg" alt="" />
<div class="left-center-main">
......@@ -44,7 +44,9 @@
</li>
</ul>
</div>
</div>
<div class="left-center-type" v-if="item.type">{{ item.type }}</div>
<!-- <div class="left-center-title">{{ item.LIMITTYPE }}</div> -->
</div>
<div class="left-bottom">
......@@ -55,7 +57,7 @@
{{ item.INTRODUCTION || "暂无内容摘要" }}
</div>
</div>
</div>
</el-carousel-item>
<!-- 无数据时的占位展示 -->
......@@ -108,6 +110,7 @@
查看更多
</div>
</div> -->
<RiskSignal :list="riskSignals" @more-click="handleToMoreRiskSignal" postDate="time" name="content"
riskLevel="title" @item-click="handleClickToDetail" />
</div>
......@@ -233,7 +236,7 @@ onMounted(() => {
.left {
width: 1064px;
height: 460px;
height: 450px;
margin-right: 16px;
border-radius: 10px;
background-color: #fff;
......@@ -330,6 +333,7 @@ onMounted(() => {
.left-center-main {
width: 439px;
height: 175px;
position: relative;
.left-center-main-title {
margin-left: 19px;
......@@ -420,6 +424,29 @@ onMounted(() => {
}
}
}
}
.left-center-type {
position: absolute;
top: 0;
right: 0;
height: 32px;
font-family: "Source Han Sans CN";
font-weight: 700;
font-size: 18px;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
color: rgb(5, 95, 194);
background-color: rgb(231, 243, 255);
align-items: center;
border-radius: 4px;
padding-left: 8px;
padding-right: 8px;
padding-top: 3px;
padding-bottom: 5px;
}
.left-center-title {
......@@ -439,7 +466,7 @@ onMounted(() => {
}
.left-bottom {
margin: 17px 0 0 62px;
margin: 17px 0 0 59px;
ul {
list-style-position: inside;
......
......@@ -8,8 +8,26 @@
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="left-main">
<div class="left-main-echarts" ref="leftChartRef"></div>
<div class="left-main" :class="{ 'left-main--empty': !hasLeftChartData }">
<template v-if="!hasLeftChartData">
<el-empty class="datasub-left-empty" description="暂无数据" :image-size="100" />
</template>
<template v-else>
<div class="left-chart-row">
<div ref="leftChartRef" class="left-main-echarts"></div>
</div>
<div class="source">
<TipTab :text="COOP_LEFT_TIP_TEXT" />
</div>
<div class="chart-box">
<div v-if="!isShowAiLeft" class="btn-box" @mouseenter="handleSwitchAiLeft(true)">
<AiButton />
</div>
<div v-if="isShowAiLeft" class="content-box" @mouseleave="handleSwitchAiLeft(false)">
<AiPane :aiContent="aiContentLeft" />
</div>
</div>
</template>
</div>
</div>
<div class="right">
......@@ -20,19 +38,75 @@
<el-option v-for="item in options1" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="right-main">
<div class="right-main-echarts" ref="rightChartRef"></div>
<div class="right-main" :class="{ 'right-main--empty': !hasRightChartData }">
<template v-if="!hasRightChartData">
<el-empty class="datasub-right-empty" description="暂无数据" :image-size="100" />
</template>
<template v-else>
<div class="right-chart-row">
<div ref="rightChartRef" class="right-main-echarts"></div>
</div>
<div class="source">
<TipTab :text="COOP_RIGHT_TIP_TEXT" />
</div>
<div class="chart-box">
<div v-if="!isShowAiRight" class="btn-box" @mouseenter="handleSwitchAiRight(true)">
<AiButton />
</div>
<div v-if="isShowAiRight" class="content-box" @mouseleave="handleSwitchAiRight(false)">
<AiPane :aiContent="aiContentRight" />
</div>
</div>
</template>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, watch } from "vue";
import { ref, onMounted, onBeforeUnmount, watch, nextTick, computed } from "vue";
import * as echarts from "echarts";
import { getCoopRestrictionCompare, getCoopRestrictionDomain } from "@/api/coopRestriction/coopRestriction";
import { getChartAnalysis } from "@/api/aiAnalysis/index";
import TipTab from "@/views/thinkTank/TipTab/index.vue";
import AiButton from "@/components/base/Ai/AiButton/index.vue";
import AiPane from "@/components/base/Ai/AiPane/index.vue";
const COOP_LEFT_TIP_TEXT = "各类型合作限制政策对比,数据来源:美对华科技合作限制信息平台";
const COOP_RIGHT_TIP_TEXT = "各领域规则分布情况,数据来源:美对华科技合作限制信息平台";
const value = ref(10);
const value1 = ref("2025");
const options = [
{ value: 1, label: "近一年" },
{ value: 2, label: "近两年" },
{ value: 3, label: "近三年" },
{ value: 4, label: "近四年" },
{ value: 5, label: "近五年" },
{ value: 10, label: "近十年" },
{ value: 15, label: "近十五年" },
{ value: 20, label: "近二十年" }
];
const options1 = [
{ value: "2026", label: "2026年" },
{ value: "2025", label: "2025年" },
{ value: "2024", label: "2024年" },
{ value: "2023", label: "2023年" },
{ value: "2022", label: "2022年" },
{ value: "2021", label: "2021年" },
{ value: "2020", label: "2020年" },
{ value: "2019", label: "2019年" },
{ value: "2018", label: "2018年" },
{ value: "2017", label: "2017年" },
{ value: "2016", label: "2016年" },
{ value: "2015", label: "2015年" },
{ value: "2014", label: "2014年" },
{ value: "2013", label: "2013年" },
{ value: "2012", label: "2012年" },
{ value: "2011", label: "2011年" },
{ value: "2010", label: "2010年" }
];
// 合作限制-各领域规则分布情况接口
const coopRestrictionDomain = ref([]);
const getCoopRestrictionDomainData = async () => {
try {
......@@ -41,14 +115,19 @@ const getCoopRestrictionDomainData = async () => {
});
if (res && res.code === 200) {
coopRestrictionDomain.value = res.data || [];
} else {
// 接口返回 500/非 200:清空,避免继续展示上一次的图表数据
coopRestrictionDomain.value = [];
aiContentRight.value = "";
}
} catch (error) {
console.error("获取合作限制各领域规则分布情况数据失败:", error);
// 请求失败同样清空,避免展示旧图表/旧 AI
coopRestrictionDomain.value = [];
aiContentRight.value = "";
}
};
// 合作限制-各类型合作限制政策对比接口
const coopRestrictionCompare = ref([]);
const getCoopRestrictionCompareData = async () => {
try {
......@@ -57,130 +136,252 @@ const getCoopRestrictionCompareData = async () => {
});
if (res && res.code === 200) {
coopRestrictionCompare.value = res.data || [];
} else {
// 接口返回 500/非 200:清空,避免继续展示上一次的图表数据
coopRestrictionCompare.value = [];
aiContentLeft.value = "";
}
} catch (error) {
console.error("获取合作限制各类型合作限制政策对比数据失败:", error);
// 请求失败同样清空,避免展示旧图表/旧 AI
coopRestrictionCompare.value = [];
aiContentLeft.value = "";
}
};
watch(() => coopRestrictionCompare.value, () => {
initLeftChart();
}, { deep: true });
watch(() => coopRestrictionDomain.value, () => {
initRightChart();
}, { deep: true });
const hasLeftChartData = computed(() => {
const list = coopRestrictionCompare.value;
return Array.isArray(list) && list.length > 0;
});
const value = ref(10);
const value1 = ref("2026");
const options = [
{ value: 1, label: "近一年" },
{ value: 2, label: "近两年" },
{ value: 3, label: "近三年" },
{ value: 4, label: "近四年" },
{ value: 5, label: "近五年" },
{ value: 10, label: "近十年" },
{ value: 15, label: "近十五年" },
{ value: 20, label: "近二十年" }
];
const options1 = [
{
value: "2026",
label: "2026年"
},
{
value: "2025",
label: "2025年"
},
{
value: "2024",
label: "2024年"
},
{
value: "2023",
label: "2023年"
},
{
value: "2022",
label: "2022年"
},
{
value: "2021",
label: "2021年"
},
{
value: "2020",
label: "2020年"
},
{
value: "2019",
label: "2019年"
},
{
value: "2018",
label: "2018年"
},
{
value: "2017",
label: "2017年"
},
{
value: "2016",
label: "2016年"
},
{
value: "2015",
label: "2015年"
},
{
value: "2014",
label: "2014年"
},
{
value: "2013",
label: "2013年"
},
{
value: "2012",
label: "2012年"
},
{
value: "2011",
label: "2011年"
},
{
value: "2010",
label: "2010年"
}
];
const hasRightChartData = computed(() => {
const list = coopRestrictionDomain.value;
return Array.isArray(list) && list.length > 0;
});
const leftChartRef = ref(null);
let leftChart;
const rightChartRef = ref(null);
let rightChart;
const isShowAiLeft = ref(true);
const aiContentLeft = ref("");
const isLeftInterpretLoading = ref(false);
const isShowAiRight = ref(true);
const aiContentRight = ref("");
const isRightInterpretLoading = ref(false);
const handleSwitchAiLeft = (val) => {
isShowAiLeft.value = val;
if (val) {
fetchLeftInterpretation();
}
};
const handleSwitchAiRight = (val) => {
isShowAiRight.value = val;
if (val) {
fetchRightInterpretation();
}
};
/** 兼容 getChartAnalysis 返回对象:从 data[0] 提取「解读」文本 */
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 buildLeftChartPayload = () => {
const rawData = coopRestrictionCompare.value || [];
if (!rawData.length) {
return null;
}
const yearsSet = new Set();
const typesSet = new Set();
rawData.forEach((item) => {
yearsSet.add(item.LIMITDATE);
typesSet.add(item.TYPENAME);
});
const finalYears = Array.from(yearsSet).sort();
const types = Array.from(typesSet);
if (!finalYears.length || !types.length) {
return null;
}
const data = finalYears.map((year) => {
const point = { period: String(year) };
types.forEach((type) => {
const found = rawData.find((item) => item.TYPENAME === type && item.LIMITDATE === year);
point[type] = found ? found.TYPECOUNT : 0;
});
return point;
});
return {
type: "折线图",
name: "各类型合作限制政策对比",
data
};
};
const fetchLeftInterpretation = async () => {
const payload = buildLeftChartPayload();
if (!payload) {
aiContentLeft.value = "暂无图表数据";
return;
}
const hasValidContent =
aiContentLeft.value &&
aiContentLeft.value !== "解读生成中…" &&
aiContentLeft.value !== "解读加载失败" &&
aiContentLeft.value !== "暂无图表数据";
if (hasValidContent || isLeftInterpretLoading.value) {
return;
}
isLeftInterpretLoading.value = true;
aiContentLeft.value = "解读生成中…";
try {
const res = await getChartAnalysis(
{ text: JSON.stringify(payload) },
{
onChunk: (chunk) => {
// 与智库概览「数量变化趋势」一致:按 chunk 增量拼接展示
appendAiInterpretationChunk(aiContentLeft, chunk);
}
}
);
const text = getInterpretationTextFromChartResponse(res);
// 与智库概览一致:优先用最终「解读」收口;否则保留已拼接内容
aiContentLeft.value = text || aiContentLeft.value || "未返回有效解读内容";
} catch (error) {
console.error("合作限制政策对比图表解读请求失败", error);
aiContentLeft.value = "解读加载失败";
} finally {
isLeftInterpretLoading.value = false;
}
};
/** 雷达图解读入参 */
const buildRightChartPayload = () => {
const rawData = coopRestrictionDomain.value || [];
if (!rawData.length) {
return null;
}
const domainsSet = new Set();
const typesSet = new Set();
rawData.forEach((item) => {
domainsSet.add(item.AREA);
typesSet.add(item.COOPERTYPE);
});
const domains = Array.from(domainsSet);
const types = Array.from(typesSet);
if (!domains.length || !types.length) {
return null;
}
const series = types.map((type) => ({
name: type,
values: domains.map((domain) => {
const found = rawData.find((item) => item.COOPERTYPE === type && item.AREA === domain);
return {
domain,
value: found ? found.COOPERTYPECOUNT : 0
};
})
}));
return {
type: "雷达图",
name: "各领域规则分布情况",
year: value1.value,
data: series
};
};
const fetchRightInterpretation = async () => {
const payload = buildRightChartPayload();
if (!payload) {
aiContentRight.value = "";
return;
}
const hasValidContent =
aiContentRight.value &&
aiContentRight.value !== "解读生成中…" &&
aiContentRight.value !== "解读加载失败" &&
aiContentRight.value !== "暂无图表数据";
if (hasValidContent || isRightInterpretLoading.value) {
return;
}
isRightInterpretLoading.value = true;
aiContentRight.value = "解读生成中…";
try {
const res = await getChartAnalysis(
{ text: JSON.stringify(payload) },
{
onChunk: (chunk) => {
appendAiInterpretationChunk(aiContentRight, chunk);
}
}
);
const text = getInterpretationTextFromChartResponse(res);
aiContentRight.value = text || aiContentRight.value || "未返回有效解读内容";
} catch (error) {
console.error("合作限制领域分布图表解读请求失败", error);
aiContentRight.value = "解读加载失败";
} finally {
isRightInterpretLoading.value = false;
}
};
const initLeftChart = () => {
if (!leftChartRef.value) return;
if (leftChart) leftChart.dispose();
if (!hasLeftChartData.value) {
if (leftChart) {
leftChart.dispose();
leftChart = null;
}
return;
}
if (!leftChartRef.value) {
return;
}
if (leftChart) {
leftChart.dispose();
}
leftChart = echarts.init(leftChartRef.value);
// 处理动态数据
const rawData = coopRestrictionCompare.value;
const yearsSet = new Set();
const typesSet = new Set();
rawData.forEach(item => {
rawData.forEach((item) => {
yearsSet.add(item.LIMITDATE);
typesSet.add(item.TYPENAME);
});
const years = Array.from(yearsSet).sort();
const types = Array.from(typesSet);
const finalYears = years.length > 0 ? years : [];
// 如果没有数据,给一些默认年份展示
const finalYears = years.length > 0 ? years : ["2020", "2021", "2022", "2023", "2024", "2025"];
if (!finalYears.length || !types.length) {
leftChart.dispose();
leftChart = null;
return;
}
// 定义颜色配置,确保线条色与阴影色对应
const colorMap = [
{ line: "#2f7ae5", start: "rgba(47, 122, 229, 0.3)", end: "rgba(47, 122, 229, 0.05)" },
{ line: "#29c0b1", start: "rgba(41, 192, 177, 0.3)", end: "rgba(41, 192, 177, 0.05)" },
......@@ -189,39 +390,65 @@ const initLeftChart = () => {
];
const series = types.map((type, index) => {
const data = finalYears.map(year => {
const found = rawData.find(item => item.TYPENAME === type && item.LIMITDATE === year);
return found ? found.TYPECOUNT : 0;
const data = finalYears.map((year) => {
const found = rawData.find((item) => item.TYPENAME === type && item.LIMITDATE === year);
const v = found ? found.TYPECOUNT : 0;
// 确保是整数,避免 ECharts 自动推断出小数坐标/刻度
return Math.round(Number(v) || 0);
});
// 根据索引获取颜色配置,超出范围则循环使用
const colorConfig = colorMap[index % colorMap.length];
return {
name: type,
type: "line",
smooth: false,
data: data,
smooth: true,
data,
symbol: "circle",
symbolSize: 6,
// 点应覆盖在折线之上,避免视觉上“穿过”节点圆
z: 3,
lineStyle: {
color: colorConfig.line,
width: 2
},
itemStyle: {
color: colorConfig.line
color: "#fff",
borderColor: colorConfig.line,
borderWidth: 2
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: colorConfig.start },
{ offset: 1, color: colorConfig.end }
])
},
axisLine: {
lineStyle: {
color: 'rgba(231, 241, 255, 1)'
}
},
};
});
const legendData = types.map((type, index) => ({
name: type,
itemStyle: {
// 图例需要实心圆:独立于 series 的空心点样式
color: colorMap[index % colorMap.length].line,
borderColor: colorMap[index % colorMap.length].line,
borderWidth: 0
}
}));
const maxYValue = Math.max(
0,
...series.flatMap((s) => (Array.isArray(s.data) ? s.data : []))
);
const option = {
color: colorMap.map(c => c.line),
color: colorMap.map((c) => c.line),
grid: { left: 40, right: 24, top: 46, bottom: 36 },
tooltip: { trigger: "axis", axisPointer: { type: "line" } },
legend: {
......@@ -237,14 +464,29 @@ const initLeftChart = () => {
fontWeight: 400,
lineHeight: 24
},
data: types
data: legendData
},
xAxis: {
type: "category",
boundaryGap: false,
data: finalYears,
axisLine: { show: false },
axisTick: { show: false },
axisLine: {
show: true,
lineStyle: {
color: "rgba(231, 241, 255, 1)"
}
},
axisTick: {
show: true,
// 让刻度和 label 对齐,显示短直线
alignWithLabel: true,
length: 6,
length2: 0,
lineStyle: {
color: "rgba(231, 241, 255, 1)",
type: "solid"
}
},
axisLabel: {
color: "rgba(132, 136, 142, 1)",
fontSize: 14,
......@@ -256,53 +498,73 @@ const initLeftChart = () => {
yAxis: {
type: "value",
min: 0,
splitLine: { show: true, lineStyle: { color: "#e6e6e6", type: "dashed" } },
axisLine: { show: false },
axisTick: { show: false },
// 强制整数刻度:0/1/2/3...
interval: 1,
max: Math.ceil(maxYValue),
name: "数量",
nameLocation: "end",
nameGap: 20,
nameRotate: 0,
nameTextStyle: {
color: "rgba(170, 173, 177, 1)",
fontFamily: "Microsoft YaHei",
fontWeight: 400,
fontSize: 14,
lineHeight: 22,
letterSpacing: 0,
align: "right",
verticalAlign: "bottom"
},
axisLabel: {
color: "rgba(132, 136, 142, 1)",
fontSize: 14,
fontFamily: "Microsoft YaHei",
fontWeight: 400,
lineHeight: 22
}
lineHeight: 22,
formatter: (val) => String(Math.round(Number(val) || 0))
},
splitLine: {
show: true,
lineStyle: { color: "rgba(231, 241, 255, 1)", type: "dashed", width: 1 }
},
series: series
axisLine: { show: false },
axisTick: { show: false }
},
series
};
leftChart.setOption(option);
window.addEventListener("resize", handleResize);
};
const handleResize = () => { if (leftChart) leftChart.resize(); if (rightChart) rightChart.resize(); };
const handleResize = () => {
if (leftChart) {
leftChart.resize();
}
if (rightChart) {
rightChart.resize();
}
};
const initRightChart = () => {
if (!rightChartRef.value) return;
if (rightChart) rightChart.dispose();
rightChart = echarts.init(rightChartRef.value);
const rawData = coopRestrictionDomain.value;
// 数据为空处理
if (!rawData || rawData.length === 0) {
rightChart.setOption({
graphic: {
type: 'text',
left: 'center',
top: 'middle',
style: {
text: '暂无数据',
fill: '#999',
fontSize: 16
if (!hasRightChartData.value) {
if (rightChart) {
rightChart.dispose();
rightChart = null;
}
return;
}
});
if (!rightChartRef.value) {
return;
}
if (rightChart) {
rightChart.dispose();
}
rightChart = echarts.init(rightChartRef.value);
const rawData = coopRestrictionDomain.value;
// 1. 动态提取所有领域(雷达图的维度)
const domainsSet = new Set();
const typesSet = new Set();
rawData.forEach(item => {
rawData.forEach((item) => {
domainsSet.add(item.AREA);
typesSet.add(item.COOPERTYPE);
});
......@@ -310,18 +572,16 @@ const initRightChart = () => {
const domains = Array.from(domainsSet);
const types = Array.from(typesSet);
// 2. 构造指示器(Indicator),自动计算 Max 值
const indicators = domains.map(domain => {
const domainData = rawData.filter(item => item.AREA === domain);
const maxVal = Math.max(...domainData.map(d => d.COOPERTYPECOUNT), 5); // 最小给个5
return { name: domain, max: Math.ceil(maxVal * 1.2) }; // 留 20% 的余量
const indicators = domains.map((domain) => {
const domainData = rawData.filter((item) => item.AREA === domain);
const maxVal = Math.max(...domainData.map((d) => d.COOPERTYPECOUNT), 5);
return { name: domain, max: Math.ceil(maxVal * 1.2) };
});
// 3. 构造 Series 数据
const colorMap = ["#2f7ae5", "#29c0b1", "#e45f5f", "#7b5de6"];
const seriesData = types.map((type, index) => {
const dataValues = domains.map(domain => {
const found = rawData.find(item => item.COOPERTYPE === type && item.AREA === domain);
const dataValues = domains.map((domain) => {
const found = rawData.find((item) => item.COOPERTYPE === type && item.AREA === domain);
return found ? found.COOPERTYPECOUNT : 0;
});
......@@ -329,7 +589,8 @@ const initRightChart = () => {
name: type,
value: dataValues,
itemStyle: { color: colorMap[index % colorMap.length] },
areaStyle: { color: colorMap[index % colorMap.length], opacity: 0.1 }
// 不要填充多边形:让雷达图“圆里面是空的”
// areaStyle 不设置(或设为 0)可避免穿透同心圆的填充效果
};
});
......@@ -353,11 +614,7 @@ const initRightChart = () => {
radar: {
center: ["50%", "55%"],
radius: "65%",
indicator: indicators.length > 0 ? indicators : [
{ name: "暂无数据", max: 100 },
{ name: "暂无数据", max: 100 },
{ name: "暂无数据", max: 100 }
],
indicator: indicators,
axisName: {
color: "rgba(132, 136, 142, 1)",
fontSize: 14,
......@@ -366,24 +623,63 @@ const initRightChart = () => {
splitLine: { lineStyle: { color: ["#e6e6e6"] } },
splitArea: { show: false }
},
series: [{
series: [
{
type: "radar",
data: seriesData
}]
data: seriesData,
// 移除雷达图端点圆点(既不显示实心圆,也不显示空心圆)
symbol: "none",
symbolSize: 0,
showSymbol: false
}
]
};
rightChart.setOption(option);
window.addEventListener("resize", handleResize);
};
onMounted(() => {
// 合作限制-各类型合作限制政策对比接口
getCoopRestrictionCompareData();
// 合作限制-各领域规则分布情况接口
getCoopRestrictionDomainData();
watch(
coopRestrictionCompare,
async () => {
aiContentLeft.value = "";
await nextTick();
initLeftChart();
if (isShowAiLeft.value && hasLeftChartData.value) {
fetchLeftInterpretation();
}
},
{ deep: true, immediate: true }
);
watch(
coopRestrictionDomain,
async () => {
aiContentRight.value = "";
await nextTick();
initRightChart();
if (isShowAiRight.value && hasRightChartData.value) {
fetchRightInterpretation();
}
},
{ deep: true, immediate: true }
);
onMounted(async () => {
window.addEventListener("resize", handleResize);
await getCoopRestrictionCompareData();
await getCoopRestrictionDomainData();
});
onBeforeUnmount(() => {
window.removeEventListener("resize", handleResize);
if (leftChart) {
leftChart.dispose();
leftChart = null;
}
if (rightChart) {
rightChart.dispose();
rightChart = null;
}
});
onBeforeUnmount(() => { window.removeEventListener("resize", handleResize); if (leftChart) { leftChart.dispose(); leftChart = null; } if (rightChart) { rightChart.dispose(); rightChart = null; } });
</script>
<style lang="scss" scoped>
......@@ -391,24 +687,27 @@ onBeforeUnmount(() => { window.removeEventListener("resize", handleResize); if (
margin: 0;
padding: 0;
}
.datasub {
width: 1600px;
height: 460px;
display: flex;
justify-content: space-between;
.left {
width: 1063px;
height: 460px;
margin-right: 16px;
border-radius: 10px;
// border: 1px solid rgb(234, 236, 238);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background-color: #fff;
.left-title {
width: 1063px;
height: 48px;
border-bottom: 1px solid rgb(234, 236, 238);
position: relative;
img {
width: 19px;
height: 19px;
......@@ -416,6 +715,7 @@ onBeforeUnmount(() => { window.removeEventListener("resize", handleResize); if (
top: 16px;
left: 21px;
}
.tit {
margin-left: 60px;
height: 48px;
......@@ -426,6 +726,7 @@ onBeforeUnmount(() => { window.removeEventListener("resize", handleResize); if (
line-height: 26px;
color: rgb(5, 95, 194);
}
.select {
width: 150px;
height: 28px;
......@@ -435,28 +736,84 @@ onBeforeUnmount(() => { window.removeEventListener("resize", handleResize); if (
right: 31px;
}
}
.left-main {
width: 1063px;
height: 412px;
padding: 15px 23px 25px 38px;
box-sizing: border-box;
position: relative;
padding: 24px 24px 65px 24px;
&.left-main--empty {
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
:deep(.el-empty__image) {
margin-bottom: 0;
}
}
.datasub-left-empty {
padding: 0;
margin: 0;
}
.left-chart-row {
display: flex;
flex-direction: row;
align-items: flex-start;
width: 100%;
box-sizing: border-box;
}
.left-main-echarts {
width: 1002px;
height: 372px;
width: 1015px;
height: 323px;
}
.source {
position: absolute;
bottom: 21px;
left: 24px;
height: 22px;
display: flex;
}
.chart-box {
position: absolute;
right: 0;
bottom: 18px;
.btn-box {
width: 74px;
height: 28px;
}
.content-box {
width: 1063px;
position: absolute;
right: 0;
bottom: -18px;
}
}
}
}
.right {
width: 521px;
height: 460px;
border-radius: 10px;
// border: 1px solid rgb(234, 236, 238);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background-color: #fff;
.right-title {
width: 521px;
height: 48px;
border-bottom: 1px solid rgb(234, 236, 238);
position: relative;
img {
width: 19px;
height: 19px;
......@@ -464,6 +821,7 @@ onBeforeUnmount(() => { window.removeEventListener("resize", handleResize); if (
top: 16px;
left: 21px;
}
.tit {
margin-left: 60px;
height: 48px;
......@@ -474,6 +832,7 @@ onBeforeUnmount(() => { window.removeEventListener("resize", handleResize); if (
line-height: 26px;
color: rgb(5, 95, 194);
}
.select {
width: 150px;
height: 28px;
......@@ -483,13 +842,65 @@ onBeforeUnmount(() => { window.removeEventListener("resize", handleResize); if (
right: 31px;
}
}
.right-main {
width: 521px;
height: 421px;
padding: 15px 50px 25px 50px;
height: 412px;
box-sizing: border-box;
padding: 24px 24px 64px 24px;
position: relative;
&.right-main--empty {
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
:deep(.el-empty__image) {
margin-bottom: 0;
}
}
.datasub-right-empty {
padding: 0;
margin: 0;
}
.right-chart-row {
display: flex;
width: 100%;
box-sizing: border-box;
}
.right-main-echarts {
width: 420px;
height: 372px;
width: 473px;
height: 324px;
}
.source {
position: absolute;
bottom: 21px;
left: 24px;
height: 22px;
display: flex;
}
.chart-box {
position: absolute;
right: 0;
bottom: 18px;
.btn-box {
width: 74px;
height: 28px;
}
.content-box {
width: 520px;
position: absolute;
right: 0;
bottom: -18px;
}
}
}
}
......
<template>
<div class="reslib-page" ref="reslibContainer">
<div class="nav">
<div
v-for="item in navList"
:key="item.id"
class="nav-item"
:class="{ active: item.id === activeItem }"
@click="activeItem = item.id"
>
<div v-for="item in navList" :key="item.id" class="nav-item" :class="{ active: item.id === activeItem }"
@click="activeItem = item.id">
{{ item.name }}
</div>
</div>
<el-select v-model="value" placeholder="排序方式" class="select">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
<el-select v-model="sortModel" placeholder="发布时间" class="select" :teleported="true" placement="bottom-start"
:popper-options="sortPopperOptions" @change="handleSortChange">
<template #prefix>
<img v-if="sortModel !== true" src="@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image down.png"
class="select-prefix-img" alt="" @click.stop="toggleSortPrefix" />
<img v-else src="@/views/thinkTank/ThinkTankDetail/thinkDynamics/images/image up.png" class="select-prefix-img"
alt="" @click.stop="toggleSortPrefix" />
</template>
<el-option :key="true" label="正序" :value="true" />
<el-option :key="false" label="倒序" :value="false" />
</el-select>
<div class="main">
<div class="left">
<div class="left-ti1"></div>
<div class="left-ti2"></div>
<div class="left-title">数据来源</div>
<div class="left-title ">科技领域</div>
<div class="left-content">
<div v-for="item in dataList" :key="item.id" class="left-item">
<input
type="checkbox"
:value="String(item.id)"
v-model="selectedSources"
/>{{ item.name }}
<div v-for="item in dataList2" :key="item.id" class="left-item">
<input type="checkbox" :value="String(item.id)" v-model="selectedDomains" />{{ item.name }}
</div>
</div>
<div class="left-title cl1">涉及领域</div>
<div class="left-ti2"></div>
<div class="left-title">数据来源</div>
<div class="left-content">
<div v-for="item in dataList2" :key="item.id" class="left-item">
<input
type="checkbox"
:value="String(item.id)"
v-model="selectedDomains"
/>{{ item.name }}
<div v-for="item in dataList" :key="item.id" class="left-item">
<input type="checkbox" :value="String(item.id)" v-model="selectedSources" />{{ item.name }}
</div>
</div>
</div>
......@@ -47,14 +44,16 @@
<div class="right-main">
<div class="main-content">
<div v-for="item in mainDataList" :key="item.id" class="main-item">
<div class="date">{{ item.date }}</div>
<div class="date">{{ formatDateCn(item.date) }}</div>
<img :src="item.img" alt="" class="img" />
<div class="box">
<div class="title" @click="handleClick(item)">{{ item.title }}</div>
<div class="content" @click="handleClick(item)">{{ item.content }}</div>
<div class="domain">
<div v-for="(domain, i) in item.domain" :key="i" class="domain-item">{{ domain }}</div>
<AreaTag v-for="(domain, i) in item.domain" :key="i" " :tagName="domain">
</AreaTag>
</div>
<div class="type" :class="getTypeClass(item.type)">
{{ item.type }}
</div>
......@@ -62,15 +61,9 @@
</div>
</div>
<div class="page">
<div class="count">{{ total }}</div>
<el-pagination
v-model:current-page="currentPage"
:page-size="pageSize"
:total="total"
layout="prev, pager, next"
background
@current-change="handlePageChange"
/>
<div class="count">共 {{ total }} 项调查</div>
<el-pagination v-model:current-page="currentPage" :page-size="pageSize" :total="total"
layout="prev, pager, next" background @current-change="handlePageChange" />
</div>
</div>
</div>
......@@ -79,12 +72,23 @@
</template>
<script setup>
import { ref, onMounted, watch } from "vue";
import { ref, onMounted, watch, computed } from "vue";
import { useRouter } from "vue-router";
import { getCoopRestrictionList } from "@/api/coopRestriction/coopRestriction";
import defaultImg from "../../assets/images/default-icon2.png";
const formatDateCn = (dateStr) => {
const s = String(dateStr || "").trim();
// 兼容 YYYY-MM-DD / YYYY/MM/DD
const m = s.match(/^(\d{4})[-/](\d{1,2})[-/](\d{1,2})/);
if (!m) return s;
const y = m[1];
const month = String(Number(m[2]) || "");
const day = String(Number(m[3]) || "");
return `${y}\n${month}${day}日`;
};
// 合作限制-获取合作限制列表接口
const getMainDataList = async () => {
const params = {
......@@ -102,9 +106,8 @@ const getMainDataList = async () => {
if (activeItem.value !== "0") {
params.type = activeItem.value;
}
if (value.value) {
params.sortOrder = value.value;
}
// 与智库概览一致:null 作为占位但默认按倒序;true=正序,false=倒序
params.sortOrder = sort.value === true ? "asc" : "desc";
try {
console.log('----params getMainDataList', params)
......@@ -153,18 +156,43 @@ const navList = ref([
]);
const activeItem = ref("0");
const value = ref("");
/** null:占位「发布时间」且默认倒序;true 正序;false 倒序(显式),与智库概览一致 */
const sort = ref(null);
const options = [
{
value: "asc",
label: "正序"
const sortPopperOptions = {
modifiers: [
{ name: "preventOverflow", options: { mainAxis: false, altAxis: false } },
{ name: "flip", enabled: false }
]
};
const sortModel = computed({
get() {
return sort.value;
},
{
value: "desc",
label: "倒序"
set(v) {
sort.value = v;
}
});
const handleSortChange = () => {
// 改变排序后从第一页开始
if (currentPage.value === 1) {
getMainDataList();
} else {
currentPage.value = 1;
}
};
const toggleSortPrefix = () => {
sort.value = sort.value === true ? false : true;
// 切换排序后从第一页开始
if (currentPage.value === 1) {
getMainDataList();
} else {
currentPage.value = 1;
}
];
};
const dataList = ref([
{
id: "0",
......@@ -276,7 +304,7 @@ onMounted(() => {
});
watch(
[activeItem, selectedSources, selectedDomains, value],
[activeItem, selectedSources, selectedDomains, sort],
(newVal, oldVal) => {
const [newActive, newSources, newDomains] = newVal;
const [, oldSources, oldDomains] = oldVal;
......@@ -326,12 +354,13 @@ watch(currentPage, () => {
margin: 0;
padding: 0;
}
.reslib-page {
width: 1600px;
min-height: 1565px;
height: auto;
position: relative;
padding-bottom: 50px;
.nav {
width: 808px;
height: 42px;
......@@ -339,6 +368,7 @@ watch(currentPage, () => {
align-items: center;
justify-content: space-between;
margin-bottom: 34px;
.nav-item {
cursor: pointer;
padding: 8px 20px;
......@@ -348,6 +378,7 @@ watch(currentPage, () => {
line-height: 26px;
color: rgb(59, 65, 75);
}
.active {
background-color: rgb(5, 95, 194);
border-radius: 21px;
......@@ -355,25 +386,44 @@ watch(currentPage, () => {
font-weight: 700;
}
}
.select {
width: 128px;
position: absolute;
top: 7px;
right: 0px;
}
.select-prefix-img {
width: 7px;
height: 14px;
margin-right: 6px;
cursor: pointer;
}
/* 下拉项内边距:与智库模块 el-select 视觉一致 */
:deep(.el-select-dropdown__item) {
padding: 0 20px;
}
.main {
width: 1600px;
height: auto;
min-height: 1489px;
display: flex;
margin-bottom: 35px;
.left {
width: 300px;
height: 760px;
width: 360px;
height: 432px;
padding-bottom: 24px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
margin-right: 16px;
border-radius: 10px;
background-color: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
box-shadow: 0px 0px 20px 0px rgba(94, 95, 95, 0.1);
position: relative;
.left-ti1 {
width: 8px;
height: 16px;
......@@ -381,9 +431,10 @@ watch(currentPage, () => {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
position: absolute;
top: 17px;
top: 20px;
left: 0px;
}
.left-ti2 {
width: 8px;
height: 16px;
......@@ -391,47 +442,62 @@ watch(currentPage, () => {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
position: absolute;
top: 207px;
top: 320px;
left: 0px;
}
.left-title {
margin-left: 25px;
color: rgb(5, 95, 194);
margin-top: 16px;
color: var(--color-main-active);
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
font-family: "Source Han Sans CN";
line-height: 24px;
margin-top: 13px;
letter-spacing: 1px;
height: 24px;
}
.left-content {
// width: 109px;
// height: 132px;
margin-left: 25px;
margin-top: 13px;
margin-left: 24px;
margin-top: 12px;
display: grid;
grid-template-columns: repeat(2, 160px);
gap: 8px 4px;
.left-item {
// width: 89px;
height: 30px;
margin-bottom: 4px;
width: 160px;
height: 24px;
margin: 0;
display: flex;
align-items: center;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
font-family: "Source Han Sans CN";
line-height: 24px;
color: rgb(95, 101, 108);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
input[type="checkbox"] {
-webkit-appearance: none;
appearance: none;
width: 14px;
height: 14px;
margin-right: 8px;
flex: 0 0 auto;
border: 1px solid rgb(200, 204, 210);
border-radius: 4px;
background-color: #fff;
vertical-align: middle;
}
input[type="checkbox"]:checked {
background-color: rgb(5, 95, 194);
border-color: rgb(5, 95, 194);
}
input[type="checkbox"]:checked::after {
content: "";
display: block;
......@@ -445,22 +511,23 @@ watch(currentPage, () => {
}
}
}
.cl1 {
margin-top: 21px;
}
}
.right {
width: 1284px;
height: auto;
min-height: 1489px;
width: 1224px;
border-radius: 10px;
background-color: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
.right-title {
width: 1284px;
width: 1224px;
height: 48px;
border-bottom: 1px solid rgb(235, 238, 242);
position: relative;
img {
width: 22px;
height: 19px;
......@@ -468,6 +535,7 @@ watch(currentPage, () => {
top: 15px;
left: 20px;
}
div {
width: 120px;
height: 48px;
......@@ -480,69 +548,89 @@ watch(currentPage, () => {
padding: 11px 0;
}
}
.right-main {
width: 1284px;
height: auto;
min-height: 1441px;
padding: 22px 43px 80px 20px;
width: 1224px;
padding: 12px 0px 80px 0px;
position: relative;
.main-content {
width: 1221px;
height: auto;
min-height: 1345px;
width: 1224px;
padding-left: 28px;
border-bottom: 1px solid rgb(235, 238, 242);
.main-item {
width: 1221px;
height: auto;
min-height: 116px;
margin-bottom: 24px;
width: 1167px;
position: relative;
display: flex;
&::after {
content: "";
position: absolute;
top: 37px;
bottom: -37px;
left: 91px;
left: 108px;
width: 2px;
background-color: rgb(230, 231, 232);
z-index: 1;
}
&:last-child::after {
display: none;
content: "";
position: absolute;
top: 37px;
bottom: -37px;
left: 108px;
width: 2px;
background-color: rgb(230, 231, 232);
z-index: 1;
height: calc(100% - 37px);
}
.date {
flex-shrink: 0;
width: 62px;
width: 80px;
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(5, 95, 194);
text-align: right;
margin-top: 13px;
margin-top: 6px;
white-space: pre-line;
}
.img {
flex-shrink: 0;
width: 24px;
height: 24px;
margin: 13px 21px 0 18px;
margin: 14px 16px 0 16px;
position: relative;
z-index: 100;
}
.box {
flex: 1;
min-height: 91px;
position: relative;
padding-top: 10px;
padding-top: 14px;
.title {
font-size: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(59, 65, 75);
margin-bottom: 8px;
margin-bottom: 9px;
cursor: pointer;
}
.content {
font-size: 16px;
font-weight: 400;
......@@ -552,6 +640,7 @@ watch(currentPage, () => {
margin-bottom: 9px;
cursor: pointer;
}
.type {
padding: 2px 8px;
border-radius: 20px;
......@@ -563,29 +652,37 @@ watch(currentPage, () => {
top: 10px;
right: 0px;
}
.type1 {
background-color: rgba(255, 149, 77, 0.1);
color: rgb(255, 149, 77);
}
.type2 {
background-color: rgba(206, 79, 81, 0.1);
color: rgb(206, 79, 81);
}
.type3 {
background-color: rgba(5, 95, 194, 0.1);
color: rgb(5, 95, 194);
}
.type4 {
background-color: rgba(103, 194, 58, 0.1);
color: rgb(103, 194, 58);
}
.type-default {
background-color: rgba(144, 147, 153, 0.1);
color: rgb(144, 147, 153);
}
.domain {
margin-bottom: 15px;
margin-bottom: 12px;
display: flex;
gap: 8px;
.domain-item {
padding: 2px 8px;
border-radius: 4px;
......@@ -601,8 +698,9 @@ watch(currentPage, () => {
}
}
}
.page {
width: 1221px;
width: 1159px;
height: 40px;
display: flex;
align-items: center;
......@@ -611,6 +709,7 @@ watch(currentPage, () => {
bottom: 20px;
left: 20px;
padding-left: 11px;
.count {
font-size: 16px;
font-weight: 400;
......@@ -618,10 +717,12 @@ watch(currentPage, () => {
line-height: 24px;
color: rgb(59, 65, 75);
}
:deep(.el-pagination) {
display: flex;
align-items: center;
}
:deep(.el-pagination.is-background .el-pager li) {
min-width: 32px;
height: 32px;
......@@ -635,12 +736,14 @@ watch(currentPage, () => {
font-weight: 400;
font-family: "Microsoft YaHei";
}
:deep(.el-pagination.is-background .el-pager li.is-active) {
background-color: #fff;
color: rgba(22, 119, 255, 1);
border-color: rgba(22, 119, 255, 1);
// box-shadow: 0 0 0 2px rgba(47, 122, 229, 0.15) inset;
}
:deep(.el-pagination.is-background .el-pager li.is-ellipsis) {
border: none;
background-color: transparent;
......@@ -648,6 +751,7 @@ watch(currentPage, () => {
min-width: 16px;
margin: 0 6px;
}
:deep(.el-pagination.is-background .btn-prev),
:deep(.el-pagination.is-background .btn-next) {
min-width: 32px;
......@@ -660,6 +764,7 @@ watch(currentPage, () => {
font-family: "Microsoft YaHei";
margin: 0 6px;
}
:deep(.el-pagination.is-background .btn-prev.is-disabled),
:deep(.el-pagination.is-background .btn-next.is-disabled) {
color: rgba(95, 101, 108, 0.45);
......
......@@ -4,9 +4,9 @@
<div class="nav-main">
<img :src="coopData?.IMAGEURL || defaultImg" alt="" />
<div class="content">
<div class="cl1">{{coopData?.LIMITNAMEZH}}</div>
<div class="cl2">{{coopData?.LIMITNAME}}</div>
<div class="cl3">{{coopData?.LIMITDATE}} · {{ coopData?.LIMITORGNAME }}</div>
<div class="cl1">{{ coopData?.LIMITNAMEZH }}</div>
<div class="cl2">{{ coopData?.LIMITNAME }}</div>
<div class="cl3">{{ coopData?.LIMITDATE }} · {{ coopData?.LIMITORGNAME }}</div>
</div>
<div class="btn">
<button class="btn1"><img src="./assets/icon01.png" alt="" />查看原文</button>
......@@ -15,38 +15,34 @@
</div>
</div>
</div>
<div class="title" v-if="coopData?.Relation?.[0]" @click="handleClick">
<span class="title-one">当前合作限制数据已关联至{{coopData.Relation[0]?.TYPE}}</span>
<span class="title-two">{{coopData.Relation[0]?.RELATIONNAME}}{{coopData.Relation[0]?.RELATIONDATE}}</span>
<div class="title" v-if="coopData.Relation?.[0]" @click="handleClick">
<span class="title-one">当前合作限制数据已关联至{{ coopData.Relation[0]?.TYPE }}</span>
<span class="title-two">{{ coopData.Relation[0]?.RELATIONNAME }}{{ coopData.Relation[0]?.RELATIONDATE }}</span>
<img src="./assets/right.png" alt="" />
</div>
<div class="main">
<div class="left">
<!-- 制裁概况 -->
<div class="left-top">
<img class="img1" src="./assets/bluetitle.png" alt="" />
<div class="left-top-title">制裁概况</div>
<img class="img2" src="./assets/下载按钮.png" alt="" />
<img class="img3" src="./assets/收藏按钮.png" alt="" />
<AnalysisBox title="制裁概况" :showAllBtn="true">
<div class="box1-main">
<div class="left-top-content">
<span
>{{ coopData?.INTRODUCTION }}</span
>
<span>{{ coopData?.INTRODUCTION }}</span>
</div>
<div class="left-top-bottom">
<div><span class="tit">限制时间:</span><span class="tit1">{{ coopData?.LIMITDATE }}</span></div>
<div @click="handleClickOnLimitOrg" style="cursor: pointer;"><span class="tit">限制机构:</span><span class="tit1 tit2">{{ coopData?.LIMITORGNAME }} ></span></div>
<div><span class="tit">限制手段:</span><span class="tit1">{{ coopData?.LIMITMEANS }}</span></div>
<div><span class="tit">限制类型:</span><span class="tit1 tit3">{{ coopData?.LIMITTYPE }}</span></div>
<div><span class="tit">限制领域:</span><span class="tit1">{{ coopData?.area }}</span></div>
<div><span class="tit1">限制时间:</span><span class="tit2">{{ coopData?.LIMITDATE }}</span></div>
<div @click="handleClickOnLimitOrg" style="cursor: pointer;"><span class="tit1">限制机构:</span><span
class=" tit3">{{ coopData?.LIMITORGNAME }} ></span></div>
<div><span class="tit1">限制手段:</span><span class="tit2">{{ coopData?.LIMITMEANS }}</span></div>
<div><span class="tit1">限制类型:</span><span class="tit4">{{ coopData?.LIMITTYPE }}</span></div>
<div><span class="tit1">限制领域:</span><span class="tit2">{{ coopData?.area }}</span></div>
</div>
</div>
</AnalysisBox>
</div>
<!-- 相关实体 -->
<div class="left-bottom">
<img class="img1" src="./assets/bluetitle.png" alt="" />
<div class="left-bottom-title">相关实体</div>
<img class="img2" src="./assets/下载按钮.png" alt="" />
<img class="img3" src="./assets/收藏按钮.png" alt="" />
<AnalysisBox title="相关实体" :showAllBtn="true">
<div class="left-bottom-main">
<div v-for="item in coopRelatedData" :key="item.id" class="main-box" @click="handleClickOnEntity(item)">
<img :src="item.img || defaultCom" alt="" />
......@@ -54,6 +50,7 @@
<div class="type">{{ item.type }}</div>
</div>
</div>
</AnalysisBox>
</div>
</div>
<div class="right">
......@@ -75,8 +72,8 @@
<div class="right-bottom">
<img class="img1" src="./assets/bluetitle.png" alt="" />
<div class="right-bottom-title">限制条款</div>
<div class="btn cl1" :class="{'active': active2 === '涉华背景'}" @click="active2 = '涉华背景'">涉华背景</div>
<div class="btn cl2" :class="{'active': active2 === '全部背景'}" @click="active2 = '全部背景'">全部背景</div>
<div class="btn cl1" :class="{ 'active': active2 === '涉华条款' }" @click="active2 = '涉华条款'">涉华条款</div>
<div class="btn cl2" :class="{ 'active': active2 === '全部条款' }" @click="active2 = '全部条款'">全部条款</div>
<div class="right-bottom-content">
<div v-for="(item, index) in filteredClauseList" :key="index" class="clause-item">
<div class="clause-item-title">
......@@ -122,7 +119,9 @@ const getlimitClauseData = async () => {
limitId: route.query.id
});
if (res && res.code === 200) {
limitClauseData.value = res.data || [];
// 兼容后端返回:数组 / 单对象 / 包一层 data
const raw = res?.data?.data ?? res?.data ?? [];
limitClauseData.value = Array.isArray(raw) ? raw : raw ? [raw] : [];
} else {
limitClauseData.value = [];
}
......@@ -257,10 +256,11 @@ const filteredBackgroundList = computed(() => {
}
});
const active2 = ref("涉华背景");
// 限制条款筛选:涉华条款/全部条款
const active2 = ref("涉华条款");
const chineseNumbers = ["一", "二", "三", "四", "五", "六", "七", "八", "九", "十"];
const filteredClauseList = computed(() => {
if (active2.value === "全部背景") {
if (active2.value === "全部条款") {
return limitClauseData.value;
} else {
return limitClauseData.value.filter(item => item.ISCN === "Y");
......@@ -376,12 +376,14 @@ const dataList3 = ref([
margin: 0;
padding: 0;
}
.cooperation-restrictions-detail {
width: 100%;
height: 100%;
background: rgba(243, 243, 244, 1);
overflow: auto;
padding-bottom: 50px;
.nav {
width: 100%;
height: 120px;
......@@ -390,52 +392,60 @@ const dataList3 = ref([
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
position: sticky;
top: 0;
z-index: 99999999;
z-index: 9;
.nav-main {
width: 1600px;
height: 81px;
margin: 0 auto;
display: flex;
align-items: center;
img {
width: 72px;
height: 72px;
margin-right: 16px;
}
.content {
width: 758px;
height: 81px;
margin-right: 378px;
.cl1 {
font-size: 24px;
font-weight: 700;
line-height: 32px;
font-family: "Microsoft YaHei";
font-family: "Source Han Sans CN";
color: rgb(59, 65, 75);
margin-bottom: 1px;
}
.cl2 {
height: 24px;
font-size: 16px;
font-weight: 400;
line-height: 24px;
font-family: "Microsoft YaHei";
font-family: "Source Han Sans CN";
color: rgb(59, 65, 75);
margin-bottom: 1px;
}
.cl3 {
font-size: 16px;
font-weight: 400;
line-height: 24px;
font-family: "Microsoft YaHei";
font-family: "Source Han Sans CN";
color: rgb(95, 101, 108);
}
}
.btn {
width: 376px;
height: 36px;
display: flex;
justify-content: right;
.btn1 {
border-radius: 6px;
border: 1px solid rgb(230, 231, 232);
......@@ -447,17 +457,20 @@ const dataList3 = ref([
align-items: center;
justify-content: center;
cursor: pointer;
img {
width: 16px;
height: 16px;
margin-right: 8px;
}
font-size: 16px;
font-weight: 400;
line-height: 22px;
font-family: "Microsoft YaHei";
color: rgb(95, 101, 108);
}
.active {
background-color: rgb(5, 95, 194);
color: #fff;
......@@ -465,6 +478,7 @@ const dataList3 = ref([
}
}
}
.title {
width: 1600px;
height: 50px;
......@@ -477,22 +491,29 @@ const dataList3 = ref([
align-items: center;
position: relative;
cursor: pointer;
.title-one {
margin-left: 23px;
font-size: 16px;
font-family: "Source Han Sans CN";
font-weight: 400;
font-size: 16px;
line-height: 24px;
font-family: "Microsoft YaHei";
letter-spacing: 0px;
text-align: justify;
color: rgb(59, 65, 75);
}
.title-two {
font-size: 16px;
font-family: "Source Han Sans CN";
font-weight: 700;
font-size: 16px;
line-height: 24px;
font-family: "Microsoft YaHei";
letter-spacing: 0px;
text-align: justify;
color: rgb(5, 95, 194);
cursor: pointer;
}
img {
width: 24px;
height: 24px;
......@@ -502,15 +523,19 @@ const dataList3 = ref([
cursor: pointer;
}
}
.main {
width: 1600px;
height: 1373px;
margin: 0 auto;
display: flex;
margin-top: 16px;
.left {
width: 520px;
height: 1012px;
margin-right: 17px;
.left-top {
margin-bottom: 16px;
width: 520px;
......@@ -521,51 +546,27 @@ const dataList3 = ref([
position: relative;
box-sizing: border-box;
overflow: hidden;
.box1-main {
display: flex;
flex-direction: column;
height: 100%;
padding-bottom: 33px;
.left-top-title {
font-size: 20px;
font-weight: 700;
line-height: 26px;
font-family: "Microsoft YaHei";
color: rgb(5, 95, 194);
position: absolute;
top: 14px;
left: 22px;
}
.img1 {
width: 8px;
height: 20px;
position: absolute;
left: 0px;
top: 18px;
}
.img2 {
width: 28px;
height: 28px;
position: absolute;
top: 14px;
right: 44px;
cursor: pointer;
}
.img3 {
width: 28px;
height: 28px;
position: absolute;
top: 14px;
right: 12px;
cursor: pointer;
}
.left-top-content {
width: 470px;
margin-top: 58px;
margin-top: 4px;
margin-left: 26px;
border-radius: 4px;
border: 1px solid rgba(231, 243, 255, 1);
background-color: rgba(246, 250, 255, 1);
background: linear-gradient(to bottom, rgba(231, 243, 255, 1), rgba(231, 243, 255, 0));
display: flex;
justify-content: center;
align-items: center;
padding: 16px 24px;
span {
font-size: 16px;
font-weight: 700;
......@@ -574,141 +575,146 @@ const dataList3 = ref([
color: rgb(5, 95, 194);
}
}
.left-top-bottom {
width: 460px;
height: 184px;
margin-top: 19px;
margin-left: 26px;
div {
height: 24px;
margin-bottom: 16px;
.tit {
.tit1 {
display: inline-block;
width: 120px;
font-size: 16px;
height: 26px;
font-weight: 700;
font-family: "Microsoft YaHei";
font-family: "Source Han Sans CN";
line-height: 24px;
color: rgb(59, 65, 75);
}
.tit1 {
.tit2 {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
font-family: "Source Han Sans CN";
line-height: 30px;
color: rgb(59, 65, 75);
}
.tit2 {
.tit3 {
color: rgb(5, 95, 194);
font-family: "Source Han Sans CN";
line-height: 30px;
font-size: 16px;
font-weight: 400;
cursor: pointer;
}
.tit3 {
.tit4 {
display: inline-block;
font-family: "Source Han Sans CN";
border-radius: 4px;
background-color: rgba(231, 243, 255, 1);
line-height: 24px !important;
background-color: rgb(231, 243, 255);
color: rgb(5, 95, 194);
padding: 2px 4px;
}
}
}
}
}
.left-bottom {
width: 520px;
height: 610px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
position: relative;
.left-bottom-title {
font-size: 20px;
font-weight: 700;
line-height: 26px;
font-family: "Microsoft YaHei";
color: rgb(5, 95, 194);
position: absolute;
top: 14px;
left: 22px;
}
.img1 {
width: 8px;
height: 20px;
position: absolute;
left: 0px;
top: 18px;
}
.img2 {
width: 28px;
height: 28px;
position: absolute;
top: 14px;
right: 44px;
cursor: pointer;
}
.img3 {
width: 28px;
height: 28px;
position: absolute;
top: 14px;
right: 12px;
cursor: pointer;
}
.left-bottom-main {
width: 478px;
height: 528px;
position: absolute;
top: 60px;
left: 21px;
width: 520px;
padding: 6px 21px 22px 21px;
display: flex;
flex-direction: column;
gap: 12px;
.main-box {
width: 480px;
height: 48px;
border-radius: 50px;
border: 1px solid rgb(234, 236, 238);
margin-bottom: 12px;
position: relative;
display: flex;
padding-left: 16px;
padding-top: 12px;
padding-bottom: 12px;
background-color: rgb(247, 248, 249);
cursor: pointer;
img {
width: 24px;
height: 24px;
position: absolute;
top: 12px;
left: 16px;
}
.name {
position: absolute;
top: 12px;
left: 52px;
font-size: 16px;
font-family: "Source Han Sans CN";
font-weight: 700;
font-family: "Microsoft YaHei";
font-size: 16px;
line-height: 24px;
letter-spacing: 0;
text-align: left;
color: rgb(59, 65, 75);
margin-left: 12px;
}
.type {
position: absolute;
top: 12px;
right: 12px;
font-size: 16px;
font-family: "Source Han Sans CN";
font-weight: 400;
font-family: "Microsoft YaHei";
font-size: 16px;
line-height: 24px;
letter-spacing: 0px;
text-align: right;
color: rgb(95, 101, 108);
margin-left: auto;
margin-right: 18px;
}
}
}
}
/* 仅本页左侧两个 AnalysisBox:头部高度改为 54px(放在 .left 作用域,避免 SCSS 嵌套导致选择器失效) */
:deep(.left-top .analysis-box-wrapper .wrapper-header),
:deep(.left-bottom .analysis-box-wrapper .wrapper-header) {
height: 54px !important;
}
:deep(.left-top .analysis-box-wrapper .wrapper-header .header-title > div),
:deep(.left-bottom .analysis-box-wrapper .wrapper-header .header-title > div) {
line-height: 54px !important;
}
}
.right {
width: 1063px;
height: 1373px;
.right-top {
margin-bottom: 16px;
width: 1063px;
height: 476px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
position: relative;
padding: 60px 19px 24px 22px;
.right-top-title {
font-size: 20px;
font-weight: 700;
......@@ -719,6 +725,7 @@ const dataList3 = ref([
top: 14px;
left: 22px;
}
.img1 {
width: 8px;
height: 20px;
......@@ -726,9 +733,11 @@ const dataList3 = ref([
left: 0px;
top: 18px;
}
.right-top-content {
width: 1022px;
height: 392px;
.right-top-item {
width: 1022px;
padding: 12px 0px;
......@@ -737,11 +746,13 @@ const dataList3 = ref([
align-items: center;
overflow: auto;
}
.right-top-item:nth-child(odd) {
background-color: rgb(247, 248, 249);
border-top: 1px solid rgb(234, 236, 238);
border-bottom: 1px solid rgb(234, 236, 238);
}
.right-top-item .id {
display: inline-block;
width: 24px;
......@@ -758,16 +769,20 @@ const dataList3 = ref([
top: 16px;
left: 24px;
}
.right-top-item .name {
display: inline-block;
width: 902px;
font-size: 16px;
font-family: "Source Han Sans CN";
font-weight: 400;
font-family: "Microsoft YaHei";
font-size: 16px;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
color: rgb(59, 65, 75);
margin-left: 64px;
}
.right-top-item img {
width: 16px;
height: 31px;
......@@ -777,6 +792,7 @@ const dataList3 = ref([
cursor: pointer;
}
}
.btn {
padding: 2px 8px;
font-size: 16px;
......@@ -789,29 +805,36 @@ const dataList3 = ref([
background-color: #fff;
cursor: pointer;
position: absolute;
z-index: 100;
}
.cl1 {
top: 14px;
right: 107px;
}
.cl2 {
top: 14px;
right: 19px;
}
.active {
color: rgb(5, 95, 194);
background-color: rgba(246, 250, 255, 1);
border-color: rgb(5, 95, 194);
}
}
.right-bottom {
width: 1063px;
height: 881px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
position: relative;
padding: 60px 19px 0px 22px;
.right-bottom-title {
font-size: 20px;
font-weight: 700;
......@@ -822,6 +845,7 @@ const dataList3 = ref([
top: 14px;
left: 22px;
}
.img1 {
width: 8px;
height: 20px;
......@@ -829,6 +853,7 @@ const dataList3 = ref([
left: 0px;
top: 18px;
}
.btn {
padding: 2px 8px;
font-size: 16px;
......@@ -841,40 +866,45 @@ const dataList3 = ref([
background-color: #fff;
cursor: pointer;
position: absolute;
z-index: 100;
}
.cl1 {
top: 14px;
right: 107px;
}
.cl2 {
top: 14px;
right: 19px;
}
.active {
color: rgb(5, 95, 194);
background-color: rgba(246, 250, 255, 1);
border-color: rgb(5, 95, 194);
}
.right-bottom-content {
width: 1022px;
position: absolute;
top: 60px;
left: 22px;
margin-bottom: 24px;
overflow: auto;
.clause-item {
margin-bottom: 24px;
.clause-item-title {
width: 1022px;
height: 55px;
padding: 14px 56px 17px 24px;
padding: 14px 24px 17px 24px;
display: flex;
align-items: center;
position: relative;
justify-content: space-between;
background-color: rgb(247, 248, 249);
border-top: 1px solid rgb(234, 236, 238);
border-bottom: 1px solid rgb(234, 236, 238);
span {
font-size: 18px;
font-weight: 700;
......@@ -882,18 +912,20 @@ const dataList3 = ref([
line-height: 24px;
color: rgb(59, 65, 75);
}
img {
width: 16px;
height: 31px;
position: absolute;
right: 24px;
top: 14px;
cursor: pointer;
}
}
.clause-item-content {
width: 1022px;
padding: 12px 24px 12px 78px;
padding: 12px 24px 12px 54px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
......
......@@ -15,7 +15,8 @@
<div class="main-content" ref="homeMainRef" :class="{ 'scroll-main': isShow }">
<div class="home-top-bg"></div>
<!-- 搜索栏部分 -->
<SearchContainer v-if="homeMainRef" placeholder="搜索合作限制" :containerRef="homeMainRef" areaName="" />
<SearchContainer v-if="homeMainRef" :countInfo="cooperationCountInfo" placeholder="搜索合作限制"
:containerRef="homeMainRef" areaName="" />
<!-- 最新动态 -->
<div class="newdata" id="position1">
......@@ -57,9 +58,36 @@ import newData from "./components/dataNew/index.vue";
import askPage from "./components/askPage/index.vue";
import dataSub from "./components/dataSub/index.vue";
import resLib from "./components/resLib/index.vue";
import { getCoopRestrictionStatistics } from "@/api/coopRestriction/coopRestriction.js";
import { useContainerScroll } from "@/hooks/useScrollShow";
const cooperationCountInfo = ref([]);
// const
const getCooperationCountInfo = async () => {
try {
const res = await getCoopRestrictionStatistics();
if (res && res.code === 200) {
// console.log('----getStatCountInfo', res.data)
cooperationCountInfo.value = [
{
name: "相关法案",
count: res.data.billCounts
},
{
name: "相关政令",
count: res.data.aocounts
},
{
name: "相关政府公告",
count: res.data.ggcounts
},
];
}
} catch (error) {
console.error("获取首页统计接口失败:", error);
}
};
// 搜索框
const input = ref("");
const homeMainRef = ref(null);
......@@ -71,7 +99,9 @@ const router = useRouter();
const handleSearch = () => {
console.log("搜索内容:", input.value);
};
onMounted(() => {
getCooperationCountInfo();
});
// 锚点跳转
const handleToPosi = id => {
const element = document.getElementById(id);
......@@ -106,6 +136,7 @@ const handleToPosi = id => {
.coop-page {
width: 100%;
height: 100%;
display: flex;
// .breadcrumb {
// width: 100%;
// height: 64px;
......@@ -138,6 +169,12 @@ const handleToPosi = id => {
top: -64px;
}
:deep(.search-container .search-center) {
width: 440px;
gap: 40px;
justify-content: center;
}
.search {
width: 960px;
height: 168px;
......@@ -467,8 +504,6 @@ const handleToPosi = id => {
}
}
.scroll-main {
height: calc(100% - 144px) !important;
}
}
</style>
<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
}
......
......@@ -7,18 +7,11 @@
<div class="title">{{ "科技领域" }}</div>
</div>
<div class="select-main">
<el-checkbox-group
class="checkbox-group"
:model-value="selectedAreaList"
@change="handleAreaGroupChange">
<el-checkbox-group class="checkbox-group" :model-value="selectedAreaList" @change="handleAreaGroupChange">
<el-checkbox class="filter-checkbox all-checkbox" :label="RESOURCE_FILTER_ALL_AREA">
{{ RESOURCE_FILTER_ALL_AREA }}
</el-checkbox>
<el-checkbox
v-for="research in areaList"
:key="research.id"
class="filter-checkbox"
:label="research.id">
<el-checkbox v-for="research in areaList" :key="research.id" class="filter-checkbox" :label="research.id">
{{ research.name }}
</el-checkbox>
</el-checkbox-group>
......@@ -31,18 +24,11 @@
<div class="title">{{ "发布时间" }}</div>
</div>
<div class="select-main">
<el-checkbox-group
class="checkbox-group"
:model-value="selectedPubTimeList"
@change="handleTimeGroupChange">
<el-checkbox-group class="checkbox-group" :model-value="selectedPubTimeList" @change="handleTimeGroupChange">
<el-checkbox class="filter-checkbox all-checkbox" :label="RESOURCE_FILTER_ALL_TIME">
{{ RESOURCE_FILTER_ALL_TIME }}
</el-checkbox>
<el-checkbox
v-for="time in pubTimeList"
:key="time.id"
class="filter-checkbox"
:label="time.id">
<el-checkbox v-for="time in pubTimeList" :key="time.id" class="filter-checkbox" :label="time.id">
{{ time.name }}
</el-checkbox>
</el-checkbox-group>
......
......@@ -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")
......
......@@ -163,7 +163,35 @@ const getMultiLineChart = (data) => {
{
type: 'category',
boundaryGap: false,
data: title
data: title,
axisLabel: {
// 同时设置 color 与 textStyle,避免 ECharts/主题在 axisLine/axisTick 展示时覆盖
color: 'rgba(132, 136, 142, 1)',
textStyle: {
color: 'rgba(132, 136, 142, 1)',
fontSize: 14,
fontFamily: 'Source Han Sans CN',
fontWeight: 400
}
},
axisLine: {
show: true,
lineStyle: {
color: 'rgba(231, 241, 255, 1)' // 你要的红色
}
},
axisTick: {
show: true,
// 让刻度和 label 对齐,显示短直线
alignWithLabel: true,
length: 6,
length2: 0,
lineStyle: {
color: "rgba(231, 241, 255, 1)",
type: "solid"
}
},
}
],
yAxis: [
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论