提交 1a528d62 authored 作者: 朱政's avatar 朱政

feat:美国科研资助体系概览页样式与功能

上级 ac2f6b40
流水线 #309 已通过 于阶段
in 1 分 26 秒
......@@ -126,11 +126,25 @@ export function getAreaType() {
})
}
/**
* 资助项目列表:领域、年份用逗号拼接为一个查询参数(arealist=1,2,3&yearlist=2025,2024)
*/
function normalizeProjectListQueryParams(params) {
const next = { ...(params || {}) }
if (Array.isArray(next.arealist)) {
next.arealist = next.arealist.filter((v) => v !== undefined && v !== null && v !== "").join(",")
}
if (Array.isArray(next.yearlist)) {
next.yearlist = next.yearlist.filter((v) => v !== undefined && v !== null && v !== "").join(",")
}
return next
}
//资助体系v2.0:资助项目列表分页
export function getProjectListNew(params) {
return request({
method: 'GET',
url: `/api/fund/getProjectListNew`,
params
params: normalizeProjectListQueryParams(params)
})
}
......@@ -70,7 +70,9 @@ router.beforeEach((to, from, next) => {
if (to.meta.dynamicTitle) {
console.log('to', to);
const storageKey = to.meta.titleStorageKey || "curTabName";
document.title = window.sessionStorage.getItem(storageKey) || to.meta.title;
// 新开页签时 sessionStorage 不共享,优先用 query 带过来的 title/name
const queryTitle = (to.query && (to.query.title || to.query.name)) ? String(to.query.title || to.query.name) : "";
document.title = queryTitle || window.sessionStorage.getItem(storageKey) || to.meta.title;
} else {
document.title = to.meta.title
......
......@@ -88,7 +88,8 @@ const decreeRoutes = [
component: Institution,
meta: {
title: "行政机构主页",
dynamicTitle: true
dynamicTitle: true,
titleStorageKey: "institutionTabName"
}
},
{
......
......@@ -23,8 +23,8 @@
</div>
</div> -->
<NewsList :newsList="leftList" @more-click="handleToMoreNews" img="newsImage" title="newsTitle"
content="newsContent" from="from" />
<MessageBubble :messageList="rightList" @more-click="handleToSocialDetail" source="orgName" content="remarks"
content="newsContent" from="from" @item-click="item => gotoNewsDetail(item.newsId)" />
<MessageBubble :messageList="rightList" @person-click="handleToSocialDetail" source="orgName" content="remarks"
name="personName" imageUrl="personImage">
</MessageBubble>
<!-- <div class="right-box">
......@@ -56,6 +56,7 @@
import NewsList from "@/components/base/newsList/index.vue";
import { ref, onMounted } from "vue";
import { useGotoNewsDetail } from "@/router/modules/news";
import {
getSocialMediaInfo, getNews
} from "@/api/scientificFunding/overview";
......@@ -171,15 +172,19 @@ const handleToMoreNews = () => {
};
// 查看社交媒体详情
const handleToSocialDetail = item => {
const personId = item?.personId || item?.id;
if (!personId) return;
const route = router.resolve({
path: "/characterPage",
query: {
personId: item.id
personId
}
});
window.open(route.href, "_blank");
};
const gotoNewsDetail = useGotoNewsDetail();
onMounted(async () => {
handleNews()
handleSocialMediaInfo()
......
......@@ -21,23 +21,30 @@
<div class="left-center-main-ul">
<ul>
<li>
<img src="./assets/icon-black.png" alt="" class="li-img" />
<span class="ul-title">投资主体:</span>
<span class="ul-content">美国国家科学基金会</span>
</li>
<li>
<img src="./assets/icon-black.png" alt="" class="li-img" />
<span class="ul-title">发布日期:</span>
<span class="ul-content">{{ itemData.publicationDate }}</span>
</li>
<li>
<img src="./assets/icon-black.png" alt="" class="li-img" />
<span class="ul-title">资助经费:</span>
<span class="ul-content">{{ itemData.amount }}</span>
</li>
<li>
<img src="./assets/icon-black.png" alt="" class="li-img" />
<span class="ul-title">涉及领域:</span>
<span class="ul-pie cl1" v-for="value in itemData.toOrgNameList">{{ value }}</span>
<span class="ul-pie cl1">
<AreaTag v-for="(val, idx) in itemData.areaList" :key="idx" :tagName="val" />
</span>
</li>
<li>
<img src="./assets/icon-black.png" alt="" class="li-img" />
<span class="ul-title">资助对象:</span>
<span class="ul-content">{{ itemData.fromOrgNameList.join(',') }}</span>
</li>
......@@ -97,42 +104,7 @@ import {
import router from "@/router";
const list = ref([
{
id: 1,
title: "特别重大",
content: "NSF宣布新的“新兴技术体验式学习”计划资...",
time: "一天前"
},
{
id: 2,
title: "一般风险",
content: "美国NASA公布NIAC计划2025年度第一轮资助",
time: "一天前"
},
{
id: 3,
title: "特别重大",
content: "美国NASA公布“早期创新计划”2026年资助...",
time: "一天前"
},
{
id: 4,
title: "重大风险",
content: '美国NIH冻结多所顶尖大学资金引发广泛争议"',
time: "一天前"
},
{
id: 5,
title: "重大风险",
content: "美国NIH终止哥伦比亚大学研究项目拨款引发...",
time: "一天前"
},
{
id: 6,
title: "特别重大",
content: "美国DARPA资助可调控生物功能微系统技术开发",
time: "一天前"
}
]);
//// 获取风险信号
......@@ -286,7 +258,7 @@ onMounted(async () => {
height: 175px;
.left-center-main-title {
margin-left: 19px;
margin-left: 22px;
margin-bottom: 17px;
font-size: 20px;
font-weight: 700;
......@@ -305,6 +277,20 @@ onMounted(async () => {
width: 100%;
height: 24px;
margin-bottom: 12px;
display: flex;
.li-img {
width: 4px;
height: 4px;
margin-right: 18px;
margin-top: 10px;
img {
width: 100%;
height: 100%;
display: block;
}
}
.ul-title {
display: inline-block;
......@@ -326,19 +312,14 @@ onMounted(async () => {
}
.ul-pie {
display: inline-block;
display: flex;
gap: 8px;
box-sizing: border-box;
padding: 2px 8px;
border: 1px solid;
border-radius: 4px;
flex-direction: row;
margin-right: 8px;
}
.cl1 {
border-color: rgba(186, 224, 255, 1);
background-color: rgba(230, 244, 255, 1);
color: rgba(22, 119, 255, 1);
}
.cl2 {
border-color: rgba(255, 163, 158, 1);
......
......@@ -5,20 +5,34 @@
<div class="left-title">
<img src="./assets/icon01.png" alt="" />
<div class="tit">资助领域分布情况</div>
<div :class="radio1 === true ? 'btn-select' : 'btn'" style=" right:250px;" @click="changeradio1()">
<div :class="radio1 === true ? 'btn-select' : 'btn'" style=" right:250px;" @click="setRadio1(true)">
资助经费
</div>
<div :class="radio1 === false ? 'btn-select' : 'btn'" style=" right: 150px;" @click="changeradio1()">
<div :class="radio1 === false ? 'btn-select' : 'btn'" style=" right: 150px;" @click="setRadio1(false)">
资助项目
</div>
<el-select v-model="value1" placeholder="Select" class="select" style=" right: 31px;">
<el-select v-model="value1" placeholder="Select" class="select" style=" right: 31px;"
@change="handleLeft1YearChange">
<el-option v-for="item in options1" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="left-main">
<div class="left-main-echarts" ref="leftChartRef" v-show="radio1 === true">资助经费</div>
<div class="left-main-echarts" ref="leftChartRef1" v-show="radio1 === false">资助项目</div>
<div class="left-main-echarts" ref="leftChartRef" v-show="radio1 === true"></div>
<div class="left-main-echarts" ref="leftChartRef1" v-show="radio1 === false"></div>
<el-empty v-show="!hasLeft1ChartData && !isLeft1Loading" class="datasub-el-empty" description="暂无数据"
:image-size="100" />
<div class="source" v-show="hasLeft1ChartData">
<TipTab :text="'资助领域分布情况,数据来源:美国国会官网'" />
</div>
<div class="chart-box" v-show="hasLeft1ChartData">
<div class="btn-box" v-if="!isShowAiContentLeft1" @mouseenter="handleSwitchAiLeft1(true)">
<AiButton />
</div>
<div class="content-box" v-else @mouseleave="handleSwitchAiLeft1(false)">
<AiPane :aiContent="aiContentLeft1" />
</div>
</div>
</div>
</div>
<div class="left">
......@@ -31,6 +45,18 @@
</div>
<div class="left-main1">
<div class="left-sankey-echarts" ref="leftSankeyRef"></div>
<el-empty v-show="!hasLeft2ChartData" class="datasub-el-empty" description="暂无数据" :image-size="100" />
<div class="source" v-show="hasLeft2ChartData">
<TipTab :text="'机构资助领域情况,数据来源:美国国会官网'" />
</div>
<div class="chart-box" v-show="hasLeft2ChartData">
<div class="btn-box" v-if="!isShowAiContentLeft2" @mouseenter="handleSwitchAiLeft2(true)">
<AiButton />
</div>
<div class="content-box" v-else @mouseleave="handleSwitchAiLeft2(false)">
<AiPane :aiContent="aiContentLeft2" />
</div>
</div>
</div>
</div>
</div>
......@@ -39,33 +65,58 @@
<div class="right-title">
<img src="./assets/icon02.png" alt="" />
<div class="tit">资助经费变化情况</div>
<div :class="radio2 === true ? 'btn-select' : 'btn'" style=" right:250px;" @click="changeradio2()">
<div :class="radio2 === true ? 'btn-select' : 'btn'" style=" right:250px;" @click="setRadio2(true)">
资助经费
</div>
<div :class="radio2 === false ? 'btn-select' : 'btn'" style=" right: 150px;" @click="changeradio2()">
<div :class="radio2 === false ? 'btn-select' : 'btn'" style=" right: 150px;" @click="setRadio2(false)">
资助项目
</div>
<el-select v-model="value" placeholder="Select" class="select">
<el-select v-model="value" placeholder="Select" class="select" @change="handleRight1RangeChange">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="right-main">
<div class="right-main-echarts" ref="rightChartRef" v-show="radio2 === true"></div>
<div class="right-main-echarts" ref="rightChartRef1" v-show="radio2 === false"></div>
<div class="right-main-tit">亿美元</div>
<div class="right-main-tit" v-show="hasRight1ChartData">亿美元</div>
<el-empty v-show="!hasRight1ChartData && !isRight1Loading" class="datasub-el-empty" description="暂无数据"
:image-size="100" />
<div class="source" v-show="hasRight1ChartData">
<TipTab :text="'资助经费变化情况,数据来源:美国国会官网'" />
</div>
<div class="chart-box" v-show="hasRight1ChartData">
<div class="btn-box" v-if="!isShowAiContentRight1" @mouseenter="handleSwitchAiRight1(true)">
<AiButton />
</div>
<div class="content-box" v-else @mouseleave="handleSwitchAiRight1(false)">
<AiPane :aiContent="aiContentRight1" />
</div>
</div>
</div>
</div>
<div class="right">
<div class="right-title">
<img src="./assets/icon04.png" alt="" />
<div class="tit">项目资助强度分布</div>
<el-select v-model="value3" placeholder="Select" class="select">
<el-select v-model="value3" placeholder="Select" class="select" @change="handleRight2YearChange">
<el-option v-for="item in options1" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="right-main1">
<div class="right-boxplot-echarts" ref="boxplotChartRef"></div>
<div class="right-main1-tit">单位:亿美元</div>
<div class="right-main1-tit" v-show="hasRight2ChartData">单位:亿美元</div>
<el-empty v-show="!hasRight2ChartData" class="datasub-el-empty" description="暂无数据" :image-size="100" />
<div class="source" v-show="hasRight2ChartData">
<TipTab :text="'项目资助强度分布,数据来源:美国国会官网'" />
</div>
<div class="chart-box" v-show="hasRight2ChartData">
<div class="btn-box" v-if="!isShowAiContentRight2" @mouseenter="handleSwitchAiRight2(true)">
<AiButton />
</div>
<div class="content-box" v-else @mouseleave="handleSwitchAiRight2(false)">
<AiPane :aiContent="aiContentRight2" />
</div>
</div>
</div>
</div>
</div>
......@@ -73,13 +124,31 @@
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, nextTick } from "vue";
import { ref, onMounted, onBeforeUnmount, nextTick, computed } from "vue";
import { getChartAnalysis } from "@/api/aiAnalysis";
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";
import {
findFundField, findCountryProjectAreaList, getCountryFundingChange, getCountryFundProjectChange, getOrgFundsArea, getOrgFundStrength
} from "@/api/scientificFunding/overview";
import * as echarts from "echarts";
const isNonEmptyArray = (v) => Array.isArray(v) && v.length > 0;
// 兼容后端多种返回结构:[] / {data:[]} / {content:[]} 等
const extractArrayData = (res) => {
const d = res?.data;
if (Array.isArray(d)) return d;
if (Array.isArray(d?.data)) return d.data;
if (Array.isArray(d?.content)) return d.content;
if (Array.isArray(d?.list)) return d.list;
return [];
};
const isSuccessCode = (res) => Number(res?.code) === 200;
const value = ref(10);
......@@ -107,54 +176,153 @@ const options1 = [
label: "2024年"
}
];
//获取当前时间x年前的日期
function getDateYearsAgo(years) {
// 获取当前日期
const currentDate = new Date();
// 计算指定年数之前的日期
const pastDate = new Date(currentDate.getFullYear() - years, currentDate.getMonth(), currentDate.getDate());
// 格式化日期为 "YYYY-MM-DD" 的形式
const year = pastDate.getFullYear();
const month = String(pastDate.getMonth() + 1).padStart(2, "0"); // 月份从0开始,需要加1
const day = String(pastDate.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}
const normalizeYearParam = (val) => {
const y = Number(val);
return Number.isFinite(y) ? y : val;
};
/** 资助经费变化情况:固定时间窗起点 */
const buildStartDateByRange = (rangeYears) => {
const n = Number(rangeYears);
if (n === 10) return "2015-01-01";
if (n === 5) return "2020-01-01";
// 兜底:保持原逻辑的“年初”
const y = new Date().getFullYear() - (Number.isFinite(n) ? n : 0);
return `${y}-01-01`;
};
const radio1 = ref(true)
const changeradio1 = () => {
radio1.value = !radio1.value
}
const setRadio1 = (val) => {
if (radio1.value === val) return;
radio1.value = val;
// 切换模式:先清空当前模式数据与图表,再按当前年份重新拉数
if (val) {
left1RawFund.value = [];
if (leftChart) {
leftChart.dispose();
leftChart = null;
}
} else {
left1RawProject.value = [];
if (leftChart1) {
leftChart1.dispose();
leftChart1 = null;
}
}
handleLeft1YearChange();
// 切换显示后强制当前图表 resize,确保 canvas 尺寸稳定
nextTick(() => {
if (radio1.value) {
if (leftChart) leftChart.resize();
} else {
if (leftChart1) leftChart1.resize();
}
});
};
const value1 = ref(2025);
const leftChartRef = ref(null);
const leftChartRef1 = ref(null);
const left1RawFund = ref([]);
const left1RawProject = ref([]);
const left1FundLoading = ref(false);
const left1ProjLoading = ref(false);
const hasLeft1ChartData = computed(() => {
return radio1.value ? isNonEmptyArray(left1RawFund.value) : isNonEmptyArray(left1RawProject.value);
});
const isLeft1Loading = computed(() => (radio1.value ? left1FundLoading.value : left1ProjLoading.value));
let left1FundReqSeq = 0;
let left1ProjReqSeq = 0;
const handleLeft1YearChange = () => {
// 根据当前“资助经费/资助项目”切换状态请求对应接口
if (radio1.value) {
handleGetFundField();
} else {
handleFindCountryProjectAreaList();
}
};
// 资助体系v2.0:资助领域分布情况:资助经费
const handleGetFundField = async () => {
const seq = ++left1FundReqSeq;
left1FundLoading.value = true;
try {
let params = {
year: value1.value
year: normalizeYearParam(value1.value)
}
const res = await findFundField(params);
if (seq !== left1FundReqSeq) return;
console.log("资助领域分布情况", res);
if (res.code === 200 && res.data) {
initLeftDonut(res.data, true)
if (isSuccessCode(res)) {
const list = extractArrayData(res);
left1RawFund.value = list;
if (list.length) {
await nextTick();
initLeftDonut(list, true)
} else if (leftChart) {
leftChart.dispose();
leftChart = null;
}
} else {
left1RawFund.value = [];
if (leftChart) {
leftChart.dispose();
leftChart = null;
}
}
} catch (error) {
if (seq !== left1FundReqSeq) return;
console.error("获取资助领域分布情况error", error);
left1RawFund.value = [];
if (leftChart) {
leftChart.dispose();
leftChart = null;
}
} finally {
if (seq === left1FundReqSeq) {
left1FundLoading.value = false;
}
}
};
//资助体系v2.0:资助领域分布情况:资助项目
const handleFindCountryProjectAreaList = async () => {
const seq = ++left1ProjReqSeq;
left1ProjLoading.value = true;
try {
let params = {
year: value1.value
year: normalizeYearParam(value1.value)
}
const res = await findCountryProjectAreaList(params);
if (seq !== left1ProjReqSeq) return;
console.log("资助领域分布情况", res);
if (res.code === 200 && res.data) {
initLeftDonut(res.data, false)
if (isSuccessCode(res)) {
const list = extractArrayData(res);
left1RawProject.value = list;
if (list.length) {
await nextTick();
initLeftDonut(list, false)
} else if (leftChart1) {
leftChart1.dispose();
leftChart1 = null;
}
} else {
left1RawProject.value = [];
if (leftChart1) {
leftChart1.dispose();
leftChart1 = null;
}
}
} catch (error) {
if (seq !== left1ProjReqSeq) return;
console.error("获取资助领域分布情况error", error);
left1RawProject.value = [];
if (leftChart1) {
leftChart1.dispose();
leftChart1 = null;
}
} finally {
if (seq === left1ProjReqSeq) {
left1ProjLoading.value = false;
}
}
};
// 资助领域分布情况
......@@ -208,68 +376,179 @@ const initLeftDonut = (rawData, show) => {
};
if (show == true) {
if (leftChart) leftChart.dispose();
leftChart = echarts.init(leftChartRef.value);
leftChart.setOption(option);
nextTick(() => leftChart && leftChart.resize());
} else {
if (leftChart1) leftChart1.dispose();
leftChart1 = echarts.init(leftChartRef1.value);
leftChart1.setOption(option);
nextTick(() => leftChart1 && leftChart1.resize());
}
};
const rightChartRef = ref(null);
const rightChartRef1 = ref(null);
const right1RawFund = ref([]);
const right1RawProject = ref([]);
const right1FundLoading = ref(false);
const right1ProjLoading = ref(false);
const hasRight1ChartData = computed(() => {
return radio2.value ? isNonEmptyArray(right1RawFund.value) : isNonEmptyArray(right1RawProject.value);
});
const isRight1Loading = computed(() => (radio2.value ? right1FundLoading.value : right1ProjLoading.value));
const radio2 = ref(true)
const changeradio2 = () => {
radio2.value ? handlegetCountryFundingChange() : handlegetCountryFundProjectChange()
radio2.value = !radio2.value
}
const handleRight1RangeChange = () => {
// 根据当前“资助经费/资助项目”切换状态请求对应接口
if (radio2.value) {
handlegetCountryFundingChange();
} else {
handlegetCountryFundProjectChange();
}
};
const setRadio2 = (val) => {
if (radio2.value === val) return;
radio2.value = val;
// 切换模式:先清空当前模式数据与图表,再按当前时间窗重新拉数
if (val) {
right1RawFund.value = [];
if (rightChart) {
rightChart.dispose();
rightChart = null;
}
} else {
right1RawProject.value = [];
if (rightChart1) {
rightChart1.dispose();
rightChart1 = null;
}
}
handleRight1RangeChange();
// 切换显示后强制当前图表 resize,确保 canvas 尺寸稳定
nextTick(() => {
if (radio2.value) {
if (rightChart) rightChart.resize();
} else {
if (rightChart1) rightChart1.resize();
}
});
};
// 资助体系v2.0:资助经费变化情况:资助经费
const handlegetCountryFundingChange = async () => {
try {
right1FundLoading.value = true;
let params = {
startDate: getDateYearsAgo(value.value)
startDate: buildStartDateByRange(value.value)
}
const res = await getCountryFundingChange(params);
console.log("资助经费变化情况", res);
if (res.code === 200 && res.data) {
initRightLine(res.data, true)
if (isSuccessCode(res)) {
const list = extractArrayData(res);
right1RawFund.value = list;
if (list.length) {
await nextTick();
initRightLine(list, true)
} else if (rightChart) {
rightChart.dispose();
rightChart = null;
}
} else {
right1RawFund.value = [];
if (rightChart) {
rightChart.dispose();
rightChart = null;
}
}
} catch (error) {
console.error("获取资助经费变化情况error", error);
right1RawFund.value = [];
if (rightChart) {
rightChart.dispose();
rightChart = null;
}
} finally {
right1FundLoading.value = false;
}
};
//资助体系v2.0:资助经费变化情况:资助项目
const handlegetCountryFundProjectChange = async () => {
try {
right1ProjLoading.value = true;
let params = {
startDate: getDateYearsAgo(value.value)
startDate: buildStartDateByRange(value.value)
}
const res = await getCountryFundProjectChange(params);
console.log("资助项目变化情况", res);
if (res.code === 200 && res.data) {
initRightLine(res.data)
if (isSuccessCode(res)) {
const list = extractArrayData(res);
right1RawProject.value = list;
if (list.length) {
await nextTick();
initRightLine(list)
} else if (rightChart1) {
rightChart1.dispose();
rightChart1 = null;
}
} else {
right1RawProject.value = [];
if (rightChart1) {
rightChart1.dispose();
rightChart1 = null;
}
}
} catch (error) {
console.error("获取资助项目变化情况error", error);
right1RawProject.value = [];
if (rightChart1) {
rightChart1.dispose();
rightChart1 = null;
}
} finally {
right1ProjLoading.value = false;
}
};
//项目资助强度分布
const value3 = ref(2025);
const boxplotChartRef = ref(null);
const right2RawStrength = ref([]);
const hasRight2ChartData = computed(() => isNonEmptyArray(right2RawStrength.value));
const handleRight2YearChange = () => {
handlegetOrgFundStrength();
};
const handlegetOrgFundStrength = async () => {
try {
let params = {
year: value3.value
year: normalizeYearParam(value3.value)
}
const res = await getOrgFundStrength(params);
console.log("项目资助强度分布", res);
if (res.code === 200 && res.data) {
initBoxPlot(res.data)
if (isSuccessCode(res)) {
const list = extractArrayData(res);
right2RawStrength.value = list;
if (list.length) {
await nextTick();
initBoxPlot(list)
} else if (boxplotChart) {
boxplotChart.dispose();
boxplotChart = null;
}
} else {
right2RawStrength.value = [];
if (boxplotChart) {
boxplotChart.dispose();
boxplotChart = null;
}
}
} catch (error) {
console.error("获取项目资助强度分布error", error);
right2RawStrength.value = [];
if (boxplotChart) {
boxplotChart.dispose();
boxplotChart = null;
}
}
};
//项目资助强度分布
......@@ -420,11 +699,152 @@ const initBoxPlot = (data) => {
let leftChart;
let leftChart1;
// let rightChart;
// let rightChart1;
let rightChart;
let rightChart1;
let leftSankey;
let boxplotChart;
// ------- AI 解读(刷新后默认展开,行为对齐智库概览) -------
const isShowAiContentLeft1 = ref(true);
const isShowAiContentLeft2 = ref(true);
const isShowAiContentRight1 = ref(true);
const isShowAiContentRight2 = ref(true);
const aiContentLeft1 = ref("");
const aiContentLeft2 = ref("");
const aiContentRight1 = ref("");
const aiContentRight2 = ref("");
const isAiLoadingLeft1 = ref(false);
const isAiLoadingLeft2 = ref(false);
const isAiLoadingRight1 = ref(false);
const isAiLoadingRight2 = ref(false);
const appendAiInterpretationChunk = (targetRef, chunk, loadingText = "解读生成中…") => {
if (!chunk) return;
const current = String(targetRef.value || "");
const base = current === loadingText ? "" : current;
targetRef.value = base + String(chunk);
};
const getInterpretationTextFromChartResponse = (res) => {
const list = res?.data;
const first = Array.isArray(list) ? list[0] : null;
return (
first?.["解读"] ||
first?.["interpretation"] ||
first?.["analysis"] ||
first?.["content"] ||
""
);
};
const fetchChartInterpretationOnce = async (payload, targetRef, loadingRef) => {
if (loadingRef.value) return;
const hasValidContent =
targetRef.value &&
targetRef.value !== "解读生成中…" &&
targetRef.value !== "解读加载失败" &&
targetRef.value !== "暂无图表数据";
if (hasValidContent) return;
loadingRef.value = true;
targetRef.value = "解读生成中…";
try {
const res = await getChartAnalysis(
{ text: JSON.stringify(payload) },
{
onChunk: (chunk) => appendAiInterpretationChunk(targetRef, chunk)
}
);
const text = getInterpretationTextFromChartResponse(res);
targetRef.value = text || targetRef.value || "未返回有效解读内容";
} catch (e) {
console.error("图表解读请求失败", e);
targetRef.value = "解读加载失败";
} finally {
loadingRef.value = false;
}
};
const buildPayloadLeft1 = () => {
const raw = radio1.value ? left1RawFund.value : left1RawProject.value;
if (!Array.isArray(raw) || raw.length === 0) return null;
return {
type: "分布图",
name: radio1.value ? "资助领域分布情况-资助经费" : "资助领域分布情况-资助项目",
data: raw
};
};
const buildPayloadLeft2 = () => {
const raw = left2RawSankey.value;
if (!Array.isArray(raw) || raw.length === 0) return null;
return {
type: "桑基图",
name: "机构资助领域情况",
data: raw
};
};
const buildPayloadRight1 = () => {
const raw = radio2.value ? right1RawFund.value : right1RawProject.value;
if (!Array.isArray(raw) || raw.length === 0) return null;
return {
type: "折线图",
name: radio2.value ? "资助经费变化情况-资助经费" : "资助经费变化情况-资助项目",
data: raw
};
};
const buildPayloadRight2 = () => {
const raw = right2RawStrength.value;
if (!Array.isArray(raw) || raw.length === 0) return null;
return {
type: "箱线图",
name: "项目资助强度分布",
data: raw
};
};
const handleSwitchAiLeft1 = async (val) => {
isShowAiContentLeft1.value = val;
if (!val) return;
const payload = buildPayloadLeft1();
if (!payload) {
aiContentLeft1.value = "暂无图表数据";
return;
}
await fetchChartInterpretationOnce(payload, aiContentLeft1, isAiLoadingLeft1);
};
const handleSwitchAiLeft2 = async (val) => {
isShowAiContentLeft2.value = val;
if (!val) return;
const payload = buildPayloadLeft2();
if (!payload) {
aiContentLeft2.value = "暂无图表数据";
return;
}
await fetchChartInterpretationOnce(payload, aiContentLeft2, isAiLoadingLeft2);
};
const handleSwitchAiRight1 = async (val) => {
isShowAiContentRight1.value = val;
if (!val) return;
const payload = buildPayloadRight1();
if (!payload) {
aiContentRight1.value = "暂无图表数据";
return;
}
await fetchChartInterpretationOnce(payload, aiContentRight1, isAiLoadingRight1);
};
const handleSwitchAiRight2 = async (val) => {
isShowAiContentRight2.value = val;
if (!val) return;
const payload = buildPayloadRight2();
if (!payload) {
aiContentRight2.value = "暂无图表数据";
return;
}
await fetchChartInterpretationOnce(payload, aiContentRight2, isAiLoadingRight2);
};
//资助经费变化情况
......@@ -463,8 +883,10 @@ const initRightLine = (data, show) => {
name: orgName,
type: "line",
data: values,
symbol: "none",
showSymbol: false,
smooth: true,
symbol: "emptyCircle",
showSymbol: true,
symbolSize: 6,
endLabel: {
show: true,
formatter: orgName, // 只显示 orgName
......@@ -522,30 +944,55 @@ const initRightLine = (data, show) => {
};
if (show == true) {
let rightChart = echarts.init(rightChartRef.value);
if (rightChart) rightChart.dispose();
rightChart = echarts.init(rightChartRef.value);
rightChart.setOption(option);
nextTick(() => rightChart && rightChart.resize());
} else {
let rightChart1 = echarts.init(rightChartRef1.value);
if (rightChart1) rightChart1.dispose();
rightChart1 = echarts.init(rightChartRef1.value);
rightChart1.setOption(option);
nextTick(() => rightChart1 && rightChart1.resize());
}
};
const leftSankeyRef = ref(null);
const value2 = ref(2025);
const left2RawSankey = ref([]);
const hasLeft2ChartData = computed(() => isNonEmptyArray(left2RawSankey.value));
// 机构资助领域情况
const handleGetOrgFundsArea = async () => {
try {
let params = {
year: value2.value
year: normalizeYearParam(value2.value)
}
const res = await getOrgFundsArea(params);
console.log("机构资助领域情况", res);
if (res.code === 200 && res.data) {
initLeftSankey(res.data)
if (isSuccessCode(res)) {
const list = extractArrayData(res);
left2RawSankey.value = list;
if (list.length) {
await nextTick();
initLeftSankey(list)
} else if (leftSankey) {
leftSankey.dispose();
leftSankey = null;
}
} else {
left2RawSankey.value = [];
if (leftSankey) {
leftSankey.dispose();
leftSankey = null;
}
}
} catch (error) {
console.error("获取机构资助领域情况error", error);
left2RawSankey.value = [];
if (leftSankey) {
leftSankey.dispose();
leftSankey = null;
}
}
};
//机构资助领域情况
......@@ -638,6 +1085,7 @@ const initLeftSankey = (data) => {
};
leftSankey.setOption(option);
nextTick(() => leftSankey && leftSankey.resize());
};
......@@ -650,13 +1098,21 @@ const initLeftSankey = (data) => {
// };
onMounted(() => {
handleGetFundField()
handleFindCountryProjectAreaList()
handlegetCountryFundingChange()
handlegetCountryFundProjectChange()
handleGetOrgFundsArea()
handlegetOrgFundStrength()
// 刷新后 AiPane 默认展开:先给出“解读生成中…”占位,再在数据到位后触发解读请求
aiContentLeft1.value = "解读生成中…";
aiContentLeft2.value = "解读生成中…";
aiContentRight1.value = "解读生成中…";
aiContentRight2.value = "解读生成中…";
// 先拉数据;每块数据到位后立即触发一次 AI 解读(不必等其它块完成)
void handleGetFundField().then(() => handleSwitchAiLeft1(true));
void handleFindCountryProjectAreaList();
void handlegetCountryFundingChange().then(() => handleSwitchAiRight1(true));
void handlegetCountryFundProjectChange();
void handleGetOrgFundsArea().then(() => handleSwitchAiLeft2(true));
void handlegetOrgFundStrength().then(() => handleSwitchAiRight2(true));
});
// onBeforeUnmount(() => {
// window.removeEventListener("resize", handleResize);
......@@ -746,18 +1202,24 @@ onMounted(() => {
.left-main {
width: 792px;
height: 412px;
padding: 52px 60px 78px 61px;
box-sizing: border-box;
/* 对齐智库概览-数据总览内边距 */
padding: 24px 24px 65px 24px;
position: relative;
.left-main-echarts {
width: 780px;
height: 350px;
width: 100%;
height: 100%;
}
}
.left-main1 {
width: 792px;
height: 412px;
padding: 30px 30px 30px 30px;
box-sizing: border-box;
/* 对齐智库概览-数据总览内边距 */
padding: 24px 24px 65px 24px;
position: relative;
.left-sankey-echarts {
width: 100%;
......@@ -825,14 +1287,16 @@ onMounted(() => {
.right-main {
width: 792px;
height: 421px;
padding: 40px 5px 30px 22px;
height: 412px;
box-sizing: border-box;
/* 对齐智库概览-数据总览内边距 */
padding: 24px 24px 65px 24px;
position: relative;
.right-main-echarts {
/* 矢量 476 */
width: 780px;
height: 350px;
width: 100%;
height: 100%;
}
.right-main-tit {
......@@ -849,15 +1313,16 @@ onMounted(() => {
.right-main1 {
width: 792px;
height: 421px;
padding: 20px 20px;
height: 412px;
/* 对齐智库概览-数据总览内边距 */
padding: 24px 24px 65px 24px;
position: relative;
box-sizing: border-box;
.right-boxplot-echarts {
width: 100%;
height: 100%;
min-height: 300px;
height: 323px;
}
.right-main1-tit {
......@@ -876,6 +1341,50 @@ onMounted(() => {
}
}
/* 数据总览内:TipTab 与 AI 解读(尽量复用智库概览的定位) */
.source {
position: absolute;
left: 24px;
bottom: 21px;
z-index: 2;
}
.chart-box {
position: absolute;
right: 0px;
bottom: 18px;
width: 74px;
height: 28px;
z-index: 3;
.btn-box {
width: 74px;
height: 28px;
}
.content-box {
width: 792px;
position: absolute;
right: 0;
bottom: -18px;
}
}
.datasub-el-empty {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
margin: 0;
z-index: 5;
:deep(.el-empty__description) {
margin-top: 8px;
}
}
.btn {
position: absolute;
top: 11px;
......
......@@ -6,7 +6,7 @@
{{ item.orgName }}
</div>
</div>
<div class="select-box">
<div class="reslib-sort-box">
<div class="paixu-btn" @click="handleSwithSort()">
<div class="icon1">
<img v-if="sort" src="@/assets/icons/shengxu1.png" alt="" />
......@@ -21,42 +21,41 @@
</div>
<div class="main">
<div class="left">
<div class="left-ti1"></div>
<div class="left-ti2"></div>
<!-- <div class="left-title">项目经费</div>
<div class="left-content">
<div v-for="(item, i) in dataList" :key="item.id" class="left-item">
<input type="checkbox" checked />{{ item.name }}
<div class="select-box">
<div class="header">
<div class="icon"></div>
<div class="title">科技领域</div>
</div>
</div> -->
<div class="left-title cl1">涉及领域</div>
<div class="left-content">
<el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" class="all-checkbox"
@change="handleCheckAllChange">
全部领域
<div class="select-main">
<el-checkbox-group class="checkbox-group" :model-value="selectedAreaListModel"
@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" v-model="selectedAreaList" :label="research.id"
@change="handleCheckedAreaChange()" class="filter-checkbox">
<el-checkbox v-for="research in areaList" :key="research.id" class="filter-checkbox" :label="research.id">
{{ research.name }}
</el-checkbox>
<!-- <div v-for="(item, i) in areaList" :key="item.id" class="left-item">
<input type="checkbox" :checked="i === 0" />{{ item.name }}
</div> -->
</el-checkbox-group>
</div>
</div>
<div class="left-title cl1">发布时间</div>
<div class="left-content">
<el-checkbox v-model="checkAllTime" class="all-checkbox" :indeterminate="isIndeterminateTime"
@change="handleCheckAllChangeTime">
全部时间
<div class="select-box">
<div class="header">
<div class="icon"></div>
<div class="title">发布时间</div>
</div>
<div class="select-main">
<el-checkbox-group class="checkbox-group" :model-value="selectedPubTimeListModel"
@change="handleTimeGroupChange">
<el-checkbox class="filter-checkbox all-checkbox" :label="RESOURCE_FILTER_ALL_TIME">
{{ RESOURCE_FILTER_ALL_TIME }}
</el-checkbox>
<el-checkbox-group v-model="selectedPubTimeList">
<el-checkbox v-for="time in pubTimeList" :key="time.id" :label="time.id" class="filter-checkbox"
@change="handleCheckedAreaChangeTime()">
<el-checkbox v-for="time in pubTimeList" :key="time.id" class="filter-checkbox" :label="time.id">
{{ time.name }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</div>
<div class="right">
<div class="right-title">
<img src="./assets/icon01.png" alt="" />
......@@ -68,29 +67,22 @@
<div class="right-item-title">{{ item.projectName }}</div>
<div class="right-item-content">{{ item.abstractContent }}</div>
<div class="right-item-pie">
<div v-for="(pie, i) in item.areaList" :key="i" class="right-item-pie-item" :class="{
cl1: pie === '新材料',
cl2: pie === '人工智能',
cl3: pie === '量子科技',
cl4: pie === '能源',
cl5: pie === '生物科技',
cl6: pie === '航空航天'
}">
{{ pie }}
</div>
<AreaTag v-for="(val, idx) in item.areaList" :key="idx" :tagName="val" />
</div>
<div class="right-item-time">{{ item.publicationDate }}</div>
<div class="right-item-money"
:style="{ color: item.amount <= 1000 ? 'rgba(232, 189, 11, 1)' : item.amount <= 10000 ? 'rgba(255, 149, 77, 1)' : 'rgba(206, 79, 81, 1)' }">
{{ '$' + item.amount + '万' }}</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>
</div>
</div>
</div>
</div>
......@@ -101,49 +93,35 @@ import { ref, onMounted } from "vue";
import {
getProjectListNew, geFundSourceOrg, getAreaType
} from "@/api/scientificFunding/overview";
import {
RESOURCE_FILTER_ALL_AREA,
RESOURCE_FILTER_ALL_TIME,
RESOURCE_FILTER_EARLIER,
normalizeExclusiveAllOption,
stripAllAreaForRequest,
stripAllTimeForRequest,
isSelectionCoveringAllOptions,
expandEarlierNumericYears
} from "@/views/thinkTank/utils/resourceLibraryFilters";
/** 领域字典与接口 arealist 使用数字 id(1、2、3…) */
const normalizeAreaId = (id) => {
const n = Number(id);
return Number.isFinite(n) ? n : id;
};
/** 请求用:仅保留合法整数领域 id */
const toAreaIdListForRequest = (ids) => {
const list = Array.isArray(ids) ? ids : [];
return list
.map((id) => normalizeAreaId(id))
.filter((id) => typeof id === "number" && Number.isInteger(id));
};
const navList = ref([]);
const activeItem = ref("");
const areaList = ref([
{
id: 1,
name: "全部领域"
},
{
id: 2,
name: "人工智能"
},
{
id: 3,
name: "集成电路"
},
{
id: 4,
name: "通信网络"
},
{
id: 5,
name: "量子科技"
},
{
id: 6,
name: "能源"
},
{
id: 7,
name: "生物科技"
},
{
id: 8,
name: "航空航天"
},
{
id: 9,
name: "海洋"
}
]);
const areaList = ref([]);
// 来源机构列表
......@@ -160,99 +138,97 @@ const handleGeFundSourceOrg = async () => {
}
};
const checkAll = ref(false);
const isIndeterminate = ref(true);
const selectedAreaList = ref([]);
const handleCheckAllChange = val => {
// console.log(val, "handleCheckAllChange");
if (val) {
isIndeterminate.value = false;
selectedAreaList.value.length !== areaList.value.length
? (selectedAreaList.value = areaList.value.map(obj => obj.id))
: "";
} else {
selectedAreaList.value = [];
}
// selectedAreaList.value = val ? areaList : []
// isIndeterminate.value = false
const selectedAreaListModel = ref([RESOURCE_FILTER_ALL_AREA]);
const selectedPubTimeListModel = ref([RESOURCE_FILTER_ALL_TIME]);
const handleAreaGroupChange = (val) => {
selectedAreaListModel.value = normalizeExclusiveAllOption(val, RESOURCE_FILTER_ALL_AREA).map((item) =>
item === RESOURCE_FILTER_ALL_AREA ? item : normalizeAreaId(item)
);
handleGetProjectListNew();
};
const handleCheckedAreaChange = () => {
// console.log(selectedAreaList.value, "handleCheckedAreaChange");
console.log(selectedAreaList.value, "当前选中的领域");
selectedAreaList.value.length !== areaList.value.length
? (isIndeterminate.value = true)
: ((checkAll.value = true), (isIndeterminate.value = false));
const handleTimeGroupChange = (val) => {
selectedPubTimeListModel.value = normalizeExclusiveAllOption(val, RESOURCE_FILTER_ALL_TIME);
handleGetProjectListNew();
};
const pubTimeList = ref([
{
id: 2025,
name: "2025"
name: "2025"
},
{
id: 2024,
name: "2024"
name: "2024"
},
{
id: 2023,
name: "2023"
name: "2023"
},
{
id: 2022,
name: "2022"
name: "2022"
},
{
id: 2021,
name: "2021"
name: "2021年"
},
{
id: RESOURCE_FILTER_EARLIER,
name: RESOURCE_FILTER_EARLIER
}
// {
// id: "更早时间",
// name: "更早时间"
// }
]);
const selectedPubTimeList = ref([""]);
const checkAllTime = ref(false);
const isIndeterminateTime = ref(true);
/** 选择「全部时间」时,yearlist 传 2000~2025 逐年 */
const YEAR_ALL_RANGE_START = 2000;
const YEAR_ALL_RANGE_END = 2025;
const buildYearlistForRequest = (selectedTimeModel) => {
const strippedTime = stripAllTimeForRequest(selectedTimeModel);
// 仅勾选「全部时间」、未选具体年份时,传 2000~2025 逐年
if (strippedTime.length === 0) {
const out = [];
for (let y = YEAR_ALL_RANGE_START; y <= YEAR_ALL_RANGE_END; y += 1) {
out.push(String(y));
}
return out;
}
const hasEarlier = strippedTime.includes(RESOURCE_FILTER_EARLIER);
const numericOnly = strippedTime.filter((id) => id !== RESOURCE_FILTER_EARLIER);
const strippedNums = numericOnly
.map((id) => Number(id))
.filter((n) => Number.isInteger(n));
// 勾选「更早」:展开为 2000~2020,并与已选数字年合并(可与其他年份同时选)
if (hasEarlier) {
const yearsSet = new Set(expandEarlierNumericYears());
strippedNums.forEach((n) => yearsSet.add(n));
return [...yearsSet]
.sort((a, b) => a - b)
.map((n) => String(n));
}
return strippedNums.map((n) => String(n));
};
const sort = ref(false);
const handleSwithSort = () => {
sort.value = !sort.value;
handleGetProjectListNew();
};
const handleCheckAllChangeTime = val => {
// console.log(val, "handleCheckAllChange");
if (val) {
isIndeterminateTime.value = false;
selectedPubTimeList.value.length !== pubTimeList.value.length
? (selectedPubTimeList.value = pubTimeList.value.map(obj => obj.id))
: "";
} else {
selectedPubTimeList.value = [];
}
// selectedAreaList.value = val ? areaList : []
// isIndeterminate.value = false
handleGetProjectListNew();
};
const handleCheckedAreaChangeTime = () => {
// console.log(selectedAreaList.value, "handleCheckedAreaChange");
console.log(selectedPubTimeList.value, "当前选中的时间");
selectedPubTimeList.value.length !== pubTimeList.value.length
? (isIndeterminateTime.value = true)
: ((checkAllTime.value = true), (isIndeterminateTime.value = false));
handleGetProjectListNew();
};
// 获取行业领域列表
const handleGetAreaType = async () => {
try {
const res = await getAreaType();
console.log("获取行业领域列表", res);
if (res.code === 200 && res.data) {
areaList.value = res.data
areaList.value = res.data.map((row) => ({
...row,
id: normalizeAreaId(row.id)
}));
}
} catch (error) {
console.error("获取行业领域列表error", error);
......@@ -270,11 +246,22 @@ const handlePageChange = p => {
// 资助体系v2.0:资助项目列表分页
const handleGetProjectListNew = async () => {
try {
const strippedArea = toAreaIdListForRequest(
stripAllAreaForRequest(selectedAreaListModel.value)
);
const allAreaIds = toAreaIdListForRequest(areaList.value.map((obj) => obj.id));
const arealist =
strippedArea.length === 0 || isSelectionCoveringAllOptions(strippedArea, allAreaIds)
? allAreaIds
: strippedArea;
const yearlist = buildYearlistForRequest(selectedPubTimeListModel.value);
let params = {
arealist: selectedAreaList.value,
arealist,
currentPage: currentPage.value,
pageSize: 10,
yearlist: selectedPubTimeList.value.map(item => item.toString().trim()).filter(item => item !== ""),
yearlist,
funSort: sort.value ? 'desc' : 'asc',
orgId: activeItem.value
}
......@@ -305,11 +292,10 @@ onMounted(async () => {
.reslib-page {
width: 1600px;
height: 1565px;
position: relative;
.select-box {
position: relative;
.reslib-sort-box {
width: 128px;
position: absolute;
top: 7px;
......@@ -370,15 +356,15 @@ onMounted(async () => {
}
.nav {
width: calc(100% - 100px);
height: 42px;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 34px;
.nav-item {
width: 100%;
text-align: center;
cursor: pointer;
padding: 8px 20px;
......@@ -397,133 +383,85 @@ onMounted(async () => {
}
}
.select {
width: 128px;
position: absolute;
top: 7px;
right: 0px;
}
.main {
width: 1600px;
height: 1489px;
display: flex;
margin-bottom: 100px;
.left {
width: 300px;
width: 360px;
margin-right: 16px;
height: 100%;
padding-bottom: 24px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
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);
background: rgba(255, 255, 255, 1);
position: relative;
.left-ti1 {
width: 8px;
height: 16px;
background-color: rgb(5, 95, 194);
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
position: absolute;
top: 17px;
left: 0px;
}
.select-box {
margin-top: 16px;
.left-ti2 {
.header {
display: flex;
gap: 17px;
.icon {
margin-top: 4px;
width: 8px;
height: 16px;
background-color: rgb(5, 95, 194);
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
position: absolute;
top: 207px;
left: 0px;
background: var(--color-main-active);
border-radius: 0 4px 4px 0;
}
.left-title {
margin-left: 25px;
color: rgb(5, 95, 194);
.title {
height: 24px;
color: var(--color-main-active);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 24px;
margin-top: 13px;
letter-spacing: 1px;
text-align: left;
}
}
.left-content {
width: 253px;
// height: 132px;
margin-left: 25px;
margin-top: 13px;
display: flex;
flex-wrap: wrap;
/* 允许内容换行 */
justify-content: space-between;
/* 两端对齐 */
.left-item {
white-space: nowrap;
/* 保持在一行内 */
overflow: hidden;
/* 隐藏超出部分 */
text-overflow: ellipsis;
/* 超出部分显示省略号 */
width: calc(50% - 8px);
/* 每个选项占一半宽度,减去间距 */
height: 30px;
margin-bottom: 4px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
.select-main {
margin-left: 24px;
margin-top: 12px;
input[type="checkbox"] {
-webkit-appearance: none;
appearance: none;
width: 14px;
height: 14px;
margin-right: 8px;
border: 1px solid rgb(200, 204, 210);
border-radius: 4px;
background-color: #fff;
vertical-align: middle;
}
.checkbox-group {
display: grid;
grid-template-columns: repeat(2, 160px);
gap: 8px 4px;
input[type="checkbox"]:checked {
background-color: rgb(5, 95, 194);
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
margin-right: 17px;
:deep(.all-checkbox) {
width: 160px;
height: 24px;
margin: 0;
}
input[type="checkbox"]:checked::after {
content: "";
display: block;
width: 4px;
height: 8px;
margin: 1px auto 0;
border: 2px solid #fff;
border-top: none;
border-left: none;
transform: rotate(45deg);
:deep(.filter-checkbox) {
width: 160px;
height: 24px;
margin-right: 0 !important;
}
}
}
.cl1 {
margin-top: 21px;
}
}
.right {
width: 1284px;
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;
......@@ -550,13 +488,13 @@ onMounted(async () => {
}
.right-main {
width: 1284px;
height: 1441px;
width: 1224px;
padding: 19px 34px 20px 29px;
position: relative;
.right-item {
width: 1221px;
width: 1161px;
height: 124px;
border-bottom: 1px solid rgb(234, 236, 238);
margin-bottom: 8px;
......@@ -588,7 +526,7 @@ onMounted(async () => {
/* 隐藏超出部分 */
text-overflow: ellipsis;
/* 超出部分显示省略号 */
width: 90%;
width: 1112px;
/* 设置一个固定的宽度或百分比 */
position: absolute;
top: 44px;
......@@ -605,6 +543,7 @@ onMounted(async () => {
top: 76px;
left: 56px;
display: flex;
gap: 8px;
.right-item-pie-item {
padding: 2px 8px;
......@@ -677,15 +616,16 @@ onMounted(async () => {
}
.page {
width: 1221px;
width: 1161px;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
position: absolute;
bottom: 20px;
left: 20px;
padding-left: 11px;
margin-top: 29px;
.count {
font-size: 16px;
......@@ -749,7 +689,10 @@ onMounted(async () => {
background-color: #fff;
}
}
}
}
}
}
......
......@@ -14,14 +14,8 @@
<div class="main-content" ref="containerRef">
<div class="home-top-bg"></div>
<!-- 搜索栏部分 -->
<SearchContainer
style="margin-bottom: 48px; height: fit-content"
v-if="containerRef"
:countInfo="countInfo"
placeholder="搜索科研资助实体、资助记录"
:containerRef="containerRef"
areaName=""
/>
<SearchContainer style="margin-bottom: 48px; height: fit-content" v-if="containerRef" :countInfo="countInfo"
placeholder="搜索科研资助实体、资助记录" :containerRef="containerRef" areaName="" />
<!-- <div class="search"> -->
<!-- <div class="search-main">
......@@ -78,7 +72,8 @@
<!-- </div> -->
<!-- 6个数据 -->
<div class="data">
<div v-for="(item, index) in dataList" :key="item.id" class="data-item">
<div v-for="(item, index) in dataList" :key="item.orgId || item.id" class="data-item"
@click="handleClickOrg(item)">
<img v-if="item.logoUrl && /\\.(jpe?g|png)$/i.test(item.logoUrl)" :src="item.logoUrl" alt="" />
<img v-else src="./assets/images/nullcorpimg.png" alt="" />
<div class="data-text-item">
......@@ -91,28 +86,28 @@
</div>
<!-- 最新动态 -->
<div class="newdata" id="position1">
<com-title title="最新动态" />
<com-title title="最新动态" style="width: 1600px;" />
<div class="newdata-main">
<newData />
</div>
</div>
<!-- 资讯要问 -->
<div class="ask" id="position2">
<com-title title="资讯要闻" />
<com-title title="资讯要闻" style="width: 1600px;" />
<div class="ask-main">
<askPage />
</div>
</div>
<!-- 数据总览 -->
<div class="datasub" id="position3">
<com-title title="数据总览" />
<com-title title="数据总览" style="width: 1600px;" />
<div class="datasub-main">
<dataSub />
</div>
</div>
<!-- 资源库 -->
<div class="reslib" id="position4">
<com-title title="资源库" />
<com-title title="资源库" style="width: 1600px;" />
<div class="reslib-main">
<resLib />
</div>
......@@ -170,6 +165,21 @@ const handleBackHome = () => {
path: "/overview"
});
};
// 点击机构卡片跳转机构详情
const handleClickOrg = (item) => {
const orgId = item?.orgId;
if (!orgId) return;
// Institution 路由开启了 dynamicTitle,这里提前写入标题,避免沿用上一次的“白宫”等旧值
const title = item?.orgName || "";
window.sessionStorage.setItem("institutionTabName", title);
window.sessionStorage.setItem("curTabName", title);
const route = router.resolve({
path: "/institution",
query: { id: orgId, name: title }
});
window.open(route.href, "_blank");
};
// 固定数据
const dataList = ref([
{
......@@ -576,12 +586,12 @@ onMounted(async () => {
.reslib {
width: 1600px;
height: 1633px;
margin: 0 auto 0px auto;
.reslib-main {
width: 1600px;
height: 1565px;
margin-top: 26px;
}
}
......
......@@ -399,7 +399,8 @@ const reportAuthors = computed(() => {
// 点击报告作者头像,跳转到人物主页
// 与核心研究人员逻辑一致:核心依赖 personId,本页面依赖作者的 id(作为 personId 传入)
const handleClickReportAuthor = async (author) => {
const personId = author?.id;
const personId = author?.personId;
if (!personId) return;
......
......@@ -96,6 +96,7 @@
</div>
<div class="divider" v-if="index !== hearingData.length - 1"></div>
</div>
</div>
</div>
<div class="right-footer">
......@@ -107,6 +108,7 @@
@current-change="handleCurrentChange" :current-page="currentPage" />
</div>
</div>
</div>
</div>
</template>
......@@ -287,6 +289,12 @@ const handleToReportDetail = item => {
.main-content {
display: flex;
gap: 16px;
height: 100%;
margin-bottom: 100px;
.left {
width: 360px;
......@@ -299,6 +307,7 @@ const handleToReportDetail = item => {
background: rgba(255, 255, 255, 1);
position: relative;
.select-research-box {
width: 360px;
height: 100%;
......@@ -480,14 +489,20 @@ const handleToReportDetail = item => {
.right {
width: 1224px;
height: 1377px;
.card-box {
width: 100%;
height: 1248px;
height: 100%;
display: flex;
background: rgba(255, 255, 255, 1);
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
......@@ -495,12 +510,15 @@ const handleToReportDetail = item => {
box-shadow: 0px 0px 20px 0px rgba(94, 95, 95, 0.1);
background: rgba(255, 255, 255, 1);
.card-content {
width: 1211px;
height: 1067px;
margin-top: 33px;
margin-top: 33px;
margin-left: 37px;
padding-bottom: 27px;
.card-item {
width: 100%;
......@@ -585,8 +603,13 @@ const handleToReportDetail = item => {
}
}
.right-footer {
margin-top: 43px;
margin-top: 35px;
display: flex;
justify-content: space-between;
......@@ -601,7 +624,6 @@ const handleToReportDetail = item => {
text-align: left;
}
}
}
}
:deep(.el-checkbox) {
......
......@@ -58,6 +58,7 @@
<div class="right">
<div class="card-box">
<div class="card-content">
<div v-for="(item, index) in hearingData" :key="item.id ?? index">
<div class="card-item">
<img class="card-item-img" :src="item.coverImgUrl" alt="report image" />
......@@ -80,9 +81,10 @@
</div>
<div class="divider" v-if="index !== hearingData.length - 1"></div>
</div>
</div>
</div>
</div>
<div class="right-footer">
<div class="info">
{{ hearingData.length }} 篇智库报告
......@@ -92,6 +94,7 @@
@current-change="handlePageChange" :current-page="currentPage" />
</div>
</div>
</div>
</div>
</template>
......@@ -186,6 +189,7 @@ const handlePageChange = page => {
.home-main-footer-main {
margin: 0 auto;
margin-top: 36px;
width: 1600px;
display: flex;
gap: 16px;
......@@ -270,11 +274,11 @@ const handlePageChange = page => {
.right {
width: 1224px;
height: 1377px;
.card-box {
width: 100%;
height: 1134px;
display: flex;
background: rgba(255, 255, 255, 1);
box-sizing: border-box;
......@@ -282,16 +286,22 @@ const handlePageChange = page => {
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(94, 95, 95, 0.1);
padding-right: 36px;
height: 100%;
.card-content {
width: 1211px;
height: 1067px;
margin-top: 33px;
margin-left: 37px;
padding-bottom: 27px;
}
}
.right-footer {
}
.right-footer {
margin-top: 43px;
display: flex;
justify-content: space-between;
......@@ -306,7 +316,6 @@ const handlePageChange = page => {
letter-spacing: 0px;
text-align: left;
}
}
}
.card-item {
......
......@@ -369,8 +369,9 @@
<ThinkTankCongressHearingOverview v-else-if="activeCate === '国会听证会'" :key="`congress-${resourceTabResetKey}`"
:hearing-data="hearingData" :research-type-list="areaList" :research-time-list="pubTimeList"
v-model:selectedAreaList="congressSelectedAreaList" v-model:selectedPubTimeList="congressSelectedPubTimeList"
:total="congressTotal" :current-page="congressCurrentPage" @filter-change="handleCongressFilterChange"
v-model:selectedAreaList="congressSelectedAreaList"
v-model:selectedPubTimeList="congressSelectedPubTimeList" :total="congressTotal"
:current-page="congressCurrentPage" @filter-change="handleCongressFilterChange"
@page-change="handleCongressCurrentChange" @report-click="handleToHearingDetail" />
<ThinkTankPolicyAdviceOverview v-else :key="`policy-${resourceTabResetKey}`" :research-type-list="areaList"
......@@ -2194,7 +2195,7 @@ const handleSearch = () => {
// 下钻至数据资源库
const handleToDataLibrary = (item) => {
if(!item.reportNumber) {
if (!item.reportNumber) {
ElMessage.warning('当前智库没有相关报告!')
return
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论