提交 40ce6cb6 authored 作者: 刘宇琪's avatar 刘宇琪

提交master分支的改动:创新主题详情页 词云组件更新 刘宇琪

上级 bd4bf45d
...@@ -42,8 +42,8 @@ ...@@ -42,8 +42,8 @@
<el-option v-for="item in num" :key="item" :label="item" :value="item" /> <el-option v-for="item in num" :key="item" :label="item" :value="item" />
</el-select> </el-select>
</template> </template>
<div class="echarts" id="wordCloudChart"> <div class="echarts" ><WordCloudChart v-if="wordLoading" :data="characterView"/></div>
</div>
</AnalysisBox> </AnalysisBox>
<AnalysisBox title=" 金钱来源" width="1064px" height="512px" :show-all-btn="false" class="left-center"> <AnalysisBox title=" 金钱来源" width="1064px" height="512px" :show-all-btn="false" class="left-center">
...@@ -305,6 +305,7 @@ import HistoricalProposal from "./components/historicalProposal/components/BillT ...@@ -305,6 +305,7 @@ import HistoricalProposal from "./components/historicalProposal/components/BillT
import PotentialNews from './components/historicalProposal/components/PotentialNews.vue' import PotentialNews from './components/historicalProposal/components/PotentialNews.vue'
import getWordCloudChart from "../../utils/worldCloudChart"; import getWordCloudChart from "../../utils/worldCloudChart";
import setChart from "@/utils/setChart"; import setChart from "@/utils/setChart";
import WordCloudChart from "@/components/base/WordCloundChart/index.vue"
import { import {
getCharacterGlobalInfo, getCharacterGlobalInfo,
getCharacterBasicInfo, getCharacterBasicInfo,
...@@ -434,7 +435,7 @@ const getCharacterBasicInfoFn = async () => { ...@@ -434,7 +435,7 @@ const getCharacterBasicInfoFn = async () => {
console.error(error); console.error(error);
} }
}; };
const wordLoading=ref(false)
// 获取人物观点 // 获取人物观点
const characterView = ref({}); const characterView = ref({});
const getCharacterViewFn = async () => { const getCharacterViewFn = async () => {
...@@ -444,6 +445,7 @@ const getCharacterViewFn = async () => { ...@@ -444,6 +445,7 @@ const getCharacterViewFn = async () => {
if (numActive.value !== '全部') { if (numActive.value !== '全部') {
params.year = numActive.value; params.year = numActive.value;
} }
wordLoading.value=false
try { try {
const res = await getCharacterView(params); const res = await getCharacterView(params);
if (res.code === 200) { if (res.code === 200) {
...@@ -456,6 +458,7 @@ const getCharacterViewFn = async () => { ...@@ -456,6 +458,7 @@ const getCharacterViewFn = async () => {
}; };
}); });
} }
wordLoading.value=true
} }
} catch (error) { } catch (error) {
...@@ -465,18 +468,15 @@ const getCharacterViewFn = async () => { ...@@ -465,18 +468,15 @@ const getCharacterViewFn = async () => {
const handleCharacterView = async () => { const handleCharacterView = async () => {
await getCharacterViewFn(); await getCharacterViewFn();
const wordCloudChart = getWordCloudChart(characterView.value); // const wordCloudChart = getWordCloudChart(characterView.value);
setChart(wordCloudChart, "wordCloudChart"); // setChart(wordCloudChart, "wordCloudChart");
}; };
const handleChangeYear = () => { const handleChangeYear = () => {
characterView.value = [] characterView.value = []
handleCharacterView() handleCharacterView()
} }
const yearList = ref([ const yearList = ref([
{ {
label: "全部", label: "全部",
......
...@@ -15,16 +15,9 @@ ...@@ -15,16 +15,9 @@
<p>{{ characterInfo.description }}</p> <p>{{ characterInfo.description }}</p>
</div> </div>
<div class="domain"> <div class="domain">
<p v-for="item in characterInfo.industryList" :key="item" class="cl1" :class="{
cl1: item.status === '1', <AreaTag v-for="tag in characterInfo.industryList" :key="tag"
cl2: item.status === '2', :tag-name="tag.industryName" />
cl3: item.status === '3',
cl4: item.status === '4',
cl5: item.status === '5',
cl6: item.status === '6'
}">
{{ item.industryName }}
</p>
</div> </div>
</div> </div>
</div> </div>
...@@ -35,7 +28,7 @@ ...@@ -35,7 +28,7 @@
</div> </div>
</div> </div>
<!-- 人物详情 --> <!-- 人物详情 -->
<div class="info-content" v-if="infoActive === '人物详情'"> <div class="info-content" v-show="infoActive === '人物详情'">
<div class="left"> <div class="left">
<!-- 科技观点 --> <!-- 科技观点 -->
<AnalysisBox title="科技观点" width="1064px" height="300px" :show-all-btn="false" class="left-top"> <AnalysisBox title="科技观点" width="1064px" height="300px" :show-all-btn="false" class="left-top">
...@@ -52,7 +45,7 @@ ...@@ -52,7 +45,7 @@
<el-option v-for="item in num" :key="item" :label="item" :value="item" /> <el-option v-for="item in num" :key="item" :label="item" :value="item" />
</el-select> </el-select>
</template> </template>
<div class="echarts" id="wordCloudChart"></div> <div class="echarts" v-if="wordloading" ><WordCloudChart :data="characterView"></WordCloudChart></div>
</AnalysisBox> </AnalysisBox>
<!-- 最新动态 --> <!-- 最新动态 -->
...@@ -170,8 +163,9 @@ ...@@ -170,8 +163,9 @@
<template v-if="resumeType === 'career'"> <template v-if="resumeType === 'career'">
<div v-for="item in currentResumeList" :key="item.startTime" class="content-item"> <div v-for="item in currentResumeList" :key="item.startTime" class="content-item">
<img src="./assets/icon01.png" alt="" class="image01" /> <img src="./assets/icon01.png" alt="" class="image01" />
<div class="content-item-time">{{ item.startTime + '-' + getendTime(item) }}</div> <div class="content-item-time">{{ item.startTime.split('-')[0] + '-' + getendTime(item) }}</div>
<div class="content-item-title">{{ item.orgName + '|' + item.jobName }}</div> <div class="content-item-title"><span>{{item.orgName}}</span><span style="margin-left: 5px;margin-right: 5px;">|</span><span>{{ item.jobName }}</span></div>
<!-- <div class="content-item-title">{{ item.orgName + '|' + item.jobName }}</div> -->
<div class="content-item-content">{{ item.content }}</div> <div class="content-item-content">{{ item.content }}</div>
<div class="content-item-door" v-if="item.door"> <div class="content-item-door" v-if="item.door">
<img src="./assets/icon02.png" alt="" /> <img src="./assets/icon02.png" alt="" />
...@@ -183,7 +177,7 @@ ...@@ -183,7 +177,7 @@
<template v-else> <template v-else>
<div v-for="(item, index) in currentResumeList" :key="index" class="content-item"> <div v-for="(item, index) in currentResumeList" :key="index" class="content-item">
<img src="./assets/icon01.png" alt="" class="image01" /> <img src="./assets/icon01.png" alt="" class="image01" />
<div class="content-item-time">{{ item.startTime + '-' + item.endTime }}</div> <div class="content-item-time">{{ item.startTime.split('-')[0] + '-' + getendTime(item) }}</div>
<div class="content-item-title">{{ item.school+'('+item.country+')' }}</div> <div class="content-item-title">{{ item.school+'('+item.country+')' }}</div>
<div class="content-item-content">{{ item.description }}</div> <div class="content-item-content">{{ item.description }}</div>
<!-- <div class="content-item-door" v-if="item.country"> <!-- <div class="content-item-door" v-if="item.country">
...@@ -207,7 +201,7 @@ ...@@ -207,7 +201,7 @@
<div class="viewpoint-header"> <div class="viewpoint-header">
<div class="viewpoint-title"> <div class="viewpoint-title">
<span class="viewpoint-tag">#{{ currentTag }}</span> <span class="viewpoint-tag">#{{ currentTag }}</span>
<span class="viewpoint-label">相关领域标签</span> <span class="viewpoint-label"> 相关领域标签</span>
</div> </div>
<div class="viewpoint-close" @click="dialogVisible = false"> <div class="viewpoint-close" @click="dialogVisible = false">
<el-icon :size="16"> <el-icon :size="16">
...@@ -256,6 +250,7 @@ import { ...@@ -256,6 +250,7 @@ import {
getareaType getareaType
} from "@/api/characterPage/characterPage.js"; } from "@/api/characterPage/characterPage.js";
import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue' import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue'
import WordCloudChart from "@/components/base/WordCloundChart/index.vue"
import Musk from "./assets/Musk.png"; import Musk from "./assets/Musk.png";
import spaceX from "./assets/spaceX.png"; import spaceX from "./assets/spaceX.png";
import tesla from "./assets/tesla.png"; import tesla from "./assets/tesla.png";
...@@ -274,6 +269,7 @@ import { useRoute } from 'vue-router'; ...@@ -274,6 +269,7 @@ import { useRoute } from 'vue-router';
const route = useRoute(); const route = useRoute();
const areaList = ref([]) const areaList = ref([])
const personId = ref(route.query.personId || "Y000064"); const personId = ref(route.query.personId || "Y000064");
const wordloading=ref(false)
const handleGetAreaType = async () => { const handleGetAreaType = async () => {
try { try {
const res = await getareaType(); const res = await getareaType();
...@@ -367,7 +363,7 @@ const getendTime=(item)=>{ ...@@ -367,7 +363,7 @@ const getendTime=(item)=>{
if(item.endTime==null){ if(item.endTime==null){
return item.endTimeStatus==0?'至今':'未知' return item.endTimeStatus==0?'至今':'未知'
}else{ }else{
return item.endTime return item.endTime.split('-')[0]
} }
} }
...@@ -435,9 +431,10 @@ const getCharacterViewFn = async () => { ...@@ -435,9 +431,10 @@ const getCharacterViewFn = async () => {
personId: personId.value personId: personId.value
}; };
if (numActive.value !== '全部') { if (numActive.value !== '全部时间') {
params.year = numActive.value; params.year = numActive.value;
} }
wordloading.value=false
try { try {
const res = await getCharacterView(params); const res = await getCharacterView(params);
if (res.code === 200) { if (res.code === 200) {
...@@ -449,6 +446,7 @@ const getCharacterViewFn = async () => { ...@@ -449,6 +446,7 @@ const getCharacterViewFn = async () => {
value: item.count value: item.count
}; };
}); });
wordloading.value=true
} }
} }
} catch (error) { } catch (error) {
...@@ -459,8 +457,9 @@ const getCharacterViewFn = async () => { ...@@ -459,8 +457,9 @@ const getCharacterViewFn = async () => {
const handleCharacterView = async () => { const handleCharacterView = async () => {
await getCharacterViewFn(); await getCharacterViewFn();
const wordCloudChart = getWordCloudChart(characterView.value); // const wordCloudChart = getWordCloudChart(characterView.value);
setChart(wordCloudChart, "wordCloudChart");
// setChart(wordCloudChart, "wordCloudChart");
}; };
const handleChangeYear = () => { const handleChangeYear = () => {
...@@ -587,8 +586,8 @@ const info = ref(["人物详情", "人物关系"]); ...@@ -587,8 +586,8 @@ const info = ref(["人物详情", "人物关系"]);
// const info = ref(["人物详情", "人物关系", "相关情况"]); // const info = ref(["人物详情", "人物关系", "相关情况"]);
const infoActive = ref("人物详情"); const infoActive = ref("人物详情");
const num = ref(['全部', "2025", "2024", "2023", "2022", "2021", "2020"]); const num = ref(['全部时间' ]);
const numActive = ref("全部"); const numActive = ref("全部时间");
const dialogVisible = ref(false); const dialogVisible = ref(false);
const currentTag = ref(null) const currentTag = ref(null)
...@@ -597,183 +596,19 @@ const handleClickTag = async (tag) => { ...@@ -597,183 +596,19 @@ const handleClickTag = async (tag) => {
dialogVisible.value = true dialogVisible.value = true
await getCharacterFieldViewFn(tag) await getCharacterFieldViewFn(tag)
} }
const newList = ref([ const initTime=()=>{
{ const currentYear = new Date().getFullYear();
id: 1,
title: "能源才是真正的货币。我预测,人工智能和机器人的普及将在 3 年内导致美国出现通缩,并在 20 年内让工作成为可选项。我们需要提前改革教育和社会保障体系,为这一转变做好准备。", const lastFiveYears = [];
titleEn: for (let i = 0; i < 5; i++) {
"Energy is the real currency. I predict that the widespread adoption of AI and robots will lead to deflation in the US within 3 years and make work optional within 20 years. We need to reform the education and social security systems in advance to prepare for this shift.", lastFiveYears.push(String(currentYear - i));
pie: ["人工智能"], }
origin: "X",
time: "2025年10月10日", num.value = ['全部时间', ...lastFiveYears]
type: 1 }
},
{
id: 2,
title: "Neuralink 首位受试者二次植入脑机接口,目标实现下肢自主活动",
content:
"2025 年 11 月 21 日,马斯克宣布 Neuralink 首位人类受试者将接受第二次脑机接口植入手术。新版植入物电极数升级至 3000 个,信号延迟缩短至 15 毫秒,通过 “脑 - 脊信号桥接” 技术激活下肢神经,有望让脊髓损伤患者重新站立,标志着技术从 “操控外部设备” 迈入 “修复自身运动功能” 阶段。",
pie: ["量子科技"],
origin: "华尔街日报",
time: "2025年10月10日",
type: 2
},
{
id: 3,
title: "马斯克预判 AI 财富流向:英伟达、谷歌成关键,入口掌控终极价值",
content:
"2025 年 11 月 30 日,马斯克在播客采访中指出,AI 财富将沿 “芯片→平台→系统→入口” 路径流动。他点名英伟达(算力工具)和谷歌(数据生态)具备核心价值,同时透露 X 平台将打造 “AI 时代超级入口”,整合支付、社交、任务执行等功能,实现 “WeChat++” 愿景。",
pie: ["新能源"],
origin: "金融时报",
time: "2025年10月09日",
type: 2
},
{
id: 4,
title: "特斯拉人形机器人复数定名 “Optimi”,马斯克透露年产亿台目标",
content:
"2025 年 12 月 1 日,马斯克在社交平台确认特斯拉人形机器人 Optimus 的复数形式为 “Optimi”。他重申该机器人将成为特斯拉产量最高的产品,长期年产能目标达数亿台,目前计划在加州、得州工厂分别搭建年产 100 万台和数千万台的生产线。",
pie: ["量子科技"],
origin: "泰晤士报",
time: "2025年10月09日",
type: 2
},
{
id: 5,
title: "究竟要有多少无辜的人死去,美国才能进行改革?我们需要采取激进行动。错误地给非暴力人士贴上 “法西斯” 或 “纳粹” 的标签,应被视为煽动谋杀。这种扣帽子的行为往往被用来为施暴找借口,对社会而言极为危险。",
titleEn:
"How many innocent people have to die before the US reforms? We need radical action. Falsely labeling non-violent people as “fascist” or “Nazi” should be considered incitement to murder. This kind of name-calling is often used to rationalize violence, which is extremely dangerous for society.",
pie: ["人工智能"],
origin: "X",
time: "2025年10月08日",
type: 1
},
{
id: 6,
title: "Grok5 将于 2026 年与《英雄联盟》顶级人类战队对决,规则严格:无数据直读,仅通过摄像头输入;反应时间和点击速度不超过人类水平;必须通过阅读说明书自主学习玩法。目的是测试人工智能的策略与学习能力。",
titleEn:
"Grok5 will play League of Legends against top human teams in 2026, with strict constraints: no data access, only camera input; reaction time and click speed capped at human levels; must learn by reading the manual. The purpose is to test the strategic and learning abilities of AI.",
pie: ["集成电路"],
origin: "X",
time: "2025年10月08日",
type: 1
},
{
id: 7,
title: "SpaceX 频谱收购获 FCC 认可,美国下一代连接技术领先优势强化",
content:
"美国联邦通信委员会(FCC)正式表态支持 SpaceX 收购回声星通信频谱交易。FCC 指出,此次 170 亿美元交易将极大增强行业竞争力,助力美国在下一代连接技术领域巩固领先地位,同时为偏远地区提供更可靠的移动网络覆盖。",
pie: ["量子科技"],
origin: "华盛顿邮报",
time: "2025年10月07日",
type: 2
}
]);
const companyList = ref([
{
id: 1,
cn: "太空探索技术公司",
name: "SpaceX",
logo: spaceX
},
{
id: 2,
cn: "特斯拉",
name: "Tesla",
logo: tesla
},
{
id: 3,
cn: "星链",
name: "Starlink",
logo: starlink
},
{
id: 4,
cn: "X 平台(原推特)",
name: "X (formerly Twitter)",
logo: X
}
]);
// 个人履历
const resumeList = ref([
{
id: 1,
time: "2022-至今",
title: "Twitter/X | 所有者、CTO",
content: "以440亿美元收购Twitter,并担任首席技术官,进行大规模重组和品牌重塑为X。",
door: "旋转门:从硬件科技到社交媒体"
},
{
id: 2,
time: "2004-至今",
title: "Tesla | 董事长、产品架构师,后任CEO",
content: "早期投资者并加入特斯拉,领导产品设计和开发,推动电动汽车革命。推出了多款畅销电动汽车。",
door: "旋转门:从互联网支付到汽车制造"
},
{
id: 3,
time: "2002-至今",
title: "SpaceX | 创始人、CEO兼CTO",
content: "创立太空探索技术公司,目标是降低太空运输成本,最终实现火星殖民。开发了猎鹰系列火箭和龙飞船。",
door: "旋转门:从互联网支付到太空科技"
},
{
id: 4,
time: "1999-2002",
title: "PayPal | 创始人、CEO",
content: "创立太空探索技术公司,目标是降低太空运输成本,最终实现火星殖民。开发了猎鹰系列火箭和龙飞船。",
door: ""
},
{
id: 5,
time: "1995-1999",
title: "Zip2 | 联合创始人",
content: "与弟弟金巴尔·马斯克共同创立了Zip2,为报纸行业提供在线城市指南。1999年康柏以3.07亿美元收购了Zip2。",
door: ""
}
]);
// 弹框数据
const dialogData = ref([
{
id: 1,
name: "山姆・奥特曼",
content: "主张分级监管,反对 “一刀切” 审批;警告美国领先优势有限,电力与基建是关键瓶颈。",
img: img1,
job: "OpenAI CEO"
},
{
id: 2,
name: "布拉德・史密斯",
content: "主张全技术栈监管与基础建设投入,强调美国需在芯片、算法、数据各层保持领先。",
img: img2,
job: "微软副董事长"
},
{
id: 3,
name: "黄仁勋",
content: "认为 AI 处于 “智能基建起步期”,大模型为基础,代理式 AI 为下一阶段;否定 AI 泡沫,强调与实体经济融合。",
img: img3,
job: "NVIDIA CEO"
},
{
id: 4,
name: "杰弗里・辛顿",
content: "担忧 AI 发展过快,警告 “涌现能力” 超预期,可能导致存在性风险;呼吁放缓研发、加强安全。",
img: img4,
job: "图灵奖得主、深度学习先驱"
},
{
id: 5,
name: "李飞飞",
content: "关注 AI 伦理与公平性,强调安全与普惠并重,对 AGI 时间表持谨慎态度。",
img: img5,
job: "斯坦福大学教授"
}
]);
onMounted(() => { onMounted(() => {
initTime()
getCharacterGlobalInfoFn(); getCharacterGlobalInfoFn();
getCharacterBasicInfoFn(); getCharacterBasicInfoFn();
// getCharacterViewFn(); // getCharacterViewFn();
...@@ -791,7 +626,10 @@ onMounted(() => { ...@@ -791,7 +626,10 @@ onMounted(() => {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
:deep(.el-select-dropdown__item) {
text-align: center !important;
justify-content: center;
}
.tech-leader { .tech-leader {
width: 1600px; width: 1600px;
margin: 0 auto; margin: 0 auto;
...@@ -853,7 +691,8 @@ onMounted(() => { ...@@ -853,7 +691,8 @@ onMounted(() => {
.domain { .domain {
font-size: 14px; font-size: 14px;
display: flex;
gap: 8px;
p { p {
display: inline-block; display: inline-block;
padding: 1px 8px; padding: 1px 8px;
...@@ -1012,7 +851,7 @@ onMounted(() => { ...@@ -1012,7 +851,7 @@ onMounted(() => {
padding-right: 50px; padding-right: 50px;
position: relative; position: relative;
z-index: 110; z-index: 110;
margin-top: 10px;
.main-item { .main-item {
width: 1014px; width: 1014px;
margin-bottom: 40px; margin-bottom: 40px;
...@@ -1490,6 +1329,7 @@ onMounted(() => { ...@@ -1490,6 +1329,7 @@ onMounted(() => {
font-weight: 700; font-weight: 700;
color: rgba(59, 65, 75, 1); color: rgba(59, 65, 75, 1);
line-height: 24px; line-height: 24px;
margin-left: 2px;
} }
.viewpoint-close { .viewpoint-close {
......
...@@ -24,16 +24,16 @@ ...@@ -24,16 +24,16 @@
<div class="info-content" v-show="infoActive === '人物详情'"> <div class="info-content" v-show="infoActive === '人物详情'">
<div class="left"> <div class="left">
<AnalysisBox title="核心观点" width="1064px" height="300px" :show-all-btn="false" class="left-top"> <AnalysisBox title="核心观点" width="1064px" height="300px" :show-all-btn="false" class="left-top">
<template #headerBtn> <template #header-btn>
<el-select v-model="numActive" class="tab-select" :teleported="true" @change="handleChangeYear"> <el-select v-model="numActive" class="tab-select" :teleported="true" @change="handleChangeYear">
<el-option label="全部" value="全部" /> <el-option label="全部" value="全部" />
<el-option v-for="item in num" :key="item" :label="item" :value="item" /> <el-option v-for="item in num" :key="item" :label="item" :value="item" />
</el-select> </el-select>
</template> </template>
<div class="echarts" id="wordCloudChart"></div> <div class="echarts" > <WordCloudChart v-if="wordLoading" :data="characterView" /></div>
</AnalysisBox> </AnalysisBox>
<AnalysisBox title="最新动态" width="1064px" height="1617px" :show-all-btn="false" class="left-bottom"> <AnalysisBox title="最新动态" width="1064px" height="1617px" :show-all-btn="false" class="left-bottom">
<template #headerBtn> <template #header-btn>
<div class="input"><input type="checkbox" v-model="isChecked" @change="handleChange" />只看涉华动态 <div class="input"><input type="checkbox" v-model="isChecked" @change="handleChange" />只看涉华动态
</div> </div>
</template> </template>
...@@ -168,6 +168,7 @@ import CharacterRelationships from "./components/characterRelationships/index.vu ...@@ -168,6 +168,7 @@ import CharacterRelationships from "./components/characterRelationships/index.vu
import RelevantSituation from "./components/relevantSituation/index.vue"; import RelevantSituation from "./components/relevantSituation/index.vue";
import HistoricalProposal from "./components/historicalProposal/components/NewsTracker.vue"; import HistoricalProposal from "./components/historicalProposal/components/NewsTracker.vue";
import getWordCloudChart from "../../utils/worldCloudChart"; import getWordCloudChart from "../../utils/worldCloudChart";
import WordCloudChart from "@/components/base/WordCloundChart/index.vue"
import { import {
getCharacterGlobalInfo, getCharacterGlobalInfo,
getCharacterBasicInfo, getCharacterBasicInfo,
...@@ -249,7 +250,7 @@ const getCharacterBasicInfoFn = async () => { ...@@ -249,7 +250,7 @@ const getCharacterBasicInfoFn = async () => {
console.error(error); console.error(error);
} }
}; };
const wordLoading=ref(false)
const characterView = ref({}); const characterView = ref({});
const getCharacterViewFn = async () => { const getCharacterViewFn = async () => {
const params = { const params = {
...@@ -259,6 +260,7 @@ const getCharacterViewFn = async () => { ...@@ -259,6 +260,7 @@ const getCharacterViewFn = async () => {
if(numActive.value!='全部'){ if(numActive.value!='全部'){
params['year']=numActive.value params['year']=numActive.value
} }
wordLoading.value=false
try { try {
const res = await getCharacterView(params); const res = await getCharacterView(params);
if (res.code === 200) { if (res.code === 200) {
...@@ -271,6 +273,7 @@ const getCharacterViewFn = async () => { ...@@ -271,6 +273,7 @@ const getCharacterViewFn = async () => {
}; };
}); });
} }
wordLoading.value=true
} }
} catch (error) { } catch (error) {
...@@ -280,8 +283,8 @@ const getCharacterViewFn = async () => { ...@@ -280,8 +283,8 @@ const getCharacterViewFn = async () => {
const handleCharacterView = async () => { const handleCharacterView = async () => {
await getCharacterViewFn(); await getCharacterViewFn();
const wordCloudChart = getWordCloudChart(characterView.value); // const wordCloudChart = getWordCloudChart(characterView.value);
setChart(wordCloudChart, "wordCloudChart"); // setChart(wordCloudChart, "wordCloudChart");
}; };
const handleChangeYear = (value) => { const handleChangeYear = (value) => {
...@@ -415,7 +418,7 @@ onMounted(() => { ...@@ -415,7 +418,7 @@ onMounted(() => {
const info = ref(["人物详情", "成果报告", "人物关系" ]); const info = ref(["人物详情", "成果报告", "人物关系" ]);
// const info = ref(["人物详情", "成果报告", "人物关系", "相关情况"]); // const info = ref(["人物详情", "成果报告", "人物关系", "相关情况"]);
const infoActive = ref("人物详情"); const infoActive = ref("人物详情");
const num = ref(["2025", "2024", "2023", "2022", "2021", "2020"]); const num = ref(["2026","2025", "2024", "2023", "2022", "2021" ]);
const numActive = ref("全部"); const numActive = ref("全部");
......
...@@ -175,10 +175,43 @@ ...@@ -175,10 +175,43 @@
</div> </div>
</div> </div>
</div> --> </div> -->
<NewsList :newsList="newsList" @item-click="handleNewsInfoClick" @more-click="handleToMoreNews" <NewsList style="margin-right:16px" :newsList="newsList" @item-click="handleNewsInfoClick" @more-click="handleToMoreNews"
img="newsImage" title="newsTitle" content="newsContent" /> img="newsImage" title="newsTitle" content="newsContent" />
<MessageBubble :messageList="messageList" imageUrl="personImage" @more-click="handleToSocialDetail" <div class="social-media-wrapper">
name="personName" content="remarks" source="orgName" /> <!-- 翻页按钮 - 绝对定位到右上角 -->
<div class="pagination-btns">
<button
class="page-btn"
:class="{ disabled: currentPage <= 1 }"
:disabled="currentPage <= 1"
@click="prevPage"
>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<button
class="page-btn"
:class="{ disabled: currentPage >= totalPages }"
:disabled="currentPage >= totalPages"
@click="nextPage"
>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
<!-- MessageBubble 组件 -->
<MessageBubble
:messageList="paginatedList"
imageUrl="personImage"
@more-click="handleToSocialDetail"
name="personName"
content="remarks"
source="orgName"
/>
</div>
<!-- <div class="box4"> <!-- <div class="box4">
<div class="box4-header"> <div class="box4-header">
<div class="header-icon"> <div class="header-icon">
...@@ -356,7 +389,31 @@ const handleCurrentChange = page => { ...@@ -356,7 +389,31 @@ const handleCurrentChange = page => {
currentPage.value = page; currentPage.value = page;
handleFindListBySubjectTypeId(); handleFindListBySubjectTypeId();
}; };
// 计算总页数
const totalPages = computed(() => {
return Math.ceil(messageList.value.length / pageSize.value) || 1;
});
// 当前页的数据
const paginatedList = computed(() => {
const start = (currentPage.value - 1) * pageSize.value;
const end = start + pageSize.value;
return messageList.value.slice(start, end);
});
// 上一页
const prevPage = () => {
if (currentPage.value > 1) {
currentPage.value--;
}
};
// 下一页
const nextPage = () => {
if (currentPage.value < totalPages.value) {
currentPage.value++;
}
};
// 地理分布 // 地理分布
const distributionList = ref([ const distributionList = ref([
{ {
...@@ -568,38 +625,22 @@ const handleGetNews = async () => { ...@@ -568,38 +625,22 @@ const handleGetNews = async () => {
// 社交媒体 // 社交媒体
const messageList = ref([ const messageList = ref([
{
img: Message1,
name: "贾森·史密斯",
time: "15:23 · 发布于真实社交",
content: `埃隆·马斯克在强力支持我竞选总统之前,早就知道我强烈反对‘电动汽车强制令’。这太荒谬了,这一直是我竞选活动的主要部分。电动汽车没问题,但不应该强迫每个人都拥有一辆。埃隆获得的补贴可能远远超过历史上任何一个人。如果没有补贴,埃隆可能不得不关门大吉,回到南非老家。`
},
{
img: Message2,
name: "詹姆斯·布莱尔",
time: "14:49 · 发布于X",
content: `如果这个疯狂的支出法案获得通过,‘美国党’将在第二天成立。`
},
{
img: Message3,
name: "塞巴斯蒂安·马拉比",
time: "11:05 · 发布于X",
content: `提出特朗普政府的AI政策强调技术开放与快速应用,但可能以牺牲安全防范为代价,开启了“潘多拉魔盒”。`
}
]); ]);
const handleGetSocialMediaInfo = async () => { const handleGetSocialMediaInfo = async () => {
try { try {
const res = await getSocialMediaInfo(); const res = await getSocialMediaInfo();
console.log("社交媒体", res); console.log("社交媒体", res);
if (res.code === 200 && res.data) { if (res.code === 200 && res.data) {
messageList.value = res.data.map(item => ({ messageList.value = res.data.map(item => ({
...item, ...item,
time: item.time.replace(/T/, " "), time: item.time.replace(/T/, " "),
})); }));
} currentPage.value = 1;
} catch (error) { }
console.error("获取社交媒体error", error); } catch (error) {
} console.error("获取社交媒体error", error);
}
}; };
// 政令涉及领域 // 政令涉及领域
...@@ -2382,4 +2423,45 @@ onMounted(async () => { ...@@ -2382,4 +2423,45 @@ onMounted(async () => {
:deep(.table-row) { :deep(.table-row) {
height: 64px; height: 64px;
} }
.social-media-wrapper {
position: relative;
display: inline-block;
}
.pagination-btns {
position: absolute;
top: 10px;
right: 20px;
display: flex;
align-items: center;
gap: 8px;
z-index: 10;
}
.page-btn {
width: 28px;
height: 28px;
border: 1px solid #055fc2;
border-radius: 4px;
background: #fff;
color: #055fc2;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
padding: 0;
&:hover:not(.disabled) {
background: #055fc2;
color: #fff;
}
&.disabled {
cursor: not-allowed;
opacity: 0.4;
border-color: #c0c4cc;
color: #c0c4cc;
}
}
</style> </style>
\ No newline at end of file
<template>
<div class="chart-summary">
<svg class="summary-icon" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="10" cy="10" r="9" stroke="#055FC2" stroke-width="2" fill="none"/>
<path d="M7 10L9 12L13 8" stroke="#055FC2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<p class="summary-text">{{ text }}</p>
<svg class="arrow-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"
@click="$emit('more')">
<path d="M9 18L15 12L9 6" stroke="#055FC2" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</template>
<script setup>
defineProps({
text: {
type: String,
required: true
}
})
defineEmits(['more'])
</script>
<style lang="scss" scoped>
.chart-summary {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 12px 16px;
margin: 0 16px 16px;
background: #F6FAFF;
border: 1px solid #E7F3FF;
border-radius: 4px;
.summary-icon {
flex-shrink: 0;
margin-top: 2px;
}
.summary-text {
flex: 1;
font-size: 16px;
line-height: 24px;
color: #055FC2;
margin: 0;
}
.arrow-icon {
flex-shrink: 0;
cursor: pointer;
transition: opacity 0.2s;
&:hover {
opacity: 0.7;
}
}
}
</style>
<template>
<div class="cases-timeline">
<div class="timeline-line-row">
<div class="top-line">
<div class="top-line1"></div>
</div>
</div>
<div class="cases-carousel">
<LeftBtn @click="prevCases" />
<div class="cases-items">
<div v-for="(item, index) in visibleCases" :key="index" class="case-item">
<div class="case-date">{{ item.date }}</div>
<div class="node-dot"></div>
<div class="connector-line"></div>
<div class="case-card">
<div class="case-tag" :class="item.tagType">{{ item.tag }}</div>
<div class="case-partner">合作主体:{{ item.partner }}</div>
<div class="case-title">{{ item.title }}</div>
</div>
</div>
</div>
<RightBtn @click="nextCases" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import LeftBtn from '@/components/base/PageBtn/LeftBtn.vue'
import RightBtn from '@/components/base/PageBtn/RightBtn.vue'
interface CaseItem {
date: string
tag: string
tagType: 'project' | 'patent' | 'paper'
partner: string
title: string
}
const props = withDefaults(defineProps<{
data?: CaseItem[]
pageSize?: number
}>(), {
pageSize: 4
})
const defaultData: CaseItem[] = [
{ date: '2025年3月12日', tag: '项目合作', tagType: 'project', partner: '清华大学', title: '"气候变化、碳中和与能源智联"(CNEST)研讨会' },
{ date: '2025年5月7日', tag: '专利合作', tagType: 'patent', partner: '北京航空航天大学', title: '北航-哈佛 Wood 实验室联合研制鲤鱼仿生吸盘机器人' },
{ date: '2025年6月23日', tag: '论文合作', tagType: 'paper', partner: '山西省古建筑与彩塑壁画保护研究院', title: '山西古建院-哈佛中国艺术实验室合作' },
{ date: '2025年10月19日', tag: '项目合作', tagType: 'project', partner: '复旦大学、四川大学', title: '哈佛中国健康伙伴计划多机构合作' },
{ date: '2024年12月5日', tag: '论文合作', tagType: 'paper', partner: '北京大学', title: '人工智能在医疗诊断中的应用研究' },
{ date: '2024年9月18日', tag: '专利合作', tagType: 'patent', partner: '中国科学院', title: '新型纳米材料在能源存储中的应用专利' },
{ date: '2024年7月22日', tag: '项目合作', tagType: 'project', partner: '上海交通大学', title: '智能制造与工业4.0联合研究项目' },
{ date: '2024年4月10日', tag: '论文合作', tagType: 'paper', partner: '浙江大学', title: '量子计算在密码学中的突破性进展' },
{ date: '2024年2月28日', tag: '专利合作', tagType: 'patent', partner: '南京大学', title: '生物降解塑料新型合成技术专利' },
{ date: '2023年11月15日', tag: '项目合作', tagType: 'project', partner: '武汉大学', title: '长江流域生态环境保护联合研究' }
]
const casesData = computed(() => props.data && props.data.length > 0 ? props.data : defaultData)
const currentPage = ref(0)
const visibleCases = computed(() => {
const start = currentPage.value * props.pageSize
return casesData.value.slice(start, start + props.pageSize)
})
const totalPages = computed(() => Math.ceil(casesData.value.length / props.pageSize))
// 上一页
const prevCases = () => {
if (currentPage.value > 0) {
currentPage.value--
}
}
// 下一页
const nextCases = () => {
if (currentPage.value < totalPages.value - 1) {
currentPage.value++
}
}
</script>
<style lang="scss" scoped>
.cases-timeline {
padding: 24px 16px;
position: relative;
.timeline-line-row {
position: absolute;
left: 48px;
right: 48px;
top: calc(24px + 22px + 8px + 11px - 4px);
height: 8px;
z-index: 0;
.top-line {
width: 100%;
height: 8px;
background: url('../assets/top-line-icon.png') repeat-x;
background-size: auto 8px;
.top-line1 {
display: none;
}
}
}
.cases-carousel {
display: flex;
align-items: center;
gap: 60px;
.cases-items {
flex: 1;
display: flex;
gap: 60px;
overflow: hidden;
margin-top: 9px;
.case-item {
flex-shrink: 0;
width: 273px;
display: flex;
flex-direction: column;
align-items: flex-start;
position: relative;
z-index: 1;
.case-date {
font-size: 14px;
font-weight: 700;
color: #055FC2;
margin-bottom: 8px;
padding-left: 2px;
}
.node-dot {
width: 11px;
height: 11px;
border-radius: 50%;
background: #fff;
border: 4px solid #1677FF;
box-sizing: border-box;
position: relative;
z-index: 2;
}
.connector-line {
width: 1px;
height: 20px;
background: #1677FF;
margin-left: 5px;
}
.case-card {
width: 273px;
min-height: 210px;
padding: 16px;
background: #FFFFFF;
border: 1px solid #E5E7EB;
border-radius: 4px;
display: flex;
flex-direction: column;
gap: 8px;
.case-tag {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
width: fit-content;
&.project {
background: #FFF1F0;
color: #F5222D;
border: 1px solid #FFA39E;
}
&.patent {
background: #FFFBE6;
color: #FAAD14;
border: 1px solid #FFE58F;
}
&.paper {
background: #F0F5FF;
color: #2F54EB;
border: 1px solid #ADC6FF;
}
}
.case-partner {
font-size: 14px;
color: #5F656C;
}
.case-title {
font-size: 16px;
font-weight: 700;
color: #3B414B;
line-height: 24px;
}
}
}
}
}
}
</style>
<template>
<div class="history-timeline-wrapper">
<div class="timeline-list">
<div v-for="(event, index) in events" :key="index" class="timeline-item">
<!-- 左侧:实心三角箭头 + 年份,上下布局 -->
<div class="item-label">
<!-- 实心右三角 SVG,与设计图一致 -->
<svg class="arrow-icon" width="12" height="14" viewBox="0 0 12 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0L12 7L0 14V0Z" fill="#F97316"/>
</svg>
<span class="year-text">{{ event.year }}</span>
</div>
<!-- 右侧:标题 + 描述,上下布局 -->
<div class="item-content">
<div class="item-title">{{ event.title }}</div>
<div class="item-desc" v-if="event.description">{{ event.description }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
defineProps({
events: {
type: Array,
default: () => [],
// 每项结构:{ year: string, title: string, description?: string }
}
})
</script>
<style lang="scss" scoped>
.history-timeline-wrapper {
width: 100%;
padding: 16px 24px;
.timeline-list {
display: flex;
flex-direction: column;
gap: 20px;
.timeline-item {
display: flex;
flex-direction: column;
gap: 8px;
// 第一行:实心三角 + 年份横排
.item-label {
display: flex;
align-items: center;
gap: 8px;
.arrow-icon {
flex-shrink: 0;
margin-top: 1px;
}
.year-text {
font-size: 18px;
font-weight: 700;
font-family: 'Source Han Sans CN', 'Microsoft YaHei', sans-serif;
color: rgb(5, 95, 194);
line-height: 24px;
}
}
// 第二行起:标题 + 描述,左侧缩进与年份对齐
.item-content {
padding-left: 20px; // 12px箭头 + 8px gap
.item-title {
font-size: 16px;
font-weight: 700;
font-family: 'Source Han Sans CN', 'Microsoft YaHei', sans-serif;
color: rgb(59, 65, 75);
line-height: 24px;
}
.item-desc {
font-size: 14px;
font-weight: 400;
font-family: 'Source Han Sans CN', 'Microsoft YaHei', sans-serif;
color: rgb(95, 101, 108);
line-height: 22px;
margin-top: 6px;
}
}
}
}
}
</style>
<template>
<div class="semi-donut-chart-container">
<div ref="chartRef" class="chart"></div>
<div class="chart-legend">
<div v-for="(item, index) in legendData" :key="index" class="legend-item">
<span class="legend-dot" :style="{ background: colors[index] }"></span>
<span class="legend-label">{{ item.name }}</span>
<span class="legend-value">{{ item.value }}%</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch, shallowRef, computed } from 'vue'
import * as echarts from 'echarts'
import { semiDonutOption } from './bindEcharts'
const props = defineProps<{
data: {
names: string[]
values: number[]
}
}>()
const chartRef = ref<HTMLDivElement>()
const chartInstance = shallowRef<echarts.ECharts>()
const colors = ['rgba(105, 177, 255, 1)', 'rgba(255, 192, 105, 1)', 'rgba(135, 232, 222, 1)']
const legendData = computed(() => {
return props.data.names.map((name, index) => ({
name,
value: props.data.values[index]
}))
})
function initChart() {
if (!chartRef.value) return
chartInstance.value = echarts.init(chartRef.value)
updateChart()
}
function updateChart() {
if (!chartInstance.value) return
if (!props.data?.names?.length) return
const option = semiDonutOption(props.data)
chartInstance.value.setOption(option)
}
function handleResize() {
chartInstance.value?.resize()
}
onMounted(() => {
initChart()
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
chartInstance.value?.dispose()
})
watch(() => props.data, updateChart, { deep: true })
</script>
<style scoped>
.semi-donut-chart-container {
display: flex;
align-items: center;
justify-content: center;
gap: 40px;
padding: 24px 40px;
height: 320px;
.chart {
width: 240px;
height: 240px;
flex-shrink: 0;
}
.chart-legend {
display: flex;
flex-direction: column;
gap: 16px;
flex-shrink: 0;
.legend-item {
display: flex;
align-items: center;
gap: 8px;
white-space: nowrap;
.legend-dot {
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
}
.legend-label {
font-size: 14px;
color: #3B414B;
}
.legend-value {
font-size: 14px;
font-weight: 700;
color: #3B414B;
margin-left: 8px;
}
}
}
}
</style>
// echarts 图表配置
export const pieOption = (data: { names: string[]; values: number[]; total: number }) => {
const seriesData = data.names.map((name, index) => ({
value: data.values[index],
name: name
}))
return {
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'right',
data: data.names
},
series: [
{
name: '合作类型',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: [8, 8, 0, 0]
},
label: {
show: false
},
emphasis: {
label: {
show: true
}
},
labelLine: {
show: false
},
data: seriesData,
color: ['rgba(105, 177, 255, 1)', 'rgba(255, 192, 105, 1)', 'rgba(135, 232, 222, 1)']
}
]
}
}
// 完整环形饼图配置
export const semiDonutOption = (data: { names: string[]; values: number[] }) => {
const seriesData = data.names.map((name, index) => ({
value: data.values[index],
name: name
}))
return {
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
legend: {
show: false
},
series: [
{
name: '合作类型',
type: 'pie',
center: ['50%', '50%'],
radius: ['55%', '80%'],
avoidLabelOverlap: false,
itemStyle: {
borderWidth: 2,
borderColor: '#fff'
},
label: {
show: false
},
emphasis: {
label: {
show: false
}
},
labelLine: {
show: false
},
data: seriesData,
color: ['rgba(105, 177, 255, 1)', 'rgba(255, 192, 105, 1)', 'rgba(135, 232, 222, 1)']
}
]
}
}
<template>
<div class="timeline-wrapper">
<button class="arrow left" :disabled="index <= 0" @click="index--">
{{ '<' }} </button>
<div class="timeline-box">
<div class="line"></div>
<div v-for="(item, i) in showList" :key="item[idKey]" class="node" :style="leftOffset(i)">
<div class="node" :style="leftOffset(i)">
<div class="time">
{{ item.cooperateDate }}
</div>
<!-- 圆环 -->
<div class="dot" :class="linePos(i, flip)"></div>
<!-- 卡片:放到线右侧 -->
<div class="card" :class="[cardPos(i, flip), 'right-side']" @click="$emit('click-card', item)">
<div class="tag">
{{ item.typeName }}
</div>
<div class="title">
{{ '合作主体:' + item.subjectlist.join(',') }}
<img class="item-header-icon" src="@/assets/images/icon/copy.png" style="cursor: pointer;" />
</div>
<div class="content">
{{ item.cooperateName }}
</div>
</div>
</div>
</div>
</div>
<button class="arrow right" :disabled="index >= total - 5" @click="index++">
{{ '>' }}
</button>
</div>
</template>
<script>
export default {
name: 'TimeLine',
props: {
data: { // 父组件传入的数组
type: Array,
required: true
},
textKey: { // 要显示的文本字段
type: String,
default: 'text'
},
idKey: { // 唯一标识字段
type: String,
default: 'id'
}
},
data() {
return { index: 0 };
},
computed: {
total() {
return this.data.length;
},
showList() {
return this.data.slice(this.index, this.index + 5);
},
flip() { return this.index % 2 === 1; }
},
methods: {
leftOffset(i) {
return { left: `${(i * 100) / 5}%` };
},
/* 上下层翻转(保留上次逻辑) */
cardPos(i, flip = false) {
// return (i % 2) ^ flip ? 'down' : 'up';
return 'down';
},
/* 线延伸方向 = 卡片出现方向 */
linePos(i, flip = false) {
return this.cardPos(i, flip); // up / down
}
}
};
</script>
<style scoped>
/* 样式与之前完全一致,不再重复 */
.timeline-wrapper {
display: flex;
align-items: center;
width: 100%;
position: relative;
padding: 0 40px;
}
.arrow {
position: absolute;
top: 45px;
/* 左右切换按钮 */
width: 24px;
height: 48px;
font-size: 24px;
border-color: #E7F3FF;
border: 0;
background: #E7F3FF;
cursor: pointer;
z-index: 10;
color: #3E84D1;
}
.arrow:disabled {
color: #c0c4cc;
cursor: not-allowed;
}
.left {
left: 0;
border-radius: 0px 4px 4px 0px;
}
.right {
right: 0;
border-radius: 4px 0px 0px 4px;
}
.timeline-box {
flex: 1;
height: 100%;
position: relative;
}
.line {
position: absolute;
left: 0;
right: 0;
top: 50%;
height: 6px;
background-image: url("@/assets/images/bg/timeLine-bg.jpg");
transform: translateY(-50%);
background-size: auto 100%;
}
.node {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
z-index: 2;
}
/* ===== 圆环基础 ===== */
.dot {
top: -30px;
width: 14px;
height: 14px;
border-radius: 50%;
border: 3px solid #409eff;
background: #fff;
position: relative;
margin: 0 auto;
z-index: 2;
}
/* ===== 延伸线 ===== */
.dot::after {
content: '';
position: absolute;
left: 50%;
transform: translateX(-1px);
/* 居中细线 */
width: 1px;
background: #409eff;
}
/* 向上节点:线往下伸 */
.dot.up::after {
bottom: 100%;
height: 165px;
/* 圆环底部 → 卡片顶 */
}
/* 向下节点:线往上伸 */
.dot.down::after {
top: 100%;
height: 165px;
}
.card {
position: absolute;
padding: 8px 12px;
text-align: left;
cursor: pointer;
font-size: 14px;
/* 容器 299 */
width: 273px;
height: 210px;
border-radius: 4px;
/* 业务系统/模块阴影 */
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: var(--主色/白色主色, rgba(255, 255, 255, 1));
}
.time {
width: 125px;
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 0px;
text-align: justify;
margin-bottom: 35px;
margin-left: 125px;
}
.title {
color: rgba(59, 65, 75, 1);
height: 85px;
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 18px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.content {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 18px;
font-weight: 700;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.card.up {
bottom: 20px;
}
.card.down {
top: 55px;
left: 115px;
}
</style>
\ No newline at end of file
import * as echarts from "echarts";
// 计算总和
function getTotal(data) {
return data.reduce((sum, item) => sum + item.value, 0);
};
export const pieOption = (data) => {
console.log(data, 'datadatadata')
// 颜色数组
const colors = [
'rgba(105, 177, 255, 1)',
'rgba(255, 236, 61, 1)',
'rgba(135, 232, 222, 1)',
'rgba(133, 165, 255, 1)',
'rgba(255, 120, 117, 1)',
'rgba(179, 127, 235, 1)',
'rgba(255, 187, 120, 1)',
'rgba(120, 255, 180, 1)',
'rgba(255, 150, 150, 1)'
];
const seriesData = data.names.map((name, index) => ({
name,
value: data.values[index],
itemStyle: { color: colors[index % colors.length] }
}));
const option = {
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
x: 'right',
y: 'center',
align: 'left',
left: '50%',
data: data.names,
textStyle: { // 图例字体样式
color: "rgba(59, 65, 75, 1)",
fontSize: 16
},
// formatter: function (name) {
// var total = data.total;
// var item = data.find(item => item.name === name);
// var percentage = ((item.value / total) * 100).toFixed(2);
// return `${name} ${percentage}%`;
// }
},
series: [
{
name: '频度',
type: 'pie',
center: ['25%', '50%'],
radius: ['40%', '70%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
labelLine: {
show: false
},
data: seriesData
}
]
};
return option
}
var data1 = [
{
name: "捐赠基金",
value: 27
},
{
name: "政府拨款",
value: 22
},
{
name: "企业合作",
value: 18
},
{
name: "学费收入",
value: 15
},
{
name: "其他来源",
value: 12
}
];
export const pieOption1 = (data) => {
// 提取部门名称和对应的金额
const colors = [
'rgba(105, 177, 255, 1)',
'rgba(255, 236, 61, 1)',
'rgba(135, 232, 222, 1)',
'rgba(133, 165, 255, 1)',
'rgba(255, 120, 117, 1)'
];
// 提取部门名称和对应的金额
const departmentNames = data.map(item => item.cooperateTypeName);
const amounts = data.map(item => item.amount);
// 计算总金额
const getTotal = (data) => {
return data.reduce((total, item) => total + item.amount, 0);
};
// 饼图配置
const option = {
legend: {
orient: 'vertical',
x: 'right',
y: 'center',
align: 'left',
left: '60%',
data: departmentNames,
textStyle: { // 图例字体样式
color: "rgba(59, 65, 75, 1)",
fontSize: 16
},
formatter: function (name) {
var total = getTotal(data);
var item = data.find(item => item.cooperateTypeName === name);
var percentage = ((item.amount / total) * 100).toFixed(2);
return `${name} ${percentage}%`;
}
},
series: [
{
name: '频度',
type: 'pie',
center: ['30%', '50%'],
radius: ['40%', '70%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
labelLine: {
show: false
},
data: data.map((item, index) => ({
name: item.cooperateTypeName,
value: item.amount,
itemStyle: { color: colors[index % colors.length] } // 使用颜色列表
}))
}
]
};
return option;
}
export const raderOption = (data) => {
// 提取所有可能的 areaName
const allAreaNames = new Set();
data.forEach(subject => {
subject.areaVoList.forEach(area => {
allAreaNames.add(area.areaName);
});
});
const indicatorNames = Array.from(allAreaNames);
// 定义雷达图的指标最大值(可以根据实际数据调整)
const indicatorMaxValues = {};
indicatorNames.forEach(name => {
indicatorMaxValues[name] = 5; // 假设每个指标的最大值为 5
});
// 生成雷达图的 indicator 配置
const radarIndicators = indicatorNames.map(name => ({
name,
max: indicatorMaxValues[name]
}));
// 为每个 subjectTypeName 生成雷达图数据
const radarSeriesData = data.map(subject => {
const values = indicatorNames.map(indicator => {
const area = subject.areaVoList.find(a => a.areaName === indicator);
return area ? area.amount : 0; // 如果存在该指标,取其 amount 值,否则为 0
});
return {
value: values,
name: subject.subjectTypeName,
areaStyle: { color: `rgba(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, 0.2)` }
};
});
// 雷达图配置
const option = {
title: { text: '' },
legend: {
icon: 'circle',
orient: 'vertical',
right: 50,
top: 'center',
align: 'left',
textStyle: {
color: "rgba(59, 65, 75, 1)",
fontSize: "16px"
}
},
radar: {
radius: '60%',
indicator: radarIndicators,
axisName: {
formatter: '{value}',
color: 'rgba(59, 65, 75, 1)',
fontSize: 16,
fontWeight: 700
}
},
series: [
{
name: 'Budget vs spending',
type: 'radar',
data: radarSeriesData
}
]
};
return option;
}
export const barOption = (data) => {
// 提取年份和对应的专利数量
const years = data.map(item => item.year.toString());
const counts = data.map(item => item.countNum);
// 柱状图配置
const option = {
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow"
}
},
grid: {
top: '3%',
right: '3%',
bottom: '1%',
left: '1%',
containLabel: true
},
xAxis: [{
axisLine: {
lineStyle: {
width: 1,
color: "rgba(231, 243, 255, 1)"
}
},
axisTick: { show: false },
type: "category",
boundaryGap: [100, 100],
axisLabel: {
color: "rgba(95, 101, 108, 1)",
// fontSize: 22,
// fontWeight: 400
},
data: years, // 动态设置 xAxis 数据
}],
yAxis: {
type: "value",
axisLine: {
lineStyle: {
type: "dashed"
}
},
axisLabel: {
color: "rgba(95, 101, 108, 1)",
// fontSize: 22,
// fontWeight: 400
},
splitNumber: 5,
splitLine: {
lineStyle: {
width: 1,
type: "dashed",
color: "rgba(231, 243, 255, 1)"
},
}
},
series: [
{
name: "专利数量",
data: counts, // 动态设置 series 数据
type: "bar",
barWidth: 20,
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "rgba(46, 165, 255, 1)" },
{ offset: 1, color: "rgba(46, 165, 255, 0)" }
])
},
label: {
show: false,
position: 'top',
textStyle: {
fontSize: '20px',
fontWeight: 400,
color: 'rgba(255, 255, 255, 1)'
}
}
}
]
};
return option;
}
export const lineChart = (data) => {
// 提取年份和对应的专利数量
const years = data.map(item => item.year.toString());
const counts = data.map(item => item.num);
// 折线图配置
const option = {
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow"
}
},
grid: {
left: '2%',
top: '8%',
right: '2%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
splitLine: {
show: false
},
axisLine: {
show: false
},
data: years, // 动态设置 xAxis 数据
},
yAxis: {
type: 'value',
splitLine: {
show: true,
lineStyle: {
type: "dashed",
color: "#E7F3FF"
}
},
axisLine: {
show: false
},
},
color: ['rgba(255, 149, 77, 1)'],
series: [
{
data: counts, // 动态设置 series 数据
type: 'line',
emphasis: {
focus: 'series'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(255, 149, 77, 0.5)' }, // 起始颜色:深色
{ offset: 1, color: 'rgba(255, 149, 77, 0)' } // 结束颜色:浅色且透明度降低
])
},
}
]
};
return option;
}
export const lineChart1 = (data) => {
// 提取年份和对应的 fundAmount
const years = data.map(item => item.year.toString());
const fundAmounts = data.map(item => item.fund);
// 折线图配置
const option = {
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow"
}
},
grid: {
left: '2%',
top: '8%',
right: '2%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
splitLine: {
show: false
},
axisLine: {
show: false
},
data: years, // 动态设置 xAxis 数据
},
yAxis: {
type: 'value',
splitLine: {
show: true,
lineStyle: {
type: "dashed",
color: "#E7F3FF"
}
},
axisLine: {
show: false
},
},
color: ['rgba(33, 129, 57, 1)'],
series: [
{
data: fundAmounts, // 动态设置 series 数据
type: 'line',
emphasis: {
focus: 'series'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(33, 129, 57, 0.5)' }, // 起始颜色:深色
{ offset: 1, color: 'rgba(33, 129, 57, 0)' } // 结束颜色:浅色且透明度降低
])
},
}
]
};
return option;
}
export const getColumnChart = (rawData) => {
// 1. 提取所有年份并排序
const years = [...new Set(rawData.map(item => item.year))].sort((a, b) => a - b);
const yearToIndex = {};
years.forEach((year, idx) => {
yearToIndex[year] = idx;
});
// 2. 收集所有 areaName(去重,保持首次出现顺序)
const areaNames = [];
const seen = new Set();
rawData.forEach(item => {
item.areaVoList.forEach(area => {
if (!seen.has(area.areaName)) {
seen.add(area.areaName);
areaNames.push(area.areaName);
}
});
});
// 3. 初始化每个 areaName 的数据数组(长度 = years.length,初始为 0)
const seriesDataMap = {};
areaNames.forEach(name => {
seriesDataMap[name] = Array(years.length).fill(0);
});
// 4. 填充数据
rawData.forEach(item => {
const idx = yearToIndex[item.year];
if (idx === undefined) return;
item.areaVoList.forEach(area => {
if (seriesDataMap.hasOwnProperty(area.areaName)) {
seriesDataMap[area.areaName][idx] = area.amount;
}
});
});
// 5. 定义颜色池
const colorPalette = [
'rgba(179, 127, 235, 1)', // 集成电路
'rgba(255, 120, 117, 1)', // 生物科技
'rgba(133, 165, 255, 1)', // 量子科技
'rgba(135, 232, 222, 1)', // 能源
'rgba(255, 192, 105, 1)', // 人工智能
'rgba(105, 177, 255, 1)' // 通信网络
];
// 6. 构建 series
const series = areaNames.map((name, index) => {
const isLast = index === areaNames.length - 1; // 最后一个显示标签
return {
name,
type: 'bar',
stack: 'total',
barWidth: 20,
itemStyle: {
borderRadius: 0,
color: colorPalette[index % colorPalette.length]
},
data: seriesDataMap[name],
// ...(isLast ? {
// label: {
// show: true,
// position: 'top',
// formatter: '{c}',
// fontSize: 16,
// fontWeight: 'bold',
// color: 'rgba(5, 95, 194, 1)'
// }
// } : {})
};
});
// 7. 返回 ECharts 配置
return {
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: {
data: areaNames,
top: 0,
icon: 'circle',
textStyle: { color: '#757B82', fontSize: 15 }
},
grid: { top: '20%', right: '3%', bottom: '0%', left: '3%', containLabel: true },
xAxis: {
type: 'category',
data: years,
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { color: '#666', fontSize: 12 }
},
yAxis: {
type: 'value',
axisLine: { show: false },
axisTick: { show: false },
axisLabel: { color: '#666' },
splitLine: {
show: true,
lineStyle: { type: "dashed", color: "#E7F3FF" }
}
},
// graphic: [{
// type: 'text',
// right: 20,
// top: 40,
// style: { text: '单位:项', font: '14px sans-serif', fill: '#666' }
// }],
series
};
};
const nameList = ["教育学院", "文理学院", "法学院", "商学院", "工程学院", "医学院"];
const valueList = [21, 21, 25, 79, 95, 109];
export const horizontalBaroption = (data) => {
// 提取部门名称和对应的金额
const departmentNames = data.map(item => item.departmentName);
const amounts = data.map(item => item.amount);
// 水平柱状图配置
const option = {
grid: {
top: '0',
right: '3%',
bottom: '1%',
left: '1%',
containLabel: true
},
color: ['#ce4f51', '#1778ff'],
xAxis: {
type: 'value',
splitLine: {
show: false
},
show: false
},
yAxis: {
type: 'category',
data: departmentNames, // 动态设置 yAxis 数据
splitLine: {
show: false
},
axisTick: {
show: false
},
axisLine: {
show: false
},
axisLabel: {
show: true
}
},
series: [{
type: 'bar',
data: amounts.map((item, index) => {
return {
value: item,
label: {
textStyle: {
color: index < 3 ? '#1778ff' : '#ce4f51'
}
}
};
}),
label: {
show: true,
position: [650, -2]
},
barWidth: 8,
itemStyle: {
color: function (params) {
if (params.dataIndex < 3) {
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: 'rgba(22, 119, 255, 0)' },
{ offset: 1, color: '#1778ff' }
]);
} else {
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: 'rgba(206, 79, 81, 0)' },
{ offset: 1, color: '#ce4f51' }
]);
}
},
barBorderRadius: 4,
}
}]
};
return option;
}
export const raderOption1 = (data) => {
// 提取指标名称和对应的值
const indicatorNames = data.map(item => item.areaName);
const indicatorValues = data.map(item => item.areaValue);
// 动态生成雷达图的 indicator 配置
const indicators = indicatorNames.map((name, index) => ({
name,
max: Math.max(...indicatorValues) * 1.2 // 设置最大值为所有值的最大值的1.2倍
}));
let radarData = {
color:
"rgba(215, 27, 56, 0.2)",
name: '',
value: indicatorValues
}
console.log(indicators, 'indicators', radarData, 'radarDataradarData')
// 雷达图配置
const option = {
title: { text: '' },
legend: {
icon: 'circle',
orient: 'vertical',
right: 50,
top: 'center',
align: 'left',
textStyle: {
color: "rgba(59, 65, 75, 1)",
fontSize: "16px"
}
},
radar: {
radius: '60%',
indicator: indicators,
axisName: {
formatter: '{value}',
color: 'rgba(59, 65, 75, 1)',
fontSize: 16,
fontWeight: 700
}
},
series: [
{
name: 'Budget vs spending',
type: 'radar',
data: [radarData]
}
]
};
return option;
}
<!--合作情况 v2.0-->
<template>
<div class="detail-wrap">
<div class="row">
<div class="statisticsItem box">
<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="statisticsChart">
<Echarts :option="lineChart(cooperateNumWithChinaist)" height="100%"></Echarts>
</div>
<div class="statisticsAI">
<div class="AIbox">
<img src="./assets/images/icon-ai.png" />
<div class="AItext">哈佛大学近十年专利数量呈现稳定增长趋势,尤其在生物技术和人工智能领域表现突出,2025年达到历史新高。</div>
<img src="./assets/images/arrow.png" />
</div>
</div>
</div>
<div class="statisticsItem box">
<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="statisticsChart">
<Echarts :option="pieOption1(cooperateTypeWithChina)" height="100%"></Echarts>
</div>
<div class="statisticsAI">
<div class="AIbox">
<img src="./assets/images/icon-ai.png" />
<div class="AItext">哈佛大学近十年论文发表数量持续增长,高质量论文占比显著提升,特别是在医学和工程领域。</div>
<img src="./assets/images/arrow.png" />
</div>
</div>
</div>
</div>
<div class="row">
<div class="statisticsItem box">
<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="statisticsChart">
<Echarts :option="getColumnChart(cooperateAreaWithChina)" height="100%"></Echarts>
</div>
<div class="statisticsAI">
<div class="AIbox">
<img src="./assets/images/icon-ai.png" />
<div class="AItext">哈佛大学在生物科技和人工智能领域实力最为突出,同时在量子科技和能源领域也有显著优势,体现了其跨学科研究能力。</div>
<img src="./assets/images/arrow.png" />
</div>
</div>
</div>
<div class="statisticsItem box">
<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="statisticsChart">
<Echarts :option="lineChart1(cooperateFundWithChina)" height="100%"></Echarts>
</div>
<div class="statisticsAI">
<div class="AIbox">
<img src="./assets/images/icon-ai.png" />
<div class="AItext">哈佛大学经费近十年保持稳定增长,研究经费占比逐年提高,2023年总经费突破50亿美元。</div>
<img src="./assets/images/arrow.png" />
</div>
</div>
</div>
</div>
<div class="row">
<div class="statisticsItem box" style="width: 1600px;height: 355px;">
<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="statisticsChart" style="width: 1600px;height: 355px;">
<Timeline :data="cooperateExampleWithChina" text-key="title" id-key="seq" style=" margin-top: 45px;" />
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import Echarts from "@/components/Chart/index.vue";
import { barOption, lineChart, getColumnChart, lineChart1, pieOption1, horizontalBaroption } from "./charts.js";
import {
getCooperateNumWithChina,
getCooperateTypeWithChina,
getCooperateAreaWithChina,
getCooperateFundWithChina,
getCooperateExampleWithChina,
} from "@/api/innovationSubject/overview.js";
import Timeline from "./Timeline.vue";
import { useRouter } from "vue-router";
const router = useRouter();
//合作情况:与中国合作数量变化
const cooperateNumWithChinaist = ref([])
const handleGetCooperateNumWithChina = async () => {
try {
let params = {
id: router.currentRoute._value.params.id
}
const res = await getCooperateNumWithChina(params);
console.log("与中国合作数量变化", res);
if (res.code === 200 && res.data) {
cooperateNumWithChinaist.value = res.data
}
} catch (error) {
console.error("获取与中国合作数量变化error", error);
}
};
//与中国合作类型变化
const cooperateTypeWithChina = ref([])
const handleetCooperateTypeWithChina = async () => {
try {
let params = {
year: 2022,
id: router.currentRoute._value.params.id
}
const res = await getCooperateTypeWithChina(params);
console.log("与中国合作类型变化", res);
if (res.code === 200 && res.data) {
cooperateTypeWithChina.value = res.data
}
} catch (error) {
console.error("获取与与中国合作类型变化error", error);
}
};
//合作情况:与中国合作领域变化
const cooperateAreaWithChina = ref([])
const handleetCooperateAreaWithChina = async () => {
try {
let params = {
year: 2022,
id: router.currentRoute._value.params.id
}
const res = await getCooperateAreaWithChina(params);
console.log("与中国合作领域变化", res);
if (res.code === 200 && res.data) {
cooperateAreaWithChina.value = res.data
}
} catch (error) {
console.error("获取与与中国合作领域变化error", error);
}
};
//合作情况:与中国合作经费变化
const cooperateFundWithChina = ref([])
const handleetCooperateFundWithChina = async () => {
try {
let params = {
year: 2022,
id: router.currentRoute._value.params.id
}
const res = await getCooperateFundWithChina(params);
console.log("与中国合作经费变化", res);
if (res.code === 200 && res.data) {
cooperateFundWithChina.value = res.data
}
} catch (error) {
console.error("获取与中国合作经费变化error", error);
}
};
//合作情况:与中国合作事例
const cooperateExampleWithChina = ref([])
const handleetCooperateExampleWithChina = async () => {
try {
let params = {
year: 2022,
id: router.currentRoute._value.params.id
}
const res = await getCooperateExampleWithChina(params);
console.log("与中国合作事例", res);
if (res.code === 200 && res.data) {
cooperateExampleWithChina.value = res.data
}
} catch (error) {
console.error("获取与中国合作事例error", error);
}
};
onMounted(async () => {
handleGetCooperateNumWithChina()
handleetCooperateTypeWithChina()
handleetCooperateAreaWithChina()
handleetCooperateFundWithChina()
handleetCooperateExampleWithChina()
});
</script>
<style lang="scss" scoped>
.detail-wrap {
display: flex;
flex-direction: column;
gap: 16px;
padding-bottom: 30px;
.box {
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);
}
.box-header {
height: 56px;
display: flex;
position: relative;
.header-left {
margin-top: 18px;
width: 8px;
height: 20px;
border-radius: 0 4px 4px 0;
background: rgba(10, 87, 166, 1);
}
.title {
margin-left: 14px;
margin-top: 14px;
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 0px;
text-align: left;
}
.header-btn-box {
position: absolute;
top: 14px;
right: 52px;
display: flex;
.btn {
margin-left: 8px;
height: 28px;
padding: 0 8px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
text-align: center;
line-height: 28px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
cursor: pointer;
}
.btnActive {
border: 1px solid rgba(10, 87, 166, 1);
background: rgba(246, 250, 255, 1);
color: rgba(10, 87, 166, 1);
}
}
.header-info {
height: 22px;
position: absolute;
right: 84px;
top: 17px;
display: flex;
justify-content: flex-end;
.icon {
margin-top: 3px;
width: 14px;
height: 14px;
margin-right: 8px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 22px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
display: flex;
justify-content: flex-end;
gap: 8px;
.icon {
width: 28px;
height: 28px;
img {
width: 100%;
height: 100%;
}
}
.checkboxRight {
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
.btnRightActive {
width: 80px;
height: 28px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
box-sizing: border-box;
border: 1px solid rgba(5, 95, 194, 1);
border-radius: 4px;
background: rgba(231, 243, 255, 1);
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: center;
}
.btnRight {
width: 80px;
height: 28px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: center;
}
}
}
.row {
display: flex;
width: 1600px;
height: 500px;
gap: 16px;
.statisticsItem {
width: 792px;
height: 500px;
.statisticsChart {
width: 736px;
height: 321px;
margin: 20px auto;
}
.statisticsAI {
margin: 0 auto;
width: 760px;
height: 64px;
/* 自动布局 */
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 10;
padding: 6px 12px 6px 12px;
box-sizing: border-box;
border: 1px solid rgba(231, 243, 255, 1);
border-radius: 4px;
background: rgba(246, 250, 255, 1);
.AIbox {
width: 736px;
height: 52px;
/* 自动布局 */
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: 13px;
padding: 2px 0px 2px 0px;
.AItext {
width: 667px;
height: 48px;
display: flex;
flex-direction: row;
align-items: center;
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
}
}
}
}
}
</style>
\ No newline at end of file
<template> <template>
<div class="wrap"> <div class="university-detail">
<div class="header"> <!-- 背景区域 -->
<div class="header-left"> <div class="bg-area">
<img :src="institutionInfo.logoUrl" alt="" /> <div class="bg-overlay"></div>
</div> </div>
<div class="header-right">
<div class="title">{{ institutionInfo.orgName }}</div>
<div class="en-title">{{ institutionInfo.orgNameEn }}</div>
<div class="desc">{{ institutionInfo.orgIntroduction }}</div>
<div class="tag-box">
<div class="tag" v-for="(tag, index) in institutionInfo.taglist" :key="index">
{{ tag }}
</div>
</div>
</div>
<div class="header-btn">
<div class="icon">
<img src="@/assets/images/links-icon.png" alt="" />
</div>
<div class="text" @click="toURL(institutionInfo.url)">{{ "查看官网" }}</div>
</div>
</div>
<div class="tab-box">
<div class="tab" @click="handleClickTab(item, index)" :class="{ tabActive: item.active }"
v-for="(item, index) in tabList" :key="index">
{{ item.name }}
</div>
</div>
<div class="main">
<overView v-if="activeTabName === '学校详情'"></overView>
<researchStrength v-else-if="activeTabName === '科研实力'"></researchStrength>
<cooperationStatus v-else-if="activeTabName === '合作情况'"></cooperationStatus>
<otherStatus v-else-if="activeTabName === '其他情况'"></otherStatus>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import overView from "./overView/index.vue";
import researchStrength from "./researchStrength/index.vue";
import cooperationStatus from "./cooperationStatus/index.vue";
import otherStatus from "./otherStatus/index.vue";
import { useRouter } from "vue-router";
import {
getInfo
} from "@/api/innovationSubject/overview.js";
const router = useRouter();
const institutionInfo = ref({
name: "哈佛大学",
enName: "Harvard University",
desc: "哈佛大学是 1636 年创立于美国马萨诸塞州剑桥市的顶尖私立研究型大学,为常春藤盟校成员、科研与学术影响力卓著。",
tagList: ["常春藤", "精英摇篮"]
});
const handleGetInfo = async () => {
try {
let params = {
id: router.currentRoute._value.params.id
}
const res = await getInfo(params);
console.log("创新主体详情", res);
if (res.code === 200 && res.data) {
institutionInfo.value = res.data
}
} catch (error) {
console.error("获取创新主体详情error", error);
}
};
const toURL = (url) => {
} <!-- 可滚动内容区 -->
const activeTabName = ref("学校详情"); <div class="scroll-container">
<!-- 头部信息 -->
<div class="header-section">
<div class="header-content">
<div class="logo">
<img :src="universityInfo.logo" alt="University Logo" />
</div>
<div class="info">
<div class="name-row">
<h1 class="name-cn">{{ universityInfo.name }}</h1>
<span class="name-en">{{ universityInfo.ename }}</span>
</div>
<p class="description">{{ universityInfo.description }}</p>
<div class="tags">
<AreaTag v-for="(tag, index) in universityInfo.tags" :key="index" :tag-name="tag.name" :type="tag.type" />
</div>
</div>
<div class="visit-btn" @click="handleVisitWebsite">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 12.6667H4C3.26667 12.6667 2.66667 12.0667 2.66667 11.3333V4.66667C2.66667 3.93333 3.26667 3.33333 4 3.33333H7.33333V4.66667H4V11.3333H12V8H13.3333V11.3333C13.3333 12.0667 12.7333 12.6667 12 12.6667ZM8.66667 7.72L12 4.39333V6.66667H13.3333V2.66667H9.33333V4H11.6067L8.27333 7.33333L8.66667 7.72Z" fill="white"/>
</svg>
<span>查看官网</span>
</div>
</div>
</div>
const tabList = ref([ <!-- 导航切换 -->
{ <div class="nav-tabs">
name: "学校详情", <div
active: true v-for="tab in tabs"
}, :key="tab"
{ :class="['nav-tab', { active: activeTab === tab }]"
name: "科研实力", @click="activeTab = tab"
active: false >
}, {{ tab }}
{ </div>
name: "合作情况", </div>
active: false
},
{
name: "其他情况",
active: false
}
]);
const handleClickTab = (val, index) => { <!-- 主内容区域 -->
tabList.value.forEach(item => { <div class="main-content">
item.active = false; <!-- 学校详情Tab -->
}); <SchoolDetail
tabList.value[index].active = true; v-if="activeTab === '学校详情'"
activeTabName.value = val.name; :basic-info="basicInfo"
}; :key-people="keyPeople"
:statistics="statistics"
:history-events="historyEvents"
:latest-dynamics="latestDynamics"
:total-dynamics="totalDynamics"
@page-change="handlePageChange"
/>
onMounted(async () => { <!-- 科研实力Tab -->
handleGetInfo() <ResearchStrength v-else-if="activeTab === '科研实力'" />
}); <Cooperation v-else-if="activeTab === '合作情况'" />
</script>
<style lang="scss" scoped> <OtherInfo v-else-if="activeTab === '其他情况'" />
.wrap { </div>
width: 1920px; </div>
height: 100%; </div>
background-image: url("./assets/images/bg.png"); </template>
background-repeat: no-repeat;
background-size: 100% auto;
padding-top: 16px;
.header { <script setup>
width: 1600px; import { ref } from 'vue'
height: 200px; import AreaTag from '@/components/base/AreaTag/index.vue'
margin: 0 auto 16px; import SchoolDetail from './tabs/SchoolDetail.vue'
box-sizing: border-box; import ResearchStrength from './tabs/ResearchStrength.vue'
border: 1px solid rgba(255, 255, 255, 1); import Cooperation from './tabs/Cooperation.vue'
border-radius: 10px; import OtherInfo from './tabs/OtherInfo.vue'
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
background: rgba(255, 255, 255, 0.8);
display: flex;
position: relative;
.header-left { // 大学基本信息
width: 160px; const universityInfo = ref({
height: 160px; name: '哈佛大学',
margin: 20px; ename: 'Harvard University',
description: '哈佛大学是 1636 年创立于美国马萨诸塞州剑桥市的顶尖私立研究型大学,为常春藤盟校成员,科研与学术影响力卓著。',
logo: 'https://upload.wikimedia.org/wikipedia/en/thumb/2/29/Harvard_shield_wreath.svg/1200px-Harvard_shield_wreath.svg.png',
tags: [
{ name: '人工智能', type: 'tag1' },
{ name: '航空航天', type: 'tag6' },
{ name: '先进制造', type: 'tag10' },
{ name: '太空', type: 'tag2' }
]
})
img { // 导航标签
width: 100%; const tabs = ref(['学校详情', '科研实力', '合作情况', '其他情况'])
height: 100%; const activeTab = ref('学校详情')
}
}
.header-right { // 基本信息
margin-left: 24px; const basicInfo = ref({
image: 'https://upload.wikimedia.org/wikipedia/commons/thumb/c/cc/Harvard_University_Memorial_Hall.jpg/1200px-Harvard_University_Memorial_Hall.jpg',
establishedTime: '1636年',
location: '剑桥市、马萨诸塞州、美国',
nature: '私立研究型大学',
studentCount: '约22,000人',
staffCount: '约2,400人',
qsRanking: '#1',
focusTags: [
{ name: '核心技术领域7', type: 'tag6' },
{ name: '气候变化解决方案', type: 'tag6' },
{ name: '人工智能伦理', type: 'tag6' },
{ name: '跨学科交叉领域', type: 'tag6' }
]
})
.title { // 重点人物
margin-top: 26px; const keyPeople = ref([
height: 42px; {
color: rgba(59, 65, 75, 1); name: '艾伦·M·加伯',
font-family: Microsoft YaHei; title: '第30任校长',
font-size: 32px; avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=100&h=100&fit=crop&crop=face'
font-weight: 700; },
line-height: 42px; {
letter-spacing: 0px; name: '迈克尔·桑德尔',
text-align: left; title: '政府学教授',
} avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face'
},
{
name: '拉凯什·库拉纳',
title: '哈佛学院院长',
avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=100&h=100&fit=crop&crop=face'
},
{
name: '乔治·丘吉',
title: '哈佛医学院遗传学教授',
avatar: 'https://images.unsplash.com/photo-1560250097-0b93528c311a?w=100&h=100&fit=crop&crop=face'
}
])
.en-title { // 统计数据
margin-top: 8px; const statistics = ref([
height: 24px; { label: '诺贝尔奖', value: '161', color: 'rgba(5, 95, 194, 1)' },
color: rgba(59, 65, 75, 1); { label: '图灵奖', value: '18', color: 'rgba(250, 173, 20, 1)' },
font-family: Microsoft YaHei; { label: '菲尔兹奖', value: '14', color: 'rgba(5, 95, 194, 1)' },
font-size: 16px; { label: '美国院士', value: '273', color: 'rgba(250, 173, 20, 1)' }
font-weight: 400; ])
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.desc { // 历史事件
margin-top: 6px; const historyEvents = ref([
height: 24px; {
color: rgba(59, 65, 75, 1); year: '2023年',
font-family: Microsoft YaHei; title: '克劳丁·盖伊成为首位非裔女校长',
font-size: 16px; description: '克劳丁·盖伊(Claudine Gay)正式就任哈佛大学第30任校长,成为学校历史上首位非洲裔女性校长。'
font-weight: 400; },
line-height: 24px; {
letter-spacing: 0px; year: '1999年',
text-align: justify; title: '拉德克利夫学院转型为"拉德克利夫高等研究院"',
} description: '为了加强女性在研究和学术中的地位,拉德克利夫学院转型为专业研究机构。'
},
{
year: '1977年',
title: '哈佛与拉德克利夫学院正式合并注册',
description: '哈佛大学与拉德克利夫学院在招生、财务和管理方面实现全面整合。'
},
{
year: '1950年',
title: '逐步整合招生中的宗教、种族限制',
description: '哈佛开始审视并逐步移除招生中的种族和宗教歧视政策,推进学生群体多元化。'
},
{
year: '1908年',
title: '哈佛商学院成立,首创MBA案例教学法',
description: '哈佛商学院正式成立,率先采用案例教学法,成为现代商学教育的奠基者。'
},
{
year: '1869年',
title: '查尔斯·W·艾略特任校长',
description: '查尔斯·W·艾略特(Charles William Eliot)出任哈佛校长,任期长达40年。在其领导下,哈佛推行选修制改革、重建法学院和医学院、新建商学院等。'
},
{
year: '1636年',
title: '哈佛大学成立',
description: '哈佛大学在美国马萨诸塞州剑桥市成立,是美国第一所大学,也是常春藤盟校中最古老的成员。'
}
])
.tag-box { // 最新动态
margin-top: 14px; const latestDynamics = ref([
display: flex; {
gap: 8px; year: '2025',
date: '10月10日',
title: '哈佛大学再遭电话钓鱼攻击致数据泄露',
content: '一名未授权人员通过社会工程手段入侵校友数据库,泄露联系方式、捐赠记录等信息。这是该校2025年内第二次重大数据泄露事件。',
tags: [{ name: '数安治理', type: 'tag9' }],
isHighlight: true
},
{
year: '2025',
date: '10月4日',
title: '哈佛医学院发生爆炸,警方认定系蓄意制造',
content: '2024年11月1日凌晨2:49,哈佛医学院一栋教学楼四层发生爆炸,无人伤亡,波士顿警方公布两名震惊罪犯确人监控画面,FBI介入调查。',
tags: [{ name: '恐怖袭击', type: 'tag1' }],
isHighlight: true
},
{
year: '2025',
date: '9月29日',
title: '常春藤盟校联合加强网络安全联盟',
content: '哈佛与普林斯顿、耶鲁、斯坦福比亚等校宣布成立"常春藤网络安全协作体",应对日益频繁的恶意网络攻击。',
tags: [{ name: '生物科技', type: 'tag2' }],
isHighlight: true
},
{
year: '2025',
date: '9月21日',
title: '哈佛校长克劳丁·盖伊因学术诚信争议辞职',
content: '首位非裔女校长克劳丁·盖伊(Claudine Gay)因多篇论文涉嫌抄袭及国会听证会上言论引发舆论风暴,上任不足一年即辞职。',
tags: [{ name: '先进制造', type: 'tag10' }],
isHighlight: true
},
{
year: '2025',
date: '9月15日',
title: '艾伦·M·加伯接任临时校长',
content: '原教务长艾伦·加伯(Alan M. Garber)出任哈佛第30任校长(临时),并完整复学术诚信与校园团�����。',
tags: [{ name: '集成��路', type: 'tag6' }],
isHighlight: true
},
{
year: '2025',
date: '9月11日',
title: '联邦法官叫停特朗普政府"禁招国际学生"令',
content: '2025年5月29日,马萨诸塞州联邦法官艾莉森·伯恩斯顿发布临时限制令,阻止国土安全部驱逐哈佛招收国际学生资质。',
tags: [{ name: '集成电路', type: 'tag6' }],
isHighlight: true
},
{
year: '2025',
date: '8月28日',
title: '哈佛启动"AI与伦理"研究中心',
content: '投资500万美元成立新的跨学科研究中心,旨在探索人工智能技术的伦理应用和社会影响,汇聚全球顶尖学者。',
tags: [{ name: '人工智能', type: 'tag1' }]
},
{
year: '2025',
date: '8月15日',
title: '哈佛医学院宣布重大癌症研究突破',
content: '研究团队开发出新型免疫疗法,在临床试验中显示出对晚期肺癌的显著疗效,成果已发表于《自然》杂志。',
tags: [{ name: '生物医学', type: 'tag2' }]
},
{
year: '2025',
date: '7月30日',
title: '哈佛大学与MIT联合建立量子计算实验室',
content: '两校联合投资1亿美元,共同建立量子计算与信息研究实验室,推进量子技术的商业化应用。',
tags: [{ name: '前沿科技', type: 'tag11' }]
},
{
year: '2025',
date: '7月10日',
title: '2025年学年入学录取率创历史新低',
content: '2025年度本科生申请录取率仅为3.2%,创造了美国高等教育历史新低,共录取1,380名学生。',
tags: [{ name: '教育新闻', type: 'tag12' }]
},
{
year: '2025',
date: '6月20日',
title: '哈佛成立"气候变化研究全球中心"',
content: '新成立的中心将汇聚来自20个国家的气候科学家,共同应对全球气候变化挑战,年度预算达3000万美元。',
tags: [{ name: '气候变化', type: 'tag7' }]
},
{
year: '2025',
date: '6月5日',
title: '哈佛建筑学院获普利兹克建筑学奖',
content: '著名建筑师与哈佛建筑学院合作的项目获得2025年普利兹克建筑学奖,该奖项被誉为建筑领域的"诺贝尔奖"。',
tags: [{ name: '艺术建筑', type: 'tag8' }]
},
{
year: '2025',
date: '5月15日',
title: '哈佛捐赠基金规模首次突破500亿美元',
content: '截至2024年6月30日,哈佛捐赠基金达501亿美元,创历史新高,位列全球大学捐赠基金首位。',
tags: [{ name: '财务报告', type: 'tag13' }]
},
{
year: '2025',
date: '4月28日',
title: '哈佛法学院宣布取消LSAT考试要求',
content: '从2025年秋季开始,申请人可无需提交LSAT成绩,法学院将采用更全面的综合评估方式审核申请。',
tags: [{ name: '招生政策', type: 'tag14' }]
}
])
.tag { // 分页
height: 24px; const totalDynamics = ref(256)
padding: 0px 8px;
border-radius: 4px;
background: rgba(231, 243, 255, 1);
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: 0px;
}
}
}
.header-btn { const handlePageChange = (page) => {
position: absolute; console.log('Page changed to:', page)
top: 26px; // 这里可以调用API获取对应页的数据
right: 30px; }
width: 120px;
height: 36px;
border-radius: 6px;
background: rgba(5, 95, 194, 1);
display: flex;
gap: 3px;
justify-content: center;
align-items: center;
cursor: pointer;
.icon { const handleVisitWebsite = () => {
width: 16px; window.open('https://www.harvard.edu', '_blank')
height: 16px; }
</script>
img { <style lang="scss" scoped>
width: 100%; .university-detail {
height: 100%; width: 100%;
} height: 100vh;
} background: rgba(247, 248, 249, 1);
position: relative;
overflow: hidden;
}
.text { .bg-area {
height: 22px; width: 100%;
color: rgba(255, 255, 255, 1); height: 459px;
font-family: Microsoft YaHei; position: absolute;
font-size: 16px; top: 0;
font-weight: 400; left: 0;
line-height: 22px; background: linear-gradient(135deg, #1e5799 0%, #207cca 50%, #2989d8 100%);
} z-index: 1;
}
} .bg-overlay {
width: 100%;
height: 100%;
background: linear-gradient(to bottom, rgba(30, 87, 153, 0.8), rgba(32, 124, 202, 0.6));
}
}
.tab-box { .scroll-container {
width: 1600px; position: relative;
height: 64px; z-index: 2;
margin: 0 auto; height: 100vh;
box-sizing: border-box; overflow-y: auto;
border: 1px solid rgba(255, 255, 255, 1); overflow-x: hidden;
border-radius: 10px; padding-bottom: 50px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1); }
background: rgba(255, 255, 255, 0.8);
display: flex;
justify-content: space-between;
align-items: center;
.tab { .header-section {
width: 397px; padding: 80px;
height: 54px; display: flex;
box-sizing: border-box; justify-content: center;
border: 2px solid transparent;
border-radius: 10px; .header-content {
text-align: center; width: 1600px;
line-height: 50px; display: flex;
color: rgba(59, 65, 75, 1); align-items: flex-start;
font-family: Microsoft YaHei; gap: 24px;
font-size: 20px;
font-weight: 400; .logo {
letter-spacing: 0px; width: 110px;
cursor: pointer; height: 130px;
background: rgba(255, 255, 255, 0.9);
border-radius: 8px;
padding: 10px;
display: flex;
align-items: center;
justify-content: center;
img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
}
.info {
flex: 1;
.name-row {
display: flex;
align-items: baseline;
gap: 16px;
margin-bottom: 8px;
.name-cn {
font-size: 32px;
font-weight: 700;
font-family: 'Microsoft YaHei', sans-serif;
color: white;
margin: 0;
}
.name-en {
font-size: 16px;
font-weight: 400;
font-family: 'Microsoft YaHei', sans-serif;
color: rgba(255, 255, 255, 0.9);
}
}
.description {
font-size: 16px;
font-weight: 400;
font-family: 'Microsoft YaHei', sans-serif;
line-height: 24px;
color: rgba(255, 255, 255, 0.9);
margin-bottom: 12px;
}
.tags {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
}
.visit-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
background: rgba(5, 95, 194, 1);
border-radius: 4px;
color: white;
font-size: 14px;
font-family: 'Microsoft YaHei', sans-serif;
cursor: pointer;
transition: opacity 0.2s;
&:hover {
opacity: 0.9;
}
}
}
}
&:hover { .nav-tabs {
background: rgba(231, 243, 255, 1); display: flex;
} justify-content: center;
} margin: 0 auto 16px;
background: rgba(255, 255, 255, 0.65);
border-radius: 10px;
padding: 5px;
box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1);
width: 1600px;
margin-left: auto;
margin-right: auto;
.nav-tab {
flex: 1;
height: 54px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: 400;
font-family: 'Microsoft YaHei', sans-serif;
color: rgb(59, 65, 75);
border-radius: 10px;
cursor: pointer;
transition: all 0.2s;
&:hover {
background: rgba(231, 243, 255, 1);
}
&.active {
font-size: 24px;
font-weight: 700;
color: rgb(5, 95, 194);
background: rgba(231, 243, 255, 1);
border: 2px solid rgba(174, 214, 255, 1);
}
}
}
.tabActive { .main-content {
border: 2px solid rgba(174, 214, 255, 1); display: flex;
background: rgba(231, 243, 255, 1); justify-content: center;
color: rgba(5, 95, 194, 1);
font-size: 24px; > * {
font-weight: 700; width: 1600px;
} }
} }
.main { .placeholder-content {
width: 1600px; padding: 100px;
margin: 16px auto; background: white;
} border-radius: 10px;
box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1);
text-align: center;
.placeholder-text {
font-size: 20px;
color: rgb(132, 136, 142);
font-family: 'Microsoft YaHei', sans-serif;
}
} }
</style> </style>
\ No newline at end of file
<!--其他情况-->
<template>
<div class="detail-wrap">
<div class="top box">
<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="labList">
<div class="labCard" v-for="item in labList">
<img :src="item.logoUrl" alt="" class="labPic" />
<div class="labTitle">{{ item.labName
}}</div>
<div class="labDes">{{ item.introduction }}</div>
<div class="labTags">
<div class="tag1" v-for="value in item.arealist"
:class="{ tag2: value === '人工智能', tag3: value === '生物科技', tag4: value === '天体物理' }">{{ value }}</div>
</div>
</div>
</div>
</div>
<div class="down box">
<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="policyList">
<div class="policyBox" v-for="item in policyList">
<img :src="item.logoUrl" alt="" style="/* 矩形 211 */
width: 57px;
height: 77px;" />
<div class="polContent">
<div class="polTitle">{{ item.name }}</div>
<div class="polDes">{{ item.introduction }}</div>
</div>
</div>
</div>
<div class="box1-main-footer">
<div class="info">
{{ `共 ${total} 项` }}
</div>
<div class="page-box">
<el-pagination @current-change="handleCurrentChange" :pageSize="pageSize" :current-page="currentPage"
size="small" background layout="prev, pager, next" :total="total" />
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import {
getLabList,
getPolicyList
} from "@/api/innovationSubject/overview.js";
import { useRouter } from "vue-router";
const router = useRouter();
import pic1 from "./assets/images/pic1.png";
import pic2 from "./assets/images/pic2.png";
import pic3 from "./assets/images/pic3.png";
import pic4 from "./assets/images/pic4.png";
import pic5 from "./assets/images/pic5.png";
import pic6 from "./assets/images/pic6.png";
import pic7 from "./assets/images/pic7.png";
// const labList = ref([
// {
// pic: pic1,
// title: `怀斯生物启发工程研究所`,
// des: "成立于2009年,由汉斯约尔格·怀斯(Hansjörg Wyss)捐赠建立,聚焦仿生学与跨学科工程,推动医疗、机器人、材料等领域的突破。",
// tags: ["生物科技"]
// },
// {
// pic: pic2,
// title: `罗兰研究所`,
// des: "原为独立研究机构,2002年并入哈佛,支持高风险、高回报的基础科学研究,尤其鼓励青年科学家。",
// tags: ["物理学", "化学"]
// },
// {
// pic: pic3,
// title: `哈佛量子计划`,
// des: "跨学院合作平台,整合物理、工程、计算机科学等资源,推动量子科学与技术发展。",
// tags: ["物理"]
// },
// {
// pic: pic4,
// title: `博德研究所`,
// des: "全球顶尖基因组学与生物医学研究中心,推动精准医学与疾病机制研究。",
// tags: ["医学"]
// },
// {
// pic: pic5,
// title: `哈佛干细胞研究所`,
// des: "成立于2004年,联合哈佛医学院、牙医学院、文理学院等,推动干细胞基础研究与临床转化。",
// tags: ["医学"]
// },
// {
// pic: pic6,
// title: `哈佛大学天体物理中心`,
// des: "由哈佛大学与史密森尼学会于1973年联合成立,是全球规模最大、最活跃的天体物理研究机构之一。",
// tags: ["天体物理"]
// }
// ]);
// const policyList = ref([
// {
// title: "《哈佛大学权利与责任声明》",
// des: "阐明学生在言论自由、学术自由、正当程序、尊重他人等方面的权利与义务。"
// },
// {
// title: "《哈佛大学学术诚信政策》",
// des: "定义抄袭、作弊、伪造等学术不端行为,并规定处理流程。"
// },
// {
// title: "《哈佛大学反歧视与反骚扰政策》",
// des: "禁止基于种族、性别、性取向、宗教、残疾等的歧视与骚扰,明确举报与调查机制。"
// },
// {
// title: "《研究合规与人类受试者保护政策》",
// des: "规范涉及人类受试者的研究(如医学、心理学、社会学实验),确保符合联邦法规(如Common Rule)。"
// },
// {
// title: "《哈佛法学院学术政策手册》",
// des: "详述J.D./LL.M./S.J.D.学位要求、课程规则、成绩制度、书面作业要求、出勤规定、荣誉毕业标准等。"
// },
// {
// title: "《哈佛文理研究生院学生手册》",
// des: "涵盖博士生资格考试、论文提交、助教职责、奖学金续期、学术进展评估等。"
// }
// ]);
//创新主体其他情况:重点实验室
const labList = ref([])
const handleGetLabList = async () => {
try {
let params = {
id: router.currentRoute._value.params.id
}
const res = await getLabList(params);
console.log("重点实验室", res);
if (res.code === 200 && res.data) {
labList.value = res.data
}
} catch (error) {
console.error("获取重点实验室error", error);
}
};
//创新主体其他情况:政策文件
const policyList = ref([])
const total = ref(0)
const currentPage = ref(1)
const handleGetPolicyList = async () => {
try {
let params = {
currentPage: currentPage.value,
pageSize: 6,
id: router.currentRoute._value.params.id
}
const res = await getPolicyList(params);
console.log("政策文件", res);
if (res.code === 200 && res.data) {
policyList.value = res.data.content
total.value = res.data.totalElements
}
} catch (error) {
console.error("获取政策文件error", error);
}
};
onMounted(async () => {
handleGetLabList()
handleGetPolicyList()
});
</script>
<style lang="scss" scoped>
.detail-wrap {
display: flex;
flex-direction: column;
gap: 16px;
padding-bottom: 30px;
.box {
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);
}
.box-header {
height: 56px;
display: flex;
position: relative;
.header-left {
margin-top: 18px;
width: 8px;
height: 20px;
border-radius: 0 4px 4px 0;
background: rgba(10, 87, 166, 1);
}
.title {
margin-left: 14px;
margin-top: 14px;
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 0px;
text-align: left;
}
.header-btn-box {
position: absolute;
top: 14px;
right: 52px;
display: flex;
.btn {
margin-left: 8px;
height: 28px;
padding: 0 8px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
text-align: center;
line-height: 28px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
cursor: pointer;
}
.btnActive {
border: 1px solid rgba(10, 87, 166, 1);
background: rgba(246, 250, 255, 1);
color: rgba(10, 87, 166, 1);
}
}
.header-info {
height: 22px;
position: absolute;
right: 84px;
top: 17px;
display: flex;
justify-content: flex-end;
.icon {
margin-top: 3px;
width: 14px;
height: 14px;
margin-right: 8px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 22px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
display: flex;
justify-content: flex-end;
gap: 8px;
.icon {
width: 28px;
height: 28px;
img {
width: 100%;
height: 100%;
}
}
.checkboxRight {
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
.btnRightActive {
width: 80px;
height: 28px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
box-sizing: border-box;
border: 1px solid rgba(5, 95, 194, 1);
border-radius: 4px;
background: rgba(231, 243, 255, 1);
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: center;
}
.btnRight {
width: 80px;
height: 28px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: center;
}
}
}
}
.top {
width: 1600px;
height: 552px;
.labList {
display: flex;
gap: 16px;
margin-left: 24px;
flex-wrap: wrap;
.labCard {
width: 376px;
height: 228px;
display: flex;
flex-direction: column;
padding: 20px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
background: rgba(255, 255, 255, 0.65);
.labPic {
width: 32px;
height: 32px;
}
.labTitle {
margin-top: 12px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
}
.labDes {
margin-top: 4px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
text-align: justify;
}
.labTags {
display: flex;
gap: 8px;
height: auto;
margin-top: auto;
.tag1 {
height: 22px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 1px 8px 1px 8px;
box-sizing: border-box;
border: 1px solid rgba(186, 224, 255, 1);
border-radius: 4px;
background: rgba(230, 244, 255, 1);
color: rgba(22, 119, 255, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
.tag2 {
height: 22px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 1px 8px 1px 8px;
box-sizing: border-box;
border: 1px solid rgba(255, 163, 158, 1);
border-radius: 4px;
background: rgba(255, 241, 240, 1);
color: rgba(245, 34, 45, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
.tag3 {
height: 22px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 1px 8px 1px 8px;
box-sizing: border-box;
border: 1px solid rgba(255, 241, 184, 1);
border-radius: 4px;
background: rgba(255, 251, 230, 1);
color: rgba(250, 173, 20, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
.tag4 {
height: 22px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 1px 8px 1px 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;
}
}
}
}
}
.down {
width: 1600px;
height: 362px;
.policyList {
width: 1600px;
display: flex;
gap: 16px;
margin-left: 24px;
flex-wrap: wrap;
.policyBox {
width: 506px;
height: 109px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
background: rgba(255, 255, 255, 0.65);
display: flex;
gap: 16px;
padding: 16px;
.polPic {
width: 57px;
height: 77px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
background: url("./assets/images/pic7.png");
}
.polContent {
display: flex;
gap: 4px;
flex-direction: column;
.polTitle {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
}
.polDes {
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
letter-spacing: 0px;
text-align: justify;
}
}
}
}
.box1-main-footer {
margin: 30px 22px 0 22px;
height: 22px;
display: flex;
justify-content: space-between;
.info {
height: 22px;
line-height: 22px;
color: rgb(132, 136, 142);
font-family: "Microsoft YaHei";
font-size: 14px;
font-weight: 400;
text-align: left;
}
}
}
</style>
\ No newline at end of file
<!--学校详情-->
<template>
<div class="detail-wrap">
<div class="left box">
<div class="box-header">
<div class="header-left"></div>
<div class="title">最新动态</div>
<div class="header-right">
<el-checkbox :checked="cRelated" label="只看涉华动态" @change="handleGetDynamics()" />
<div :class="dynamicsType === 'org' ? 'btnRightActive' : 'btnRight'" @click="changedynamicsType()">机构动态</div>
<div :class="dynamicsType === 'person' ? 'btnRightActive' : 'btnRight'" @click="changedynamicsType()">主官动态
</div>
<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="left-main">
<div class="left-main-item" v-for="(item, index) in curList" :key="index">
<div class="line"></div>
<div class="time">
<div class="timetext">{{ item.time }}</div>
<div class="timetext">{{ item.date }}</div>
</div>
<div class="icon">
<img src="./assets/images/small-harvard.png" alt="" />
</div>
<div class="info">
<div class="header">
<div class="title">{{ item.title }}</div>
</div>
<div class="content">{{ item.content }}</div>
<div class="tag-box">
<div class="tag" v-for="(val, idx) in item.tagList" :key="idx">{{ val }}</div>
</div>
</div>
</div>
</div>
<div class="left-footer">
<div class="info">
{{ `共 ` + total + ` 项` }}
</div>
<div class="page-box">
<el-pagination background layout="prev, pager, next" :total="total" v-model:current-page="currentPage"
@current-change="handleGetDynamics" />
</div>
</div>
</div>
<div class="rightcontent">
<div class="right box">
<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="right-main">
<div class="img-box">
<img :src="basicInfo.logoUrl" alt="" />
</div>
<div class="info-box">
<div class="info-item">
<div class="info-item-left">{{ "成立时间:" }}</div>
<div class="info-item-right">{{ basicInfo.establishmentDate }}</div>
</div>
<div class="info-item">
<div class="info-item-left">{{ "总部地址:" }}</div>
<div class="info-item-right">{{ basicInfo.address }}</div>
</div>
<div class="info-item">
<div class="info-item-left">{{ "组织性质:" }}</div>
<div class="info-item-right">{{ basicInfo.companyType }}</div>
</div>
<div class="info-item">
<div class="info-item-left">{{ "在校人数:" }}</div>
<div class="info-item-right">{{ basicInfo.numberOfEmployees }}</div>
</div>
<div class="info-item">
<div class="info-item-left">{{ "教职工数:" }}</div>
<div class="info-item-right">{{ basicInfo.teaNum }}</div>
</div>
<div class="info-item">
<div class="info-item-left">{{ "QS排名:" }}</div>
<div class="info-item-right">{{ basicInfo.rate }}</div>
</div>
<div class="info-item">
<div class="info-item-left">{{ "QS排名:" }}</div>
<div class="taglist">
<div class="tagdirec" v-for="item in basicInfo.tag">{{ item }}</div>
</div>
</div>
</div>
<div class="user-box">
<div class="user-header">
{{ "重点人物:" }}
</div>
<div class="user-content">
<div class="user-item" v-for="(item, index) in personList" :key="index">
<div class="user-item-left">
<img :src="item.avatarUrl" alt="" />
</div>
<div class="user-item-right">
<div class="name">{{ item.name }}</div>
<div class="position">{{ item.position }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="right-Num">
<div class="numbox" v-for="item in awardsList">
<div class="iconrec"></div>
<div class="awards">{{ item.name }}</div>
<div class="awardsNum">{{ item.num }}</div>
</div>
</div>
<div class="right-history">
<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="historytimeline">
<div class="timeline"></div>
<div class="historyList">
<div class="historyPoint" v-for="item in historyList">
<div class="historytime">{{ item.createTime }}</div>
<img src="./assets/images/timepoint.png" alt="" style="z-index: 2;" />
<div class="historyitem">{{ item.content }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import { useRouter } from "vue-router";
import {
getDynamics,
getInfo,
getEventList,
getPersonList
} from "@/api/innovationSubject/overview.js";
import Img from "./assets/images/img.png";
import User1 from "./assets/images/user1.png";
import User2 from "./assets/images/user2.png";
import User3 from "./assets/images/user3.png";
import User4 from "./assets/images/user4.png";
const router = useRouter();
//只看涉华动态
const cRelated = ref(false)
const dynamicsType = ref('org')
const basicInfo = ref({
image: Img,
shijian: "1636年",
dizhi: "剑桥市,马萨诸塞州,美国",
xingzhi: "私立研究型大学",
stuNum: "约22,000人",
teaNum: "约2,400人",
rate: "#1",
tag: ["癌症精准医疗", "气候变化解决方案", "人工智能伦理", "可持续发展能源"],
keyUser: [
{
name: "艾伦·M·加伯",
img: User1,
position: "第30任校长"
},
{
name: "迈克尔·桑德尔",
img: User2,
position: "政府学教授"
},
{
name: "拉凯什·库拉纳",
img: User3,
position: "哈佛学院院长"
},
{
name: "乔治·丘奇",
img: User4,
position: "哈佛医学院遗传学教授"
}
]
});
const curList = ref([
{
title: "哈佛大学再遭电话钓鱼攻击致数据泄露",
time: "2025",
date: "10月10日",
content:
"一名未授权人员通过社会工程手段入侵校友数据库,泄露联系方式、捐赠记录等信息。这是该校2025年内第二次重大数据泄露。",
tagList: ["数据泄露"]
},
{
title: "哈佛医学院发生爆炸,警方认定系蓄意制造",
time: "2025 ",
date: "10月4日",
content:
"202年11月1日凌晨2:48,哈佛医学院一栋教学楼四层发生爆炸,无人伤亡。波士顿警方公布两名戴面罩嫌疑人监控画面,FBI介入调查。",
tagList: ["恐怖袭击"]
},
{
title: "常春藤盟校联合加强网络安全联盟",
time: "2025 ",
date: "9月29日",
content:
"哈佛与普林斯顿、耶鲁、哥伦比亚等校宣布成立“常春藤网络安全协作体”,应对日益频繁的高校网络攻击。哈佛与普林斯顿、耶鲁、哥伦比亚等校宣布成立“常春藤网络安全协作体”。",
tagList: ["生物科技"]
},
{
title: "哈佛校长克劳丁·盖伊因学术诚信争议辞职",
time: "2025 ",
date: "9月21日",
content:
"首位非裔女校长克劳丁·盖伊(Claudine Gay)因多篇论文涉嫌抄袭及国会听证会上言论引发舆论风暴,上任不足一年即辞职。",
tagList: ["先进制造"]
},
{
title: "艾伦·M·加伯接任临时校长",
time: "2025 ",
date: "9月15日",
content:
"原教务长艾伦·加伯(Alan M. Garber)出任哈佛第30任校长(临时),承诺恢复学术诚信与校园团结。原教务长艾伦·加伯(Alan M. Garber)出任哈佛第30任校长(临时),承诺恢复学术诚信与校园团结。",
tagList: ["集成电路"]
},
{
title: "联邦法官叫停特朗普政府“禁招国际学生”令",
time: "2025 ",
date: "9月11日",
content:
"2025年5月29日,马萨诸塞州联邦法院法官艾莉森·伯勒斯发布临时限制令,阻止国土安全部取消哈佛招收国际学生资质。法院认为政府行为缺乏程序正当性,可能对学校造成“不可逆损害”,允许哈佛继续接收持有效签证学生。",
tagList: ["集成电路"]
},
{
title: "特朗普政府要求哈佛30天内提交“改革证据”",
time: "2025 ",
date: "9月10日",
content:
"2025年5月底,美国司法部致函哈佛,给予30天期限说明为何不应撤销其国际招生资质。此前政府指责哈佛“营造反犹环境”并拒绝提供学生名单。此举被视为强硬立场软化后的策略调整。",
tagList: ["人工智能"]
},
{
title: "哈佛2025届毕业典礼成政治声援现场",
time: "2025 ",
date: "9月1日",
content:
"在阴云密布的天空下,约3万师生家长齐聚哈佛第374届毕业典礼。众多毕业生佩戴白花声援国际学生,校长艾伦·加伯致辞强调“哈佛属于世界”,全场起立鼓掌逾一分钟,场面感人。",
tagList: ["集成电路"]
},
{
title: "哈佛教授警告:国际博士生面临系统性驱逐风险",
time: "2025 ",
date: "8月25日",
content: "多位哈佛教授联名发声,指当前政策使90%以上国际博士生陷入签证不确定性,恐导致人才外流。",
tagList: ["能源", "先进制造"]
},
{
title: "巴以冲突激化哈佛校园对立",
time: "2025 ",
date: "8月19日",
content:
"2023年10月,亲巴勒斯坦与亲以色列学生团体在校园多次激烈对峙,部分活动演变为肢体冲突。校方因初期回应迟缓遭多方批评,后成立特别工作组调解矛盾。",
tagList: ["先进制造"]
},
{
title: "联邦法院裁定哈佛招生不构成种族歧视",
time: "2025 ",
date: "8月19日",
content:
"2022年8月,马萨诸塞州地方法院驳回“学生公平录取组织”诉讼,认为哈佛在本科招生中审慎考虑种族因素以促进多样性,符合现行法律框架。",
tagList: ["先进制造"]
},
{
title: "哈佛捐赠基金规模达509亿美元,全球第一",
time: "2025 ",
date: "8月19日",
content:
"2023财年报告显示,尽管市场波动,哈佛管理公司(HMC)仍维持全球最大高校捐赠基金地位,为教学、科研与助学提供坚实财务支撑。",
tagList: ["先进制造"]
}
]);
const changedynamicsType = () => {
dynamicsType.value === 'person' ? dynamicsType.value = 'org' : dynamicsType.value = 'person'
handleGetDynamics()
}
const handleGetInfo = async () => {
try {
let params = {
id: router.currentRoute._value.params.id
}
const res = await getInfo(params);
console.log("创新主体基本信息", res);
if (res.code === 200 && res.data) {
basicInfo.value = res.data
}
} catch (error) {
console.error("获取基本信息error", error);
}
};
const personList = ref([])
//重点人物
const handleGetPersonList = async () => {
try {
let params = {
id: router.currentRoute._value.params.id
}
const res = await getPersonList(params);
console.log("重点人物", res);
if (res.code === 200 && res.data) {
personList.value = res.data
}
} catch (error) {
console.error("获取重点人物error", error);
}
};
const total = ref(0)
const currentPage = ref(1)
const handleGetDynamics = async () => {
try {
let params = {
currentPage: 1,
pageSize: 10,
cRelated: cRelated.value ? null : 'N',
dynamicsType: dynamicsType.value,
orgId: router.currentRoute._value.params.id
}
const res = await getDynamics(params);
console.log("创新主体最新动态", res);
if (res.code === 200 && res.data) {
curList.value = res.data.content
total.value = res.data.totalElements
}
} catch (error) {
console.error("获取最新动态error", error);
}
};
const awardsList = ref([
{
name: "诺贝尔奖",
num: 161
},
{
name: "图灵奖",
num: 18
},
{
name: "菲尔兹奖",
num: 14
},
{
name: "美国院士",
num: 273
}
]);
const historyList = ref([
{
time: "2023年",
event: "克劳丁·盖伊成为首位非裔女校长"
},
{
time: "1999年",
event: "拉德克利夫学院转型为“拉德克利夫高等研究院”"
},
{
time: "1977年",
event: "哈佛与拉德克利夫学院正式合并招生"
},
{
time: "1950年",
event: "逐步废除招生中的宗教、种族限制,学生群体多元化"
},
{
time: "1908年",
event: "哈佛商学院成立,首创MBA案例教学法"
},
{
time: "1869年",
event: "查尔斯·W·艾略特任校长40年,推行选修制、重建法学院、改革医学院、新建商学院等"
}
]);
const handleGetEventList = async () => {
try {
let params = {
id: router.currentRoute._value.params.id
}
const res = await getEventList(params);
console.log("历史动态", res);
if (res.code === 200 && res.data) {
historyList.value = res.data
}
} catch (error) {
console.error("获取历史动态error", error);
}
};
onMounted(async () => {
handleGetInfo()
handleGetDynamics()
handleGetEventList()
handleGetPersonList()
});
</script>
<style lang="scss" scoped>
.detail-wrap {
display: flex;
gap: 16px;
padding-bottom: 30px;
.box {
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);
}
.box-header {
height: 56px;
display: flex;
position: relative;
.header-left {
margin-top: 18px;
width: 8px;
height: 20px;
border-radius: 0 4px 4px 0;
background: rgba(10, 87, 166, 1);
}
.title {
margin-left: 14px;
margin-top: 14px;
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 0px;
text-align: left;
}
.header-btn-box {
position: absolute;
top: 14px;
right: 52px;
display: flex;
.btn {
margin-left: 8px;
height: 28px;
padding: 0 8px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
text-align: center;
line-height: 28px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
cursor: pointer;
}
.btnActive {
border: 1px solid rgba(10, 87, 166, 1);
background: rgba(246, 250, 255, 1);
color: rgba(10, 87, 166, 1);
}
}
.header-info {
height: 22px;
position: absolute;
right: 84px;
top: 17px;
display: flex;
justify-content: flex-end;
.icon {
margin-top: 3px;
width: 14px;
height: 14px;
margin-right: 8px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 22px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
display: flex;
justify-content: flex-end;
gap: 8px;
.icon {
width: 28px;
height: 28px;
img {
width: 100%;
height: 100%;
}
}
.checkboxRight {
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
.btnRightActive {
width: 80px;
height: 28px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
box-sizing: border-box;
border: 1px solid rgba(5, 95, 194, 1);
border-radius: 4px;
background: rgba(231, 243, 255, 1);
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: center;
}
.btnRight {
width: 80px;
height: 28px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: center;
}
}
}
.left {
width: 1064px;
height: 2083px;
.left-main {
border-top: 1px solid rgba(234, 236, 238, 1);
height: 1905px;
.left-main-item {
display: flex;
margin-top: 20px;
gap: 18px;
margin-left: 31px;
height: 130px;
position: relative;
.line {
background: #e6e7e8;
width: 2px;
height: 132px;
position: absolute;
top: 24px;
left: 106px;
}
.time {
display: flex;
flex-direction: column;
.timetext {
width: 79px;
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: right;
}
}
.icon {
width: 24px;
height: 24px;
img {
width: 100%;
height: 100%;
}
}
.info {
width: 862px;
.header {
height: 26px;
display: flex;
justify-content: space-between;
.title {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
}
}
.content {
display: -webkit-box;
-webkit-line-clamp: 2;
/* 限制显示两行 */
-webkit-box-orient: vertical;
/* 垂直排列内容 */
overflow: hidden;
/* 隐藏超出部分 */
text-overflow: ellipsis;
/* 显示省略号 */
/* Text */
width: 862px;
height: 48px;
margin-top: 8px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
.tag-box {
margin-top: 12px;
display: flex;
gap: 8px;
.tag {
height: 28px;
padding: 0 8px;
line-height: 28px;
border-radius: 4px;
background: rgba(231, 243, 255, 1);
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
}
}
}
}
}
.left-footer {
height: 75px;
border-top: 1px solid rgba(234, 236, 238, 1);
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
}
}
.rightcontent {
display: flex;
flex-direction: column;
width: 520px;
gap: 16px;
.right {
width: 520px;
height: 874px;
.right-main {
width: 469px;
margin: 3px auto;
.img-box {
width: 469px;
height: 240px;
img {
width: 100%;
height: 100%;
}
}
.info-box {
margin-top: 26px;
padding-bottom: 50px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
.info-item {
display: flex;
margin-top: 12px;
.info-item-left {
width: 88px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
}
.info-item-right {
width: 356px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.taglist {
width: 358px;
display: flex;
gap: 8px;
flex-wrap: wrap;
.tagdirec {
height: 24px;
justify-content: center;
align-items: center;
padding: 1px 8px 1px 8px;
box-sizing: border-box;
border: 1px solid rgba(186, 224, 255, 1);
border-radius: 4px;
background: rgba(230, 244, 255, 1);
color: rgba(22, 119, 255, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: 0px;
text-align: left;
}
}
}
}
.user-box {
padding-top: 19px;
.user-header {
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
}
.user-content {
margin-top: 19px;
display: flex;
flex-wrap: wrap;
gap: 16px 73px;
justify-content: start;
.user-item {
height: 49px;
display: flex;
gap: 8px;
.user-item-left {
width: 48px;
height: 48px;
img {
width: 100%;
height: 100%;
}
}
.user-item-right {
.name {
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.position {
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: left;
}
}
}
}
}
}
}
.right-Num {
display: flex;
flex-wrap: wrap;
width: 520px;
gap: 8px;
.numbox {
display: flex;
width: 256px;
height: 80px;
box-sizing: border-box;
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);
align-items: center;
.iconrec {
width: 4px;
height: 49px;
background: rgba(5, 95, 194, 1);
}
.awards {
margin-left: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 0px;
text-align: left;
}
.awardsNum {
margin-left: 72px;
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 30px;
font-weight: 700;
line-height: 24px;
letter-spacing: 0px;
text-align: right;
}
}
}
.right-history {
width: 520px;
height: 1009px;
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);
.historytimeline {
display: flex;
margin-left: 37px;
margin-top: 29px;
.timeline {
position: absolute;
width: 8px;
height: 910px;
background: url("./assets/images/arrow.png") repeat;
}
.historyList {
margin-left: -2px;
display: flex;
flex-direction: column;
margin-top: 42px;
gap: 56px;
.historyPoint {
width: 458px;
height: 89px;
gap: 6px;
.historytime {
margin-left: 24px;
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
}
.historyitem {
margin-left: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
}
}
}
}
}
}
::v-deep .el-checkbox__label {
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
padding-left: 3px;
padding-right: 10px;
}
</style>
\ No newline at end of file
<!--科研实力-->
<template>
<div class="detail-wrap">
<div class="row">
<div class="statisticsItem box">
<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="statisticsChart">
<Echarts :option="barOption(patentList)" height="100%"></Echarts>
</div>
<div class="statisticsAI">
<div class="AIbox">
<img src="./assets/images/icon-ai.png" />
<div class="AItext">哈佛大学近十年专利数量呈现稳定增长趋势,尤其在生物技术和人工智能领域表现突出,2025年达到历史新高。</div>
<img src="./assets/images/arrow.png" />
</div>
</div>
</div>
<div class="statisticsItem box">
<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="statisticsChart">
<Echarts :option="lineChart(paperList)" height="100%"></Echarts>
</div>
<div class="statisticsAI">
<div class="AIbox">
<img src="./assets/images/icon-ai.png" />
<div class="AItext">哈佛大学近十年论文发表数量持续增长,高质量论文占比显著提升,特别是在医学和工程领域。</div>
<img src="./assets/images/arrow.png" />
</div>
</div>
</div>
</div>
<div class="row">
<div class="statisticsItem box">
<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="statisticsChart" v-if="studyFieldList.length > 0">
<Echarts :option="raderOption1(studyFieldList)" height="100%"></Echarts>
</div>
<div class="statisticsAI">
<div class="AIbox">
<img src="./assets/images/icon-ai.png" />
<div class="AItext">哈佛大学在生物科技和人工智能领域实力最为突出,同时在量子科技和能源领域也有显著优势,体现了其跨学科研究能力。</div>
<img src="./assets/images/arrow.png" />
</div>
</div>
</div>
<div class="statisticsItem box">
<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="statisticsChart">
<Echarts :option="lineChart1(fundGrowth)" height="100%"></Echarts>
</div>
<div class="statisticsAI">
<div class="AIbox">
<img src="./assets/images/icon-ai.png" />
<div class="AItext">哈佛大学经费近十年保持稳定增长,研究经费占比逐年提高,2023年总经费突破50亿美元。</div>
<img src="./assets/images/arrow.png" />
</div>
</div>
</div>
</div>
<div class="row">
<div class="statisticsItem box">
<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="statisticsChart">
<Echarts :option="pieOption1(fundFromList)" height="100%"></Echarts>
</div>
<div class="statisticsAI">
<div class="AIbox">
<img src="./assets/images/icon-ai.png" />
<div class="AItext">哈佛大学经费主要来源于捐赠基金、政府拨款和企业合作项目,捐赠基金占比最大,体现了其强大的校友支持。</div>
<img src="./assets/images/arrow.png" />
</div>
</div>
</div>
<div class="statisticsItem box">
<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 style="color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: right;">(亿美元)</div>
<div class="statisticsChart" style="height: 298px; ">
<Echarts :option="horizontalBaroption(fundToList)" height="100%"></Echarts>
</div>
<div class="statisticsAI">
<div class="AIbox">
<img src="./assets/images/icon-ai.png" />
<div class="AItext">医学院和工程学院获得的经费分配最多,体现了学校对医学和工程学科的重点投入,这两个学院也是科研成果最丰富的学院。</div>
<img src="./assets/images/arrow.png" />
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import Echarts from "@/components/Chart/index.vue";
import { barOption, lineChart, raderOption1, lineChart1, pieOption1, horizontalBaroption } from "../../utils/charts.js";
import {
getPatentList,
getPaperList, getFundGrowth, getFundFromList, getFundToList, getStudyFieldList
} from "@/api/innovationSubject/overview.js";
import { useRouter } from "vue-router";
const router = useRouter();
//专利数量统计
const patentList = ref([])
const handleGetPatentList = async () => {
try {
let params = {
id: router.currentRoute._value.params.id
}
const res = await getPatentList(params);
console.log("专利数量统计", res);
if (res.code === 200 && res.data) {
patentList.value = res.data
}
} catch (error) {
console.error("获取专利数量统计error", error);
}
};
//论文数量统计
const paperList = ref([])
const handleGetPaperList = async () => {
try {
let params = {
id: router.currentRoute._value.params.id
}
const res = await getPaperList(params);
console.log("论文数量统计", res);
if (res.code === 200 && res.data) {
paperList.value = res.data
}
} catch (error) {
console.error("获取论文数量统计error", error);
}
};
//经费增长情况
const fundGrowth = ref([])
const handleGetFundGrowth = async () => {
try {
let params = {
id: router.currentRoute._value.params.id
}
const res = await getFundGrowth(params);
console.log("经费增长情况", res);
if (res.code === 200 && res.data) {
fundGrowth.value = res.data
}
} catch (error) {
console.error("获取经费增长情况error", error);
}
};
//创新主体科研实力:领域实力分布
const studyFieldList = ref([])
const handleGetStudyFieldList = async () => {
try {
let params = {
id: router.currentRoute._value.params.id
}
const res = await getStudyFieldList(params);
console.log("领域实力分布", res);
if (res.code === 200 && res.data) {
studyFieldList.value = res.data
}
} catch (error) {
console.error("获取领域实力分布error", error);
}
};
//经费来源
const fundFromList = ref([])
const handleGetFundFromList = async () => {
try {
let params = {
id: router.currentRoute._value.params.id
}
const res = await getFundFromList(params);
console.log("经费来源", res);
if (res.code === 200 && res.data) {
fundFromList.value = res.data
}
} catch (error) {
console.error("获取经费来源error", error);
}
};
//学院经费分配排序
const fundToList = ref([])
const handlegGetFundToList = async () => {
try {
let params = {
id: router.currentRoute._value.params.id
}
const res = await getFundToList(params);
console.log("学院经费分配排序", res);
if (res.code === 200 && res.data) {
fundToList.value = res.data
}
} catch (error) {
console.error("获取学院经费分配排序error", error);
}
};
onMounted(async () => {
handleGetFundGrowth()
handleGetPatentList()
handleGetPaperList()
handleGetStudyFieldList()
handleGetFundFromList()
handlegGetFundToList()
});
</script>
<style lang="scss" scoped>
.detail-wrap {
display: flex;
flex-direction: column;
gap: 16px;
padding-bottom: 30px;
.box {
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);
}
.box-header {
height: 56px;
display: flex;
position: relative;
.header-left {
margin-top: 18px;
width: 8px;
height: 20px;
border-radius: 0 4px 4px 0;
background: rgba(10, 87, 166, 1);
}
.title {
margin-left: 14px;
margin-top: 14px;
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 20px;
font-weight: 700;
line-height: 26px;
letter-spacing: 0px;
text-align: left;
}
.header-btn-box {
position: absolute;
top: 14px;
right: 52px;
display: flex;
.btn {
margin-left: 8px;
height: 28px;
padding: 0 8px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
text-align: center;
line-height: 28px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
cursor: pointer;
}
.btnActive {
border: 1px solid rgba(10, 87, 166, 1);
background: rgba(246, 250, 255, 1);
color: rgba(10, 87, 166, 1);
}
}
.header-info {
height: 22px;
position: absolute;
right: 84px;
top: 17px;
display: flex;
justify-content: flex-end;
.icon {
margin-top: 3px;
width: 14px;
height: 14px;
margin-right: 8px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 22px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 22px;
}
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
display: flex;
justify-content: flex-end;
gap: 8px;
.icon {
width: 28px;
height: 28px;
img {
width: 100%;
height: 100%;
}
}
.checkboxRight {
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
.btnRightActive {
width: 80px;
height: 28px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
box-sizing: border-box;
border: 1px solid rgba(5, 95, 194, 1);
border-radius: 4px;
background: rgba(231, 243, 255, 1);
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: center;
}
.btnRight {
width: 80px;
height: 28px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: center;
}
}
}
.row {
display: flex;
width: 1600px;
height: 500px;
gap: 16px;
.statisticsItem {
width: 792px;
height: 500px;
.statisticsChart {
width: 736px;
height: 321px;
margin: 20px auto;
}
.statisticsAI {
margin: 0 auto;
width: 760px;
height: 64px;
/* 自动布局 */
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 10;
padding: 6px 12px 6px 12px;
box-sizing: border-box;
border: 1px solid rgba(231, 243, 255, 1);
border-radius: 4px;
background: rgba(246, 250, 255, 1);
.AIbox {
width: 736px;
height: 52px;
/* 自动布局 */
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: 13px;
padding: 2px 0px 2px 0px;
.AItext {
width: 667px;
height: 48px;
display: flex;
flex-direction: row;
align-items: center;
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
}
}
}
}
}
</style>
\ No newline at end of file
<template>
<div class="cooperation-content">
<Teleport to="body">
<div v-if="tooltipVisible" class="chart-tooltip"
:style="{ left: tooltipPosition.x + 10 + 'px', top: tooltipPosition.y - 30 + 'px' }">
<span class="tooltip-year">{{ tooltipContent.year }}</span>
<span class="tooltip-value">{{ tooltipContent.value }}</span>
</div>
</Teleport>
<div class="chart-row">
<AnalysisBox title="与中国合作数量变化" width="100%" height="500px" :show-all-btn="false" class="chart-box">
<div class="chart-container">
<div class="area-chart">
<div class="y-axis">
<span>400</span>
<span>300</span>
<span>200</span>
<span>100</span>
<span>0</span>
</div>
<div class="chart-area">
<svg class="area-svg" viewBox="0 0 700 240" preserveAspectRatio="none">
<defs>
<linearGradient id="coopAreaGradient1" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="rgba(255, 149, 77, 0.6)" />
<stop offset="100%" stop-color="rgba(255, 149, 77, 0.1)" />
</linearGradient>
</defs>
<path :d="quantityAreaPath" fill="url(#coopAreaGradient1)" />
<path :d="quantityLinePath" fill="none" stroke="#FF954D" stroke-width="2" />
<g class="data-points">
<circle v-for="(point, idx) in quantityPoints" :key="idx"
:cx="point.x" :cy="point.y" r="4"
fill="#FFFFFF" stroke="#FF954D" stroke-width="2"
class="data-point"
@mouseenter="showTooltip($event, chartYears[idx], quantityData[idx])"
@mouseleave="hideTooltip" />
</g>
</svg>
<div class="x-labels">
<span v-for="year in chartYears" :key="year">{{ year }}</span>
</div>
</div>
</div>
</div>
<ChartSummary text="近十年哈佛大学与中国合作项目数量呈'前期稳步增多、2020年后敏感领域收缩、非敏感领域小幅复苏'的趋势。" />
</AnalysisBox>
<AnalysisBox title="与中国合作类型变化" width="100%" height="500px" :show-all-btn="false" class="chart-box">
<template #header-btn>
<el-select v-model="selectedYear" size="small" style="width: 80px;">
<el-option v-for="year in yearOptions" :key="year" :label="year" :value="year" />
</el-select>
</template>
<SemiDonutChart :data="{
names: ['项目合作', '论文合作', '专利合作'],
values: [57, 25, 18]
}" />
<ChartSummary text="哈佛大学与中国的合作当中,大部分是以项目合作为主" />
</AnalysisBox>
</div>
<div class="chart-row">
<AnalysisBox title="与中国合作领域变化" width="100%" height="500px" :show-all-btn="false" class="chart-box">
<div class="chart-container">
<div class="stacked-bar-chart">
<!-- 图例 -->
<div class="chart-legend">
<div v-for="(item, index) in fieldLegend" :key="index" class="legend-item">
<span class="legend-dot" :style="{ background: item.color }"></span>
<span class="legend-label">{{ item.name }}</span>
</div>
</div>
<div class="chart-body">
<div class="y-axis">
<span>400</span>
<span>300</span>
<span>200</span>
<span>100</span>
<span>0</span>
</div>
<div class="bars-area">
<div class="bars">
<div v-for="(item, index) in fieldData" :key="index" class="bar-stack">
<div v-for="(segment, sIdx) in item.segments" :key="sIdx"
class="bar-segment"
:style="{ height: `${segment.value / 400 * 100}%`, background: segment.color }">
</div>
<span class="bar-label">{{ item.year }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<ChartSummary text="近十年哈佛大学与中国合作项目中,各领域分布均匀,其中以人工智能与能源领域为主。" />
</AnalysisBox>
<AnalysisBox title="与中国合作经费变化" width="100%" height="500px" :show-all-btn="false" class="chart-box">
<div class="chart-container">
<div class="area-chart">
<div class="y-axis">
<span>400</span>
<span>300</span>
<span>200</span>
<span>100</span>
<span>0</span>
</div>
<div class="chart-area">
<svg class="area-svg" viewBox="0 0 700 240" preserveAspectRatio="none">
<defs>
<linearGradient id="coopAreaGradient2" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="rgba(34, 197, 94, 0.6)" />
<stop offset="100%" stop-color="rgba(34, 197, 94, 0.1)" />
</linearGradient>
</defs>
<path :d="fundingAreaPath" fill="url(#coopAreaGradient2)" />
<path :d="fundingLinePath" fill="none" stroke="#22C55E" stroke-width="2" />
<!-- 8px圆圈节点 -->
<g class="data-points">
<circle v-for="(point, idx) in fundingPoints" :key="idx"
:cx="point.x" :cy="point.y" r="4"
fill="#FFFFFF" stroke="#22C55E" stroke-width="2"
class="data-point"
@mouseenter="showTooltip($event, chartYears[idx], fundingData[idx])"
@mouseleave="hideTooltip" />
</g>
</svg>
<div class="x-labels">
<span v-for="year in chartYears" :key="year">{{ year }}</span>
</div>
</div>
</div>
</div>
<ChartSummary text="近十年哈佛大学与中国合作项目数量呈'前期稳步增多、2020年后敏感领域收缩、非敏感领域小幅复苏'的趋势。" />
</AnalysisBox>
</div>
<AnalysisBox title="与中国合作事例" width="100%" height="auto" :show-all-btn="false" class="cases-box">
<CooperationCases />
</AnalysisBox>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue'
import ChartSummary from '../components/ChartSummary.vue'
import SemiDonutChart from '../components/SemiDonutChart.vue'
import CooperationCases from '../components/CooperationCases.vue'
// Tooltip 状态
const tooltipVisible = ref(false)
const tooltipContent = ref({ year: '', value: 0 })
const tooltipPosition = ref({ x: 0, y: 0 })
const showTooltip = (event, year, value) => {
tooltipContent.value = { year, value }
tooltipPosition.value = { x: event.clientX, y: event.clientY }
tooltipVisible.value = true
}
const hideTooltip = () => {
tooltipVisible.value = false
}
// 年份选择
const selectedYear = ref('2024')
const yearOptions = ['2024', '2023', '2022', '2021', '2020']
// 图表年份
const chartYears = ['2016', '2017', '2018', '2019', '2020', '2021', '2022', '2023', '2024', '2025']
// 合作数量数据
const quantityData = [150, 200, 280, 320, 300, 180, 120, 100, 80, 60]
// 经费数据
const fundingData = [100, 120, 150, 180, 220, 280, 320, 280, 200, 150]
// 领域图例
// 堆叠柱状图图例颜色(与 HTML 设计稿一致)
const fieldLegend = [
{ name: '集成电路', color: 'rgba(105, 177, 255, 1)' },
{ name: '生物科技', color: 'rgba(255, 192, 105, 1)' },
{ name: '人工智能', color: 'rgba(135, 232, 222, 1)' },
{ name: '通信网络', color: 'rgba(133, 165, 255, 1)' },
{ name: '量子科技', color: 'rgba(255, 120, 117, 1)' },
{ name: '能源领域', color: 'rgba(179, 127, 235, 1)' }
]
// 堆叠柱状图数据 - 固定数据(颜色与图例一致)
const C1 = 'rgba(105, 177, 255, 1)'
const C2 = 'rgba(255, 192, 105, 1)'
const C3 = 'rgba(135, 232, 222, 1)'
const C4 = 'rgba(133, 165, 255, 1)'
const C5 = 'rgba(255, 120, 117, 1)'
const C6 = 'rgba(179, 127, 235, 1)'
const fieldData = [
{ year: '2016', segments: [
{ value: 21, color: C1 }, { value: 18, color: C2 }, { value: 22, color: C3 },
{ value: 16, color: C4 }, { value: 22, color: C5 }, { value: 22, color: C6 }
]},
{ year: '2017', segments: [
{ value: 30, color: C1 }, { value: 18, color: C2 }, { value: 28, color: C3 },
{ value: 16, color: C4 }, { value: 26, color: C5 }, { value: 22, color: C6 }
]},
{ year: '2018', segments: [
{ value: 33, color: C1 }, { value: 28, color: C2 }, { value: 22, color: C3 },
{ value: 16, color: C4 }, { value: 28, color: C5 }, { value: 22, color: C6 }
]},
{ year: '2019', segments: [
{ value: 41, color: C1 }, { value: 26, color: C2 }, { value: 28, color: C3 },
{ value: 28, color: C4 }, { value: 22, color: C5 }, { value: 28, color: C6 }
]},
{ year: '2020', segments: [
{ value: 29, color: C1 }, { value: 29, color: C2 }, { value: 32, color: C3 },
{ value: 44, color: C4 }, { value: 38, color: C5 }, { value: 36, color: C6 }
]},
{ year: '2021', segments: [
{ value: 41, color: C1 }, { value: 29, color: C2 }, { value: 22, color: C3 },
{ value: 27, color: C4 }, { value: 29, color: C5 }, { value: 22, color: C6 }
]},
{ year: '2022', segments: [
{ value: 39, color: C1 }, { value: 18, color: C2 }, { value: 29, color: C3 },
{ value: 25, color: C4 }, { value: 27, color: C5 }, { value: 22, color: C6 }
]},
{ year: '2023', segments: [
{ value: 31, color: C1 }, { value: 18, color: C2 }, { value: 22, color: C3 },
{ value: 22, color: C4 }, { value: 22, color: C5 }, { value: 22, color: C6 }
]},
{ year: '2024', segments: [
{ value: 21, color: C1 }, { value: 18, color: C2 }, { value: 22, color: C3 },
{ value: 16, color: C4 }, { value: 22, color: C5 }, { value: 22, color: C6 }
]},
{ year: '2025', segments: [
{ value: 21, color: C1 }, { value: 10, color: C2 }, { value: 22, color: C3 },
{ value: 16, color: C4 }, { value: 13, color: C5 }, { value: 10, color: C6 }
]}
]
// 面积图路径计算
const createAreaPath = (data, maxValue = 400) => {
const width = 700
const height = 240
const step = width / (data.length - 1)
let linePath = ''
let areaPath = ''
data.forEach((value, index) => {
const x = index * step
const y = height - (value / maxValue) * height
if (index === 0) {
linePath = `M${x},${y}`
areaPath = `M${x},${height} L${x},${y}`
} else {
linePath += ` L${x},${y}`
areaPath += ` L${x},${y}`
}
})
areaPath += ` L${width},${height} Z`
return { linePath, areaPath }
}
const { linePath: quantityLinePath, areaPath: quantityAreaPath } = createAreaPath(quantityData)
const { linePath: fundingLinePath, areaPath: fundingAreaPath } = createAreaPath(fundingData)
// 计算节点坐标
const getDataPoints = (data, maxValue = 400) => {
const width = 700
const height = 240
const step = width / (data.length - 1)
return data.map((value, index) => ({
x: index * step,
y: height - (value / maxValue) * height
}))
}
const quantityPoints = computed(() => getDataPoints(quantityData))
const fundingPoints = computed(() => getDataPoints(fundingData))
</script>
<style lang="scss" scoped>
.cooperation-content {
display: flex;
flex-direction: column;
gap: 16px;
}
.chart-row {
display: flex;
gap: 16px;
.chart-box {
flex: 1;
min-width: 0;
}
}
.chart-container {
padding: 16px;
flex: 1;
display: flex;
flex-direction: column;
}
.area-chart {
flex: 1;
display: flex;
gap: 16px;
.y-axis {
display: flex;
flex-direction: column;
justify-content: space-between;
font-size: 14px;
color: #84888E;
padding: 0 8px 30px 0;
}
.chart-area {
flex: 1;
display: flex;
flex-direction: column;
.area-svg {
flex: 1;
width: 100%;
overflow: visible;
.data-point {
cursor: pointer;
transition: r 0.2s ease;
&:hover {
r: 6;
}
}
}
.x-labels {
display: flex;
justify-content: space-between;
padding-top: 8px;
font-size: 14px;
color: #84888E;
}
}
}
.stacked-bar-chart {
flex: 1;
display: flex;
flex-direction: column;
.chart-legend {
display: flex;
justify-content: center;
gap: 16px;
margin-bottom: 16px;
.legend-item {
display: flex;
align-items: center;
gap: 4px;
.legend-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.legend-label {
font-size: 12px;
color: #5F656C;
}
}
}
.chart-body {
flex: 1;
display: flex;
gap: 16px;
.y-axis {
display: flex;
flex-direction: column;
justify-content: space-between;
font-size: 14px;
color: #84888E;
padding: 0 8px 30px 0;
}
.bars-area {
flex: 1;
.bars {
height: 240px;
display: flex;
align-items: flex-end;
justify-content: space-around;
border-bottom: 1px solid #E5E7EB;
.bar-stack {
display: flex;
flex-direction: column-reverse;
align-items: center;
width: 20px;
height: 240px;
position: relative;
.bar-segment {
width: 100%;
transition: height 0.3s ease;
&:last-child {
border-radius: 4px 4px 0 0;
}
}
.bar-label {
font-size: 14px;
color: #84888E;
margin-top: 8px;
position: absolute;
bottom: -24px;
}
}
}
}
}
}
.cases-box {
:deep(.wrapper-main) {
overflow: visible;
}
}
</style>
<template>
<div class="other-info-content">
<AnalysisBox title="重点实验室" width="100%" height="auto" :show-all-btn="false" class="labs-box">
<div class="labs-grid">
<div v-for="(lab, index) in labsData" :key="index" class="lab-card">
<div class="lab-logo">
<img :src="lab.logo" :alt="lab.name" />
</div>
<div class="lab-name">{{ lab.name }}</div>
<div class="lab-description">{{ lab.description }}</div>
<div class="lab-tags">
<AreaTag v-for="(tag, tagIdx) in lab.tags" :key="tagIdx" :tag-name="tag.name" :type="tag.type" />
</div>
</div>
</div>
</AnalysisBox>
<AnalysisBox title="政策文件" width="100%" height="auto" :show-all-btn="false" class="policy-box">
<div class="policy-grid">
<div v-for="(policy, index) in policyData" :key="index" class="policy-card">
<div class="policy-icon">
<!-- <img src="/images/policy-icon.png" alt="policy" /> -->
</div>
<div class="policy-content">
<div class="policy-title">{{ policy.title }}</div>
<div class="policy-description">{{ policy.description }}</div>
</div>
</div>
</div>
<div class="pagination-area">
<div class="total-count">{{ totalCount }}篇合作动态</div>
<div class="pagination">
<LeftBtn @click="prevPage" />
<span v-for="page in displayPages" :key="page" :class="['page-num', { active: page === currentPage }]" @click="goToPage(page)">
{{ page === '...' ? '...' : page }}
</span>
<RightBtn @click="nextPage" />
</div>
</div>
</AnalysisBox>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue'
import LeftBtn from '@/components/base/PageBtn/LeftBtn.vue'
import RightBtn from '@/components/base/PageBtn/RightBtn.vue'
import AreaTag from '@/components/base/AreaTag/index.vue'
// 重点实验室数据
const labsData = ref([
{
logo: '/images/lab-wyss.png',
name: '怀斯生物启发工程研究所',
description: '成立于2009年,由汉斯约尔格·怀斯(Hansjörg Wyss)捐赠建立,聚焦仿生学与跨学科工程,推动医疗、机器人、材料等领域的突破。',
tags: [{ name: '生物科技', type: 'tag2' }]
},
{
logo: '/images/lab-rowland.png',
name: '罗兰研究所',
description: '原为独立研究机构,2002年并入哈佛,支持高风险、高回报的基础科学研究,尤其鼓励青年科学家。',
tags: [{ name: '物理学', type: 'tag6' }, { name: '化学', type: 'tag10' }]
},
{
logo: '/images/lab-quantum.png',
name: '哈佛量子计划',
description: '跨学院合作平台,整合物理、工程、计算机科学等资源,推动量子科学与技术发展。',
tags: [{ name: '物理', type: 'tag6' }]
},
{
logo: '/images/lab-broad.png',
name: '博德研究所',
description: '全球顶尖基因组学与生物医学研究中心,推动精准医学与疾病机制研究。',
tags: [{ name: '医学', type: 'tag9' }]
},
{
logo: '/images/lab-stem.png',
name: '哈佛干细胞研究所',
description: '成立于2004年,联合哈佛医学院、牙医学院、文理学院等,推动干细胞基础研究与临床转化。',
tags: [{ name: '医学', type: 'tag9' }]
},
{
logo: '/images/lab-cfa.png',
name: '哈佛大学天体物理中心',
description: '由哈佛大学与史密森尼学会于1973年联合成立,是全球规模最大、最活跃的天体物理研究机构之一。',
tags: [{ name: '天体物理', type: 'tag3' }]
}
])
// 政策文件数据
const policyData = ref([
{ title: '《哈佛大学权利与责任声明》', description: '阐明学生在言论自由、学术自由、正当程序、尊重他人等方面的权利与义务。' },
{ title: '《哈佛大学学术诚信政策》', description: '定义抄袭、作弊、伪造等学术不端行为,并规定处理流程。' },
{ title: '《哈佛大学反歧视与反骚扰政策》', description: '禁止基于种族、性别、性取向、宗教、残疾等的歧视与骚扰,明确举报与调查机制。' },
{ title: '《研究合规与人类受试者保护政策》', description: '规范涉及人类受试者的研究(如医学、心理学、社会学实验),确保符合联邦法规(如Common Rule)。' },
{ title: '《哈佛法学院学术政策手册》', description: '详述J.D./LL.M./S.J.D.学位要求、课程规则、成绩制度、书面作业要求、出勤规定、荣誉毕业标准等。' },
{ title: '《哈佛文理研究生院学生手册》', description: '涵盖博士生资格考试、论文提交、助教职责、奖学金续期、学术进展评估等。' }
])
// 分页相关
const currentPage = ref(5)
const totalCount = ref(105)
const totalPages = computed(() => Math.ceil(totalCount.value / 10))
const displayPages = computed(() => {
const pages: (number | string)[] = []
if (totalPages.value <= 7) {
for (let i = 1; i <= totalPages.value; i++) pages.push(i)
} else {
pages.push(1)
if (currentPage.value > 3) pages.push('...')
const start = Math.max(2, currentPage.value - 1)
const end = Math.min(totalPages.value - 1, currentPage.value + 1)
for (let i = start; i <= end; i++) pages.push(i)
if (currentPage.value < totalPages.value - 2) pages.push('...')
pages.push(totalPages.value)
}
return pages
})
// 上一页
const prevPage = () => {
if (currentPage.value > 1) currentPage.value--
}
// 下一页
const nextPage = () => {
if (currentPage.value < totalPages.value) currentPage.value++
}
// 跳转到指定页
const goToPage = (page: number | string) => {
if (typeof page === 'number') currentPage.value = page
}
// 获取标签样式
</script>
<style lang="scss" scoped>
.other-info-content {
display: flex;
flex-direction: column;
gap: 16px;
}
.labs-box {
.labs-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
padding: 16px;
.lab-card {
padding: 16px;
background: #FFFFFF;
border: 1px solid #EAECEE;
border-radius: 4px;
display: flex;
flex-direction: column;
gap: 12px;
.lab-logo {
width: 40px;
height: 40px;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.lab-name {
font-size: 16px;
font-weight: 700;
color: #3B414B;
line-height: 24px;
}
.lab-description {
font-size: 14px;
color: #5F656C;
line-height: 22px;
flex: 1;
}
.lab-tags {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
}
}
}
.policy-box {
.policy-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
padding: 16px;
.policy-card {
display: flex;
gap: 12px;
padding: 16px;
background: #FFFFFF;
border: 1px solid #EAECEE;
border-radius: 4px;
.policy-icon {
width: 40px;
height: 40px;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.policy-content {
flex: 1;
.policy-title {
font-size: 16px;
font-weight: 700;
color: #3B414B;
line-height: 24px;
margin-bottom: 8px;
}
.policy-description {
font-size: 14px;
color: #5F656C;
line-height: 22px;
}
}
}
}
.pagination-area {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
border-top: 1px solid #EAECEE;
.total-count {
font-size: 14px;
color: #84888E;
}
.pagination {
display: flex;
align-items: center;
gap: 8px;
.page-num {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #3B414B;
cursor: pointer;
border-radius: 4px;
&:hover {
background: #F6FAFF;
}
&.active {
background: #055FC2;
color: #FFFFFF;
}
}
}
}
}
</style>
<template>
<div class="research-strength-content">
<!-- 悬浮提示框 -->
<Teleport to="body">
<div v-if="tooltipVisible" class="chart-tooltip"
:style="{ left: tooltipPosition.x + 10 + 'px', top: tooltipPosition.y - 30 + 'px' }">
<span class="tooltip-year">{{ tooltipContent.year }}</span>
<span class="tooltip-value">{{ tooltipContent.value }}</span>
</div>
</Teleport>
<!-- 第一行 -->
<div class="chart-row">
<!-- 专利数量统计 - 柱状图 -->
<AnalysisBox title="专利数量统计" width="100%" height="500px" :show-all-btn="false" class="chart-box">
<div class="chart-container">
<div class="bar-chart">
<div class="y-axis">
<span>400</span>
<span>300</span>
<span>200</span>
<span>100</span>
<span>0</span>
</div>
<div class="chart-area">
<div class="bars">
<div v-for="(item, index) in patentData" :key="index" class="bar-item">
<span class="bar-label">{{ item.year }}</span>
<div class="bar" :style="{ height: `${item.value / 400 * 100}%`, background: getPatentBarGradient() }"></div>
</div>
</div>
</div>
</div>
</div>
<ChartSummary text="哈佛大学近十年专利数量呈现稳定增长趋势,尤其在生物技术和人工智能领域表现突出,2025年达到历史新高。" />
</AnalysisBox>
<!-- 论文数量统计 - 面积图 -->
<AnalysisBox title="论文数量统计" width="100%" height="500px" :show-all-btn="false" class="chart-box">
<div class="chart-container">
<div class="area-chart">
<div class="y-axis">
<span>400</span>
<span>300</span>
<span>200</span>
<span>100</span>
<span>0</span>
</div>
<div class="chart-area">
<svg class="area-svg" viewBox="0 0 700 240" preserveAspectRatio="none">
<defs>
<linearGradient id="areaGradient1" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="rgba(255, 149, 77, 0.6)" />
<stop offset="100%" stop-color="rgba(255, 149, 77, 0.1)" />
</linearGradient>
</defs>
<path :d="paperAreaPath" fill="url(#areaGradient1)" />
<path :d="paperLinePath" fill="none" stroke="#FF954D" stroke-width="2" />
<!-- 小圆圈节点 -->
<g class="data-points">
<circle v-for="(point, idx) in paperPoints" :key="idx"
:cx="point.x" :cy="point.y" r="4"
fill="#FFFFFF" stroke="#FF954D" stroke-width="2"
class="data-point"
@mouseenter="showTooltip($event, chartYears[idx], paperData[idx])"
@mouseleave="hideTooltip" />
</g>
</svg>
<div class="x-labels">
<span v-for="year in chartYears" :key="year">{{ year }}</span>
</div>
</div>
</div>
</div>
<ChartSummary text="哈佛大学近十年论文发表数量持续增长,高质量论文占比显著提升,特别是在医学和工程领域。" />
</AnalysisBox>
</div>
<!-- 第二行 -->
<div class="chart-row">
<!-- 领域实力分布 - 雷达图 -->
<AnalysisBox title="领域实力分布" width="100%" height="500px" :show-all-btn="false" class="chart-box">
<div class="chart-container radar-container">
<div class="radar-chart">
<svg class="radar-svg" viewBox="0 0 300 300">
<!-- 六边形背景网格 -->
<g class="radar-grid">
<polygon v-for="(scale, i) in [1, 0.8, 0.6, 0.4, 0.2]" :key="i"
:points="getHexagonPoints(150, 150, 100 * scale)"
fill="none"
stroke="#E5E7EB"
stroke-width="1" />
</g>
<!-- 轴线 -->
<g class="radar-axes">
<line v-for="(_, i) in 6" :key="i"
x1="150" y1="150"
:x2="150 + 100 * Math.cos(Math.PI / 2 - i * Math.PI / 3)"
:y2="150 - 100 * Math.sin(Math.PI / 2 - i * Math.PI / 3)"
stroke="#E5E7EB"
stroke-width="1" />
</g>
<!-- 数据区域 -->
<polygon :points="radarDataPoints" fill="rgba(5, 95, 194, 0.2)" stroke="#055FC2" stroke-width="2" />
</svg>
<!-- 标签 -->
<div class="radar-labels">
<span class="label label-top">集成电路</span>
<span class="label label-top-right">生物科技</span>
<span class="label label-bottom-right">人工智能</span>
<span class="label label-bottom">通信网络</span>
<span class="label label-bottom-left">量子科技</span>
<span class="label label-top-left">能源领域</span>
</div>
</div>
</div>
<ChartSummary text="哈佛大学在生物科技和人工智能领域实力最为突出,同时在量子科技和能源领域也有显著优势,体现了其跨学科研究能力。" />
</AnalysisBox>
<!-- 大学经费增长情况 - 面积图 -->
<AnalysisBox title="大学经费增长情况" width="100%" height="500px" :show-all-btn="false" class="chart-box">
<div class="chart-container">
<div class="area-chart">
<div class="y-axis">
<span>400</span>
<span>300</span>
<span>200</span>
<span>100</span>
<span>0</span>
</div>
<div class="chart-area">
<svg class="area-svg" viewBox="0 0 700 240" preserveAspectRatio="none">
<defs>
<linearGradient id="areaGradient2" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="rgba(34, 197, 94, 0.6)" />
<stop offset="100%" stop-color="rgba(34, 197, 94, 0.1)" />
</linearGradient>
</defs>
<path :d="fundingAreaPath" fill="url(#areaGradient2)" />
<path :d="fundingLinePath" fill="none" stroke="#22C55E" stroke-width="2" />
<!-- 小圆圈节点 -->
<g class="data-points">
<circle v-for="(point, idx) in fundingPoints" :key="idx"
:cx="point.x" :cy="point.y" r="4"
fill="#FFFFFF" stroke="#22C55E" stroke-width="2"
class="data-point"
@mouseenter="showTooltip($event, chartYears[idx], fundingData[idx])"
@mouseleave="hideTooltip" />
</g>
</svg>
<div class="x-labels">
<span v-for="year in chartYears" :key="year">{{ year }}</span>
</div>
</div>
</div>
</div>
<ChartSummary text="哈佛大学经费近十年保持稳定增长,研究经费占比逐年提高,2023年总经费突破50亿美元。" />
</AnalysisBox>
</div>
<!-- 第三行 -->
<div class="chart-row">
<!-- 经费来源分布 - 环形饼图 -->
<AnalysisBox title="经费来源分布" width="100%" height="500px" :show-all-btn="false" class="chart-box">
<div class="chart-container pie-container">
<div class="pie-chart">
<svg class="pie-svg" viewBox="0 0 200 200">
<circle cx="100" cy="100" r="70" fill="none" stroke="#E5E7EB" stroke-width="30" />
<!-- 各部分 -->
<circle v-for="(item, index) in pieData" :key="index"
cx="100" cy="100" r="70" fill="none"
:stroke="item.color"
stroke-width="30"
:stroke-dasharray="`${item.percent * 4.4} 440`"
:stroke-dashoffset="getPieOffset(index)"
transform="rotate(-90 100 100)" />
</svg>
</div>
<div class="pie-legend">
<div v-for="(item, index) in pieData" :key="index" class="legend-item">
<span class="legend-dot" :style="{ backgroundColor: item.color }"></span>
<span class="legend-label">{{ item.name }}</span>
<span class="legend-value">{{ item.percent }}%</span>
</div>
</div>
</div>
<ChartSummary text="哈佛大学经费主要来源于捐赠基金、政府拨款和企业合作项目,捐赠基金占比最大,体现了其强大的校友支持。" />
</AnalysisBox>
<!-- 学院经费分配排序 - 横向条形图 -->
<AnalysisBox title="学院经费分配排序" width="100%" height="500px" :show-all-btn="false" class="chart-box">
<div class="chart-container horizontal-bar-container">
<div class="unit-label">(亿美元)</div>
<div class="horizontal-bars">
<div v-for="(item, index) in collegeData" :key="index" class="horizontal-bar-item">
<span class="bar-name">{{ item.name }}</span>
<div class="bar-wrapper">
<div class="bar-fill" :style="{ width: `${item.value / 110 * 100}%`, background: getBarGradient(index) }"></div>
</div>
<span class="bar-value" :style="{ color: index < 2 ? '#CE4F51' : '#055FC2' }">{{ item.value }}</span>
</div>
</div>
</div>
<ChartSummary text="医学院和工程学院获得的经费分配最多,体现了学校对医学和工程学科的重点投入,这两个学院也是科研成果最丰富的学院。" />
</AnalysisBox>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue'
import ChartSummary from '../components/ChartSummary.vue'
// Tooltip 状态
const tooltipVisible = ref(false)
const tooltipContent = ref({ year: '', value: 0 })
const tooltipPosition = ref({ x: 0, y: 0 })
const showTooltip = (event, year, value) => {
tooltipContent.value = { year, value }
tooltipPosition.value = { x: event.clientX, y: event.clientY }
tooltipVisible.value = true
}
const hideTooltip = () => {
tooltipVisible.value = false
}
// 专利数据 - 柱状图
const patentData = [
{ year: '2016', value: 80 },
{ year: '2017', value: 90 },
{ year: '2018', value: 120 },
{ year: '2019', value: 180 },
{ year: '2020', value: 220 },
{ year: '2021', value: 250 },
{ year: '2022', value: 280 },
{ year: '2023', value: 300 },
{ year: '2024', value: 340 },
{ year: '2025', value: 360 }
]
// 图表年份
const chartYears = ['2016', '2017', '2018', '2019', '2020', '2021', '2022', '2023', '2024', '2025']
// 论文数据
const paperData = [120, 140, 150, 160, 180, 200, 220, 280, 350, 400]
// 经费数据
const fundingData = [80, 100, 120, 150, 180, 220, 260, 300, 340, 380]
// 计算面积图路径
const createAreaPath = (data, maxValue = 400) => {
const width = 700
const height = 240
const step = width / (data.length - 1)
let linePath = `M 0 ${height - (data[0] / maxValue) * height}`
let areaPath = `M 0 ${height} L 0 ${height - (data[0] / maxValue) * height}`
data.forEach((value, index) => {
const x = index * step
const y = height - (value / maxValue) * height
linePath += ` L ${x} ${y}`
areaPath += ` L ${x} ${y}`
})
areaPath += ` L ${width} ${height} Z`
return { linePath, areaPath }
}
const { linePath: paperLinePath, areaPath: paperAreaPath } = createAreaPath(paperData)
const { linePath: fundingLinePath, areaPath: fundingAreaPath } = createAreaPath(fundingData)
// 计算节点坐标
const getDataPoints = (data, maxValue = 400) => {
const width = 700
const height = 240
const step = width / (data.length - 1)
return data.map((value, index) => ({
x: index * step,
y: height - (value / maxValue) * height
}))
}
const paperPoints = computed(() => getDataPoints(paperData))
const fundingPoints = computed(() => getDataPoints(fundingData))
// 雷达图数据和计算
const radarValues = [0.7, 0.9, 0.85, 0.6, 0.75, 0.65] // 各领域相对��
const getHexagonPoints = (cx, cy, r) => {
const points = []
for (let i = 0; i < 6; i++) {
const angle = Math.PI / 2 - i * Math.PI / 3
const x = cx + r * Math.cos(angle)
const y = cy - r * Math.sin(angle)
points.push(`${x},${y}`)
}
return points.join(' ')
}
const radarDataPoints = computed(() => {
const cx = 150, cy = 150, maxR = 100
const points = radarValues.map((v, i) => {
const angle = Math.PI / 2 - i * Math.PI / 3
const r = v * maxR
return `${cx + r * Math.cos(angle)},${cy - r * Math.sin(angle)}`
})
return points.join(' ')
})
// 饼图数据
const pieData = [
{ name: '捐赠基金', percent: 27, color: '#60A5FA' },
{ name: '政府拨款', percent: 22, color: '#FBBF24' },
{ name: '企业合作', percent: 18, color: '#34D399' },
{ name: '学费收入', percent: 15, color: '#A78BFA' },
{ name: '其他来源', percent: 12, color: '#F87171' }
]
const getPieOffset = (index) => {
let offset = 0
for (let i = 0; i < index; i++) {
offset += pieData[i].percent * 4.4
}
return -offset
}
// 学院经费数据
const collegeData = [
{ name: '医学院', value: 109 },
{ name: '工程学院', value: 95 },
{ name: '商学院', value: 79 },
{ name: '法学院', value: 25 },
{ name: '文理学院', value: 21 },
{ name: '教育学院', value: 21 },
{ name: '教育学院', value: 21 }
]
// 横向条形图渐变:左侧完全透明,右侧深色
const getBarGradient = (index) => {
if (index < 2) {
// 红色:左侧完全透明,右侧深红
return 'linear-gradient(to right, rgba(239, 68, 68, 0), #EF4444)'
}
// 蓝色:左侧完全透明,右侧深蓝
return 'linear-gradient(to right, rgba(59, 130, 246, 0), #3B82F6)'
}
// 专利柱状图渐变:底部透明,顶部深蓝(与学院经费条形图同样风格,方向转为纵向)
const getPatentBarGradient = () => {
return 'linear-gradient(to top, rgba(59, 130, 246, 0), #3B82F6)'
}
</script>
<style lang="scss" scoped>
.research-strength-content {
display: flex;
flex-direction: column;
gap: 16px;
}
// 悬浮提示框(Teleport到body,需要去掉scoped)
:global(.chart-tooltip) {
position: fixed;
z-index: 9999;
background: rgba(0, 0, 0, 0.8);
color: #fff;
padding: 6px 12px;
border-radius: 4px;
font-size: 14px;
white-space: nowrap;
pointer-events: none;
display: flex;
gap: 8px;
.tooltip-year {
color: #ccc;
}
.tooltip-value {
font-weight: 700;
}
}
.chart-row {
display: flex;
gap: 16px;
.chart-box {
flex: 1;
min-width: 0;
}
}
.chart-container {
padding: 16px 24px;
height: calc(100% - 80px);
display: flex;
flex-direction: column;
}
// 柱状图
.bar-chart {
display: flex;
flex: 1;
gap: 12px;
.y-axis {
display: flex;
flex-direction: column;
justify-content: space-between;
font-size: 14px;
color: #84888E;
text-align: right;
padding-right: 8px;
}
.chart-area {
flex: 1;
display: flex;
flex-direction: column;
.bars {
flex: 1;
display: flex;
align-items: flex-end;
gap: 24px;
padding-bottom: 8px;
border-bottom: 1px solid #E5E7EB;
.bar-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
// 柱子从下对齐,年份在顶部
justify-content: flex-end;
.bar-label {
font-size: 14px;
color: #84888E;
margin-bottom: 8px;
}
.bar {
width: 100%;
max-width: 40px;
// 竖向柱:底部完全透明,顶部深蓝的渐变
background: linear-gradient(to top, rgba(59, 130, 246, 0), #3B82F6);
border-radius: 4px 4px 0 0;
transition: height 0.3s ease;
}
}
}
}
}
// 面积图
.area-chart {
display: flex;
flex: 1;
gap: 12px;
.y-axis {
display: flex;
flex-direction: column;
justify-content: space-between;
font-size: 14px;
color: #84888E;
text-align: right;
padding-right: 8px;
}
.chart-area {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
.area-svg {
flex: 1;
width: 100%;
overflow: visible;
.data-point {
cursor: pointer;
transition: r 0.2s ease;
&:hover {
r: 7;
}
}
}
.x-labels {
display: flex;
justify-content: space-between;
padding-top: 8px;
border-top: 1px solid #E5E7EB;
font-size: 14px;
color: #84888E;
}
}
}
// 雷达图
.radar-container {
justify-content: center;
align-items: center;
}
.radar-chart {
position: relative;
width: 300px;
height: 300px;
.radar-svg {
width: 100%;
height: 100%;
}
.radar-labels {
position: absolute;
inset: 0;
.label {
position: absolute;
font-size: 14px;
color: #3B414B;
white-space: nowrap;
&.label-top { top: 0; left: 50%; transform: translateX(-50%); }
&.label-top-right { top: 25%; right: 0; }
&.label-bottom-right { bottom: 25%; right: 0; }
&.label-bottom { bottom: 0; left: 50%; transform: translateX(-50%); }
&.label-bottom-left { bottom: 25%; left: 0; }
&.label-top-left { top: 25%; left: 0; }
}
}
}
// 饼图
.pie-container {
flex-direction: row;
justify-content: center;
align-items: center;
gap: 48px;
}
.pie-chart {
width: 200px;
height: 200px;
.pie-svg {
width: 100%;
height: 100%;
}
}
.pie-legend {
display: flex;
flex-direction: column;
gap: 12px;
.legend-item {
display: flex;
align-items: center;
gap: 8px;
.legend-dot {
width: 10px;
height: 10px;
border-radius: 50%;
}
.legend-label {
font-size: 14px;
color: #3B414B;
min-width: 60px;
}
.legend-value {
font-size: 14px;
color: #84888E;
}
}
}
// 横向条形图
.horizontal-bar-container {
padding-top: 8px;
}
.unit-label {
text-align: right;
font-size: 14px;
color: #84888E;
margin-bottom: 8px;
}
.horizontal-bars {
display: flex;
flex-direction: column;
gap: 12px;
.horizontal-bar-item {
display: flex;
align-items: center;
gap: 12px;
.bar-name {
width: 60px;
font-size: 14px;
color: #3B414B;
text-align: right;
flex-shrink: 0;
}
.bar-wrapper {
flex: 1;
height: 20px;
background: transparent;
border-radius: 2px;
overflow: hidden;
.bar-fill {
height: 100%;
border-radius: 2px;
transition: width 0.3s ease;
}
}
.bar-value {
width: 40px;
font-size: 16px;
font-weight: 700;
text-align: right;
flex-shrink: 0;
}
}
}
// 图表底部摘要
</style>
<template>
<div class="school-detail-content">
<!-- 左侧 - 最新动态 -->
<div class="left-section">
<AnalysisBox title="最新动态" width="100%" height="100%" :show-all-btn="false" class="dynamics-box">
<div class="dynamics-list">
<div v-for="(item, index) in latestDynamics" :key="index" class="dynamic-item">
<!-- 左侧:日期 -->
<div class="time-col">
<div class="year">{{ item.year }}</div>
<div class="date">{{ item.date }}</div>
</div>
<!-- 中间:轴线三段式穿圆心 -->
<div class="axis-col">
<!-- 上段轴线,第一个节点不显示 -->
<div class="axis-line axis-top" :class="{ invisible: index === 0 }"></div>
<!-- 圆形节点 -->
<div :class="['timeline-node', item.isHighlight ? 'highlight' : 'normal']">
<img v-if="item.icon" :src="item.icon" alt="" class="node-img" />
</div>
<!-- 下段轴线,最后一个节点不显示 -->
<div class="axis-line axis-bottom" :class="{ invisible: index === latestDynamics.length - 1 }"></div>
</div>
<!-- 右侧:内容 -->
<div class="content-col">
<h3 class="title">{{ item.title }}</h3>
<p class="desc">{{ item.content }}</p>
<div class="tag-row">
<div class="tag-list">
<AreaTag v-for="(tag, tIndex) in item.tags" :key="tIndex" :tag-name="tag.name" :type="tag.type" />
</div>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination-area">
<div class="total">{{ `共${totalDynamics}项动态` }}</div>
<div class="pagination">
<button class="page-btn" :disabled="currentPage === 1" @click="handlePageChange(currentPage - 1)">&lt;</button>
<button
v-for="page in displayedPages"
:key="page"
:class="['page-num', { active: page === currentPage }]"
@click="typeof page === 'number' && handlePageChange(page)"
>
{{ page }}
</button>
<button class="page-btn" :disabled="currentPage === totalPages" @click="handlePageChange(currentPage + 1)">&gt;</button>
</div>
</div>
</AnalysisBox>
</div>
<!-- 右侧 -->
<div class="right-section">
<!-- 基本信息 -->
<AnalysisBox title="基本信息" width="100%" height="auto" :show-all-btn="false" class="basic-info-box">
<div class="basic-info">
<div class="university-image">
<img :src="basicInfo.image" alt="University" />
</div>
<div class="info-list">
<div class="info-item" v-for="(item, index) in infoList" :key="index">
<span class="label">{{ item.label }}</span>
<span class="value">{{ item.value }}</span>
</div>
<div class="info-item">
<span class="label">重点方向:</span>
<div class="focus-tags">
<AreaTag v-for="(tag, index) in basicInfo.focusTags" :key="index" :tag-name="tag.name" :type="tag.type" />
</div>
</div>
</div>
<!-- 重点人物 -->
<div class="key-people" v-if="keyPeople.length > 0">
<div class="section-label">重点人物:</div>
<!-- 最多展示2*2,超出可滚动 -->
<div class="people-grid" :class="{ 'single-row': keyPeople.length <= 2 }">
<div v-for="(person, index) in keyPeople" :key="index" class="person-card">
<img :src="person.avatar" :alt="person.name" class="avatar" />
<div class="person-info">
<div class="person-name">{{ person.name }}</div>
<div class="person-title">{{ person.title }}</div>
</div>
</div>
</div>
</div>
</div>
</AnalysisBox>
<!-- 统计卡片 -->
<div class="stats-cards">
<div v-for="(stat, index) in statistics" :key="index" class="stat-card">
<div class="stat-left">
<span class="stat-label">{{ stat.label }}</span>
</div>
<div class="stat-right">
<span class="stat-value" :style="{ color: stat.color }">{{ stat.value }}</span>
</div>
<div class="stat-indicator" :style="{ background: stat.color }"></div>
</div>
</div>
<!-- 历史时间轴 -->
<AnalysisBox title="历史时间轴" width="100%" height="auto" :show-all-btn="false" class="history-box">
<HistoryTimeline :events="historyEvents" />
</AnalysisBox>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import AreaTag from '@/components/base/AreaTag/index.vue'
import AnalysisBox from '@/components/base/boxBackground/analysisBox.vue'
import HistoryTimeline from '../components/HistoryTimeline.vue'
// Props
const props = defineProps({
basicInfo: {
type: Object,
default: () => ({
image: '',
establishedTime: '',
location: '',
nature: '',
studentCount: '',
staffCount: '',
qsRanking: '',
focusTags: []
})
},
keyPeople: {
type: Array,
default: () => []
},
statistics: {
type: Array,
default: () => []
},
historyEvents: {
type: Array,
default: () => []
},
latestDynamics: {
type: Array,
default: () => []
},
totalDynamics: {
type: Number,
default: 0
}
})
// Emits
const emit = defineEmits(['page-change'])
// 分页
const currentPage = ref(5)
const totalPages = computed(() => Math.ceil(props.totalDynamics / 7))
const displayedPages = computed(() => {
const pages = []
pages.push(1)
if (currentPage.value > 3) {
pages.push('...')
}
for (let i = Math.max(2, currentPage.value - 1); i <= Math.min(totalPages.value - 1, currentPage.value + 1); i++) {
if (!pages.includes(i)) {
pages.push(i)
}
}
if (currentPage.value < totalPages.value - 2) {
pages.push('...')
}
if (totalPages.value > 1) {
pages.push(totalPages.value)
}
return pages
})
const handlePageChange = (page) => {
if (page >= 1 && page <= totalPages.value) {
currentPage.value = page
emit('page-change', page)
}
}
// 基本信息列表
const infoList = computed(() => [
{ label: '成立时间:', value: props.basicInfo.establishedTime },
{ label: '总部地点:', value: props.basicInfo.location },
{ label: '组织性质:', value: props.basicInfo.nature },
{ label: '在校人数:', value: props.basicInfo.studentCount },
{ label: '教职工数:', value: props.basicInfo.staffCount },
{ label: 'QS排名:', value: props.basicInfo.qsRanking }
])
</script>
<style lang="scss" scoped>
.school-detail-content {
display: flex;
gap: 16px;
align-items: stretch;
.left-section {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
:deep(.dynamics-box),
:deep(.analysis-box-wrapper) {
flex: 1;
display: flex;
flex-direction: column;
.wrapper-main {
flex: 1;
display: flex;
flex-direction: column;
}
}
:deep(.dynamics-box) {
height: 100%;
}
}
.right-section {
width: 520px;
flex-shrink: 0;
display: flex;
flex-direction: column;
gap: 16px;
}
}
$node-size: 24px;
$axis-width: 2px;
.dynamics-list {
padding: 8px 24px 16px;
flex: 1;
overflow-y: auto;
.dynamic-item {
display: flex;
align-items: stretch;
gap: 0;
.time-col {
width: 70px;
flex-shrink: 0;
text-align: right;
padding-right: 16px;
padding-top: calc(#{$node-size} / 2 - 12px);
.year, .date {
font-size: 16px;
font-weight: 700;
font-family: 'Microsoft YaHei', sans-serif;
color: rgb(5, 95, 194);
line-height: 24px;
}
}
.axis-col {
width: $node-size;
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
.axis-line {
width: $axis-width;
background-color: #D8D8D8;
flex: 1;
min-height: 12px;
&.invisible {
background-color: transparent;
}
}
.timeline-node {
width: $node-size;
height: $node-size;
border-radius: 50%;
flex-shrink: 0;
overflow: hidden;
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
&.highlight {
background-color: rgba(245, 34, 45, 1);
}
&.normal {
background-color: rgba(22, 119, 255, 1);
}
.node-img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
}
}
.content-col {
flex: 1;
min-width: 0;
padding-left: 16px;
padding-bottom: 24px;
padding-top: 4px;
.title {
font-size: 20px;
font-weight: 700;
font-family: 'Microsoft YaHei', sans-serif;
color: rgb(59, 65, 75);
line-height: 26px;
margin: 0 0 8px 0;
cursor: pointer;
&:hover {
color: rgb(5, 95, 194);
}
}
.desc {
font-size: 16px;
font-weight: 400;
font-family: 'Microsoft YaHei', sans-serif;
color: rgb(95, 101, 108);
line-height: 24px;
margin: 0 0 8px 0;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
.tag-row {
.tag-list {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
}
}
}
}
.pagination-area {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 30px;
border-top: 1px solid rgb(234, 236, 238);
.total {
font-size: 16px;
font-weight: 400;
font-family: 'Microsoft YaHei', sans-serif;
color: rgb(59, 65, 75);
}
.pagination {
display: flex;
gap: 6px;
.page-btn,
.page-num {
min-width: 32px;
height: 32px;
border-radius: 6px;
border: 1px solid rgba(0, 0, 0, 0.15);
background: #fff;
font-size: 14px;
font-family: 'Microsoft YaHei', sans-serif;
color: rgb(95, 101, 108);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
&:disabled {
color: rgba(95, 101, 108, 0.45);
cursor: not-allowed;
}
&.active {
color: rgba(22, 119, 255, 1);
border-color: rgba(22, 119, 255, 1);
}
}
}
}
.basic-info-box {
:deep(.wrapper-main) {
overflow: visible;
}
}
.basic-info {
padding: 16px 24px;
.university-image {
width: 100%;
height: 200px;
border-radius: 8px;
overflow: hidden;
margin-bottom: 16px;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.info-list {
.info-item {
display: flex;
margin-bottom: 12px;
.label {
width: 88px;
flex-shrink: 0;
font-size: 16px;
font-weight: 700;
font-family: 'Microsoft YaHei', sans-serif;
color: rgb(59, 65, 75);
line-height: 24px;
}
.value {
font-size: 16px;
font-weight: 400;
font-family: 'Microsoft YaHei', sans-serif;
color: rgb(59, 65, 75);
line-height: 24px;
}
.focus-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
}
}
.key-people {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid rgb(234, 236, 238);
.section-label {
font-size: 16px;
font-weight: 700;
font-family: 'Microsoft YaHei', sans-serif;
color: rgb(59, 65, 75);
margin-bottom: 12px;
}
.people-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
max-height: 144px;
overflow-y: auto;
&.single-row {
max-height: 64px;
}
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgb(185, 220, 255);
border-radius: 2px;
}
.person-card {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
height: 64px;
.avatar {
width: 48px;
height: 48px;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
}
.person-info {
flex: 1;
min-width: 0;
.person-name {
font-size: 16px;
font-weight: 700;
font-family: 'Microsoft YaHei', sans-serif;
color: rgb(59, 65, 75);
line-height: 24px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.person-title {
font-size: 14px;
font-weight: 400;
font-family: 'Microsoft YaHei', sans-serif;
color: rgb(95, 101, 108);
line-height: 22px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
}
}
.stats-cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
.stat-card {
position: relative;
background: white;
border-radius: 10px;
border: 1px solid rgba(234, 236, 238, 1);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
padding: 16px 24px;
display: flex;
justify-content: space-between;
align-items: center;
overflow: hidden;
.stat-indicator {
position: absolute;
left: 0;
top: 15px;
bottom: 15px;
width: 4px;
border-radius: 0 2px 2px 0;
}
.stat-label {
font-size: 16px;
font-weight: 400;
font-family: 'Microsoft YaHei', sans-serif;
color: rgb(59, 65, 75);
}
.stat-value {
font-size: 30px;
font-weight: 700;
font-family: 'Microsoft YaHei', sans-serif;
}
}
}
</style>
...@@ -233,8 +233,8 @@ ...@@ -233,8 +233,8 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="box5-main" id="box5Chart"></div> <div class="box5-main" ><WordCloudChart v-if="WordLoading" :data="CharacterOpinionWordCloud" /></div>
</div> </div>
<div class="box6"> <div class="box6">
<div class="box6-header" style="width: 790px"> <div class="box6-header" style="width: 790px">
...@@ -324,6 +324,7 @@ import scrollToTop from "@/utils/scrollToTop"; ...@@ -324,6 +324,7 @@ import scrollToTop from "@/utils/scrollToTop";
import DivideHeader from "@/components/DivideHeader.vue"; import DivideHeader from "@/components/DivideHeader.vue";
import OverviewMainBox from '@/components/base/boxBackground/overviewMainBox.vue' import OverviewMainBox from '@/components/base/boxBackground/overviewMainBox.vue'
import setChart from "@/utils/setChart"; import setChart from "@/utils/setChart";
import WordCloudChart from "@/components/base/WordCloundChart/index.vue"
import { import {
getnewsDynamics, getnewsDynamics,
getBillRiskSignal, getBillRiskSignal,
...@@ -444,7 +445,7 @@ const handlegetareaTypeFn = async () => { ...@@ -444,7 +445,7 @@ const handlegetareaTypeFn = async () => {
} catch (error) { } } catch (error) { }
}; };
const WordLoading=ref(false)
// 获取科技人物观点词云 // 获取科技人物观点词云
const CharacterOpinionWordCloud = ref([]); const CharacterOpinionWordCloud = ref([]);
...@@ -456,6 +457,7 @@ const handlegetCharacterOpinionWordCloudFn = async () => { ...@@ -456,6 +457,7 @@ const handlegetCharacterOpinionWordCloudFn = async () => {
if (wordCloudfield.value !== "0") { if (wordCloudfield.value !== "0") {
params.areaId = wordCloudfield.value; params.areaId = wordCloudfield.value;
} }
WordLoading.value=false
try { try {
const res = await getCharacterOpinionWordCloud(params); const res = await getCharacterOpinionWordCloud(params);
console.log("科技人物观点词云", res); console.log("科技人物观点词云", res);
...@@ -466,7 +468,9 @@ const handlegetCharacterOpinionWordCloudFn = async () => { ...@@ -466,7 +468,9 @@ const handlegetCharacterOpinionWordCloudFn = async () => {
name: item.option, name: item.option,
value: item.count value: item.count
} }
}); });
WordLoading.value=true
} }
} catch (error) { } } catch (error) { }
}; };
...@@ -608,15 +612,15 @@ const ChooseArea = value => { ...@@ -608,15 +612,15 @@ const ChooseArea = value => {
const handleBox5Change = async () => { const handleBox5Change = async () => {
await handlegetCharacterOpinionWordCloudFn(); await handlegetCharacterOpinionWordCloudFn();
const wordCloudChart = getWordCloudChart(CharacterOpinionWordCloud.value); // const wordCloudChart = getWordCloudChart(CharacterOpinionWordCloud.value);
console.log(wordCloudChart,'wordCloudChart') // console.log(wordCloudChart,'wordCloudChart')
setChart(wordCloudChart, "box5Chart"); // setChart(wordCloudChart, "box5Chart");
} }
const handleBox5areaChange = async () => { const handleBox5areaChange = async () => {
await handlegetCharacterOpinionWordCloudFn(); await handlegetCharacterOpinionWordCloudFn();
const wordCloudChart = getWordCloudChart(CharacterOpinionWordCloud.value); // const wordCloudChart = getWordCloudChart(CharacterOpinionWordCloud.value);
setChart(wordCloudChart, "box5Chart"); // setChart(wordCloudChart, "box5Chart");
} }
......
...@@ -51,11 +51,15 @@ export default defineConfig({ ...@@ -51,11 +51,15 @@ export default defineConfig({
rewrite: (path) => path.replace(/^\/reportData/, '') rewrite: (path) => path.replace(/^\/reportData/, '')
}, },
'/api': { '/api': {
target: 'http://8.140.26.4:9085/', target: 'http://8.140.26.4:9085/',
// target: 'http://192.168.0.5:28080/',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '') rewrite: (path) => path.replace(/^\/api/, '')
// '/api': {
// target: 'http://10.119.133.162:28080/',
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/, '')
}, },
'/sseChat': { '/sseChat': {
// target: 'http://192.168.26.115:8000', // target: 'http://192.168.26.115:8000',
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论