dashboard/src/components/Dashboard.vue
“zhuzihan”  15adb29bd3 暂存
2025-07-05 17:05:50 +08:00

1571 lines
40 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="dashboard" style="display: flex; flex-direction: column;position:absolute;top:0;left:0;right:0;bottom:0;">
<!-- 顶部导航栏 -->
<header class="dashboard-header">
<div class="logo">
<img src="../assets/logo1.png" alt="北京理工大学" @click="handleLogoClick" style="cursor: pointer;" />
<img src="../assets/logo2.png" alt="北京理工大学" @click="handleLogoClick" style="cursor: pointer;" />
<h1 class="main-title">
<div class="title-line"></div>
<span class="title-text">智慧科研评估系统</span>
<div class="title-line"></div>
</h1>
</div>
<!-- <div class="year-selector">
<el-dropdown>
<span class="el-dropdown-link">
2025 <el-icon class="el-icon--right"><arrow-down /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>2023</el-dropdown-item>
<el-dropdown-item>2024</el-dropdown-item>
<el-dropdown-item>2025</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div> -->
</header>
<!-- 仪表盘内容 - 三列布局 -->
<div class="dashboard-content" style="flex: 1; overflow: hidden;">
<!-- 第一列科研成果学术产出研究经费 -->
<div class="dashboard-column" style="overflow-y: auto;">
<!-- 科研成果 -->
<div class="dashboard-panel" style="flex: none; height: 130px;min-height:100px">
<div class="panel-header">
<h2>科研成果</h2>
<button class="panel-link" @click="redirectToResearchEvaluation">进入评估 >></button>
</div>
<div class="research-stats">
<div class="stat-card">
<h3>论文数量</h3>
<div class="stat-value"><span class="stat-prefix">累计</span>0</div>
</div>
<div class="stat-card">
<h3>专利数量</h3>
<div class="stat-value"><span class="stat-prefix">本年</span>0</div>
</div>
<div class="stat-card">
<h3>高影响力论文</h3>
<div class="stat-value"><span class="stat-prefix">累计</span>0</div>
</div>
<div class="stat-card">
<h3>科研项目数量</h3>
<div class="stat-value"><span class="stat-prefix">国家重点</span>0<span></span></div>
</div>
</div>
</div>
<!-- 学术产出 -->
<div class="dashboard-panel" style="flex: 1 1 0;">
<h2>学术产出</h2>
<div class="output-content">
<div ref="outputChartRef" class="chart-container-65"></div>
<div class="international-impact">
<h3 style="font-size:25px;border-bottom: 5px solid rgb(73,134,255);text-align: left;padding-bottom:5px ">国际影响力</h3>
<div class="journal-stat">
<span class="journal-name">Nature</span>
<span class="journal-count">15</span>
<span class="journal-name2"></span>
</div>
<div class="journal-stat">
<span class="journal-name">Science</span>
<span class="journal-count">8</span>
<span class="journal-name2"></span>
</div>
</div>
</div>
</div>
<!-- 研究经费 -->
<div class="dashboard-panel" style="flex: 1 1 0;">
<h2>研究经费: 500万元</h2>
<div ref="fundingChartRef" class="chart-container-funding"></div>
</div>
</div>
<!-- 第二列教师科研人才学术奖项新闻与动态 -->
<div class="dashboard-column" style="overflow-y: auto;">
<!-- 教师科研人才 -->
<div class="dashboard-panel" style="flex: 1 1 0;">
<div class="panel-header">
<h2>教师科研人才</h2>
<button class="panel-link" @click="navigate('talent')">进入评估 >></button>
</div>
<div ref="researcherChartRef" class="chart-container"></div>
</div>
<!-- 学术奖项 -->
<div class="dashboard-panel" style="flex: 1 1 0;">
<h2>学术奖项</h2>
<div ref="awardsChartRef" class="chart-container"></div>
</div>
<!-- 教师服务与社会贡献项目分布 -->
<div class="dashboard-panel" style="flex: 1 1 0;">
<h2>教师服务与社会贡献项目分布</h2>
<div ref="teacherServiceChartRef" class="chart-container"></div>
</div>
</div>
<!-- 第三列工程研究中心智能助手 -->
<div class="dashboard-column" style="overflow-y: auto;">
<!-- 工程研究中心 -->
<div class="dashboard-panel" style="flex: 6 1 0;">
<div class="panel-header">
<h2>工程研究中心</h2>
<button class="panel-link" @click="navigate('lab')">进入评估 >></button>
</div>
<div class="lab-charts">
<div ref="labBarChartRef" class="lab-chart-container"></div>
<div class="lab-chart-container">
<div ref="labLineChartRef" class="lab-line-chart"></div>
</div>
</div>
</div>
<!-- 智能助手 -->
<div class="dashboard-panel" style="flex: 4 1 0;">
<h2>智能助手</h2>
<div class="assistant-container">
<div class="assistant-header">
<img :src="dashboardData2?.logoUrl" class="assistant-avatar" />
<h3>北京理工大学</h3>
</div>
<div class="assistant-interface">
<div class="assistant-messages custom-scrollbar" ref="chatMessagesRef">
<div class="assistant-message message">
<div v-html="dashboardData2?.prompt"></div>
</div>
<div v-for="(message, index) in chatMessages" :key="index"
:class="['message', message.role === 'user' ? 'user-message' : 'assistant-message']">
<div v-html="message.content"></div>
</div>
<div v-if="isLoading" class="assistant-message loading-message">
<span class="loading-dot"></span>
<span class="loading-dot"></span>
<span class="loading-dot"></span>
</div>
</div>
<div class="assistant-input">
<input type="text" placeholder="请输入您的问题..." v-model="userInput" @keyup.enter="sendMessage" />
<button class="assistant-send" @click="sendMessage" :disabled="isLoading || !userInput.trim()">发送</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
import * as echarts from 'echarts/core'
import { BarChart, PieChart, LineChart, RadarChart } from 'echarts/charts'
import {
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent
} from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'
import MarkdownIt from 'markdown-it';
const md = new MarkdownIt();
// 向父组件发送页面切换事件
const emit = defineEmits(['navigate', 'logout'])
const navigate = (page) => {
emit('navigate', page)
}
// 处理Logo点击事件
const handleLogoClick = () => {
emit('logout')
}
// 跳转到外部科研成果评估链接
const redirectToResearchEvaluation = () => {
window.open('http://82.156.236.221:10004/login', '_blank');
};
// 注册必要的 echarts 组件
echarts.use([
BarChart,
PieChart,
LineChart,
RadarChart,
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
CanvasRenderer
])
// 图表DOM引用
const researcherChartRef = ref(null)
const labBarChartRef = ref(null)
const labLineChartRef = ref(null)
const outputChartRef = ref(null)
const awardsChartRef = ref(null)
const fundingChartRef = ref(null)
const teacherServiceChartRef = ref(null) // 新增教师服务图表DOM引用
// 教研人才图表数据
const researcherData = ref({
datax: [],
datay: [],
history: [],
ishistory: false
})
// 学术奖项图表数据
const studyData = ref({
datax: [],
datay: []
})
// 教师服务与社会贡献项目分布图表数据
const teacherServiceData = ref({
datax: [],
datay: []
})
// 工程研究中心图表数据
const labData = ref({
datax: [], // 存储 fieldName
series: [], // 存储 ECharts series 数组
years: [] // 存储年份列表
})
const labLineData = ref({
datax: [],
datay: []
})
// DeepSeek 智能助手相关变量
const chatMessagesRef = ref(null)
const userInput = ref('')
const isLoading = ref(false)
const chatMessages = ref([
])
// DeepSeek API 调用函数
const sendMessage = async () => {
if (!userInput.value.trim() || isLoading.value) return
// 添加用户消息到对话
const userMessage = userInput.value.trim()
chatMessages.value.push({ role: 'user', content: userMessage })
userInput.value = ''
// 滚动到底部
await nextTick()
if (chatMessagesRef.value) {
chatMessagesRef.value.scrollTop = chatMessagesRef.value.scrollHeight
}
// 设置加载状态
isLoading.value = true
try {
// 准备发送到 DeepSeek API 的消息历史
const apiMessages = [
{ role: 'system', content: '' }
]
// 添加最近的10条消息历史(如果有的话)
const recentMessages = chatMessages.value.slice(-10)
recentMessages.forEach(msg => {
apiMessages.push({
role: msg.role === 'assistant' ? 'assistant' : 'user',
content: msg.content
})
})
// 调用 DeepSeek API
const response = await fetch('https://api.deepseek.com/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer sk-06801499dd52426fa7cf3b0670931e3a'
},
body: JSON.stringify({
model: 'deepseek-chat',
messages: apiMessages,
stream: false
})
})
if (!response.ok) {
throw new Error('API请求失败')
}
const data = await response.json()
const aiResponse = data.choices && data.choices[0]?.message?.content || '抱歉,我无法回答这个问题。'
const aiResponseHtml = md.render(aiResponse)
// 添加AI回复到对话
chatMessages.value.push({ role: 'assistant', content: aiResponseHtml })
} catch (error) {
console.error('DeepSeek API调用失败:', error)
chatMessages.value.push({ role: 'assistant', content: '抱歉,我遇到了技术问题,请稍后再试。' })
} finally {
isLoading.value = false
// 滚动到底部
await nextTick()
if (chatMessagesRef.value) {
chatMessagesRef.value.scrollTop = chatMessagesRef.value.scrollHeight
}
}
}
// 研究经费图例状态
const fundingLegendStatus = ref([true, true, true])
// 学术产出图例状态
const outputLegendStatus = ref([true, true])
// 切换研究经费图例
const toggleFundingLegend = (index) => {
fundingLegendStatus.value[index] = !fundingLegendStatus.value[index]
updateFundingChart()
}
// 切换学术产出图例
const toggleOutputLegend = (index) => {
outputLegendStatus.value[index] = !outputLegendStatus.value[index]
updateOutputChart()
}
// 更新研究经费图表
const updateFundingChart = () => {
const fundingChart = echarts.getInstanceByDom(fundingChartRef.value)
if (!fundingChart) return
fundingChart.setOption({
legend: {
selected: {
'政府 50%': fundingLegendStatus.value[0],
'企业 25%': fundingLegendStatus.value[1],
'其他 25%': fundingLegendStatus.value[2]
}
}
})
}
// 更新学术产出图表
const updateOutputChart = () => {
const outputChart = echarts.getInstanceByDom(outputChartRef.value)
if (!outputChart) return
outputChart.setOption({
legend: {
selected: {
'论文总数': outputLegendStatus.value[0],
'专利总数': outputLegendStatus.value[1]
}
}
})
}
// 获取仪表盘数据
const dashboardData = ref(null);
const dashboardData2 = ref(null);
const loading = ref(true);
// 引入API配置
import { getApiBaseUrl } from '../config';
// 从API获取仪表盘数据
const fetchDashboardData = async () => {
try {
const response = await fetch(`${getApiBaseUrl()}/admin-api/pg/J-dashboard/dashboard`);
if (response.ok) {
const data = await response.json();
console.log("仪表盘数据:", data);
dashboardData.value = data.data;
// 更新相应的UI数据
if (data) {
updateChartsWithDashboardData();
}
} else {
console.error('获取仪表盘数据失败:', response.statusText);
}
} catch (error) {
console.error('获取仪表盘数据出错:', error);
} finally {
loading.value = false;
}
};
const fetchDashboardData2 = async () => {
try {
const response = await fetch(`${getApiBaseUrl()}/admin-api/pg/intelligent-assistant/get-release`);
const data = await response.json();
dashboardData2.value = data.data;
if (data) {
updateChartsWithDashboardData();
}
} catch (error) {
console.error('获取仪表盘数据出错:', error);
} finally {
loading.value = false;
}
};
const fetchTeacherbData = async (type) => {
try {
const res = await fetch(`${getApiBaseUrl()}/admin-api/pg/teacher-dashboard/get-release?type=${type}`);
if (res.ok) {
const data = await res.json();
// 处理 keyFields 数据
const keyFields = data.data.keyFields || [];
const datax = keyFields.map(item => item.fieldName);
const datay = keyFields.map(item => item.quantity);
if (type === 1) {
const highest = data.data.highestValue || [];
const ishistory = data.data.historyHighest;
const history = highest.map(item => item.quantity);
researcherData.value = {
datax,
datay,
history,
ishistory
};
// 数据更新后,手动更新图表
updateResearcherChart();
} else if (type === 2) {
studyData.value = {
datax: keyFields.map(item => ({ name: item.fieldName,max: Math.max(...datay)})),
datay
};
updateStudyChart();
} else if (type === 3) {
teacherServiceData.value = {
datax,
datay
};
updateTeacherServiceChart();
}
} else {
console.error(`获取教师数据失败 (type=${type}):`, res.statusText);
}
} catch (error) {
console.error(`获取教师数据出错 (type=${type}):`, error);
} finally {
loading.value = false;
}
};
const fetchTeacherbData1 = () => fetchTeacherbData(1);
const fetchTeacherbData2 = () => fetchTeacherbData(2);
const fetchTeacherbData3 = () => fetchTeacherbData(3);
const updateStudyChart = () => {
const awardsChart = echarts.getInstanceByDom(awardsChartRef.value);
if (awardsChart) {
awardsChart.setOption({
radar: {
indicator: studyData.value.datax
},
series: [
{
data: [
{
value: studyData.value.datay
}
]
}
]
});
}
}
const updateResearcherChart = () => {
const researcherChart = echarts.getInstanceByDom(researcherChartRef.value);
if (researcherChart) {
researcherChart.setOption({
legend: {
data: researcherData.value.ishistory ? ['当前数量', '历史最高'] : ['当前数量']
},
yAxis: {
data: researcherData.value.datax
},
series: [
{
name: '当前数量',
data: researcherData.value.datay
},
{
name: '历史最高',
data: researcherData.value.history
}
]
});
}
};
const updateTeacherServiceChart = () => {
const teacherServiceChart = echarts.getInstanceByDom(teacherServiceChartRef.value);
if (teacherServiceChart) {
teacherServiceChart.setOption({
legend: {
data: ['当前数量'],
textStyle: { color: '#fff' }
},
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#fff' } },
axisLabel: { color: '#fff' },
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
color: 'rgba(255, 255, 255, 0.2)'
}
}
},
yAxis: {
type: 'category',
data: teacherServiceData.value.datax,
axisLine: { lineStyle: { color: '#fff' } },
axisLabel: { color: '#fff' },
splitLine: { show: false }
},
series: [
{
name: '当前数量',
type: 'bar',
stack: 'total',
data: teacherServiceData.value.datay,
itemStyle: { color: '#f39c12' }, // 更换颜色
barWidth: '40%',
label: {
show: true,
position: 'inside',
color: '#fff',
formatter: '{c}'
}
}
]
});
}
};
// 定义一个颜色数组,用于不同年份的柱子
const barColors = ['#4080ff', '#d7fc33', 'rgb(63, 196, 15)']; // 蓝色,金色,紫色
const initEngineeringKeyFields = (data) => {
const fieldNames = data.map(item => item.fieldName);
const years = data.length > 0 ? Array.from(new Set(data.flatMap(item => item.yearData.map(y => y.year)))) : [];
years.sort((a, b) => a - b); // 确保年份排序
const series = years.map((year, index) => { // 添加 index 参数
return {
name: year.toString(),
type: 'bar',
barWidth: '15%',
// 根据 index 从颜色数组中获取颜色
itemStyle: {
color: barColors[index % barColors.length] // 确保颜色循环使用
},
data: fieldNames.map(fieldName => {
const item = data.find(d => d.fieldName === fieldName);
const yearData = item ? item.yearData.find(y => y.year === year) : null;
return yearData ? yearData.quantity : 0;
}),
emphasis: {
focus: 'series'
}
};
});
labData.value.datax = fieldNames;
labData.value.series = series;
labData.value.years = years;
console.log("Processed labData.value:", labData.value);
};
const fetchLabData = async () => {
try {
const res = await fetch(`${getApiBaseUrl()}/admin-api/pg/research-data-dashboard/get-release`);
if (res) {
const data = await res.json();
const keyFields = data.data.keyFields || [];
const processedData = keyFields.map((group) => {
const firstItem = group[0];
const yearData = group.map((item) => ({
year: item.year,
quantity: item.quantity
}));
return {
fieldName: firstItem.fieldName,
yearData
};
});
initEngineeringKeyFields(processedData);
updateLabBarChart();
} else {
console.error('获取工程研究中心数据失败:', res.statusText);
}
} catch (error) {
console.error('获取工程研究中心数据出错:', error);
} finally {
loading.value = false;
}
};
const fetchLabData2 = async () => {
try {
const res = await fetch(`${getApiBaseUrl()}/admin-api/pg/changes-in-achievements/get-release`);
if (res) {
const data = await res.json();
const afterAnalysis = data.data.afterAnalysis || [];
const datax = afterAnalysis.map(item => item.year);
const datay = afterAnalysis.map(item => item.quantity);
labLineData.value = {
datax,
datay
};
updateLabLineChart();
} else {
console.error('获取工程研究中心数据失败:', res.statusText);
}
} catch (error) {
console.error('获取工程研究中心数据出错:', error);
} finally {
loading.value = false;
}
};
// 添加更新工程研究中心柱状图的函数
const updateLabBarChart = () => {
const labBarChart = echarts.getInstanceByDom(labBarChartRef.value);
if (labBarChart) {
console.log("Updating labBarChart with:", {
xAxisData: labData.value.datax,
seriesData: labData.value.series
});
labBarChart.setOption({
legend: {
data: labData.value.years.map(String),
textStyle: { color: '#fff' }
},
xAxis: {
data: labData.value.datax,
axisLabel: {
textStyle: { color: '#fff' } // 标签颜色
}
},
series: labData.value.series
}, true);
}
}
const updateLabLineChart = () => {
const labLineChart = echarts.getInstanceByDom(labLineChartRef.value);
if (labLineChart) {
labLineChart.setOption({
xAxis: {
data: labLineData.value.datax
},
series: [
{
data: labLineData.value.datay
}
]
});
}
};
// 使用仪表盘数据更新图表
const updateChartsWithDashboardData = () => {
if (!dashboardData.value) return;
if (!dashboardData2.value) return;
};
// 初始化图表
const initCharts = () => {
// 研究人员图表 - 横向堆叠柱状图(柱中心显示数字)
const researcherChart = echarts.init(researcherChartRef.value)
researcherChart.setOption({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: {
data: researcherData.value.ishistory ? ['当前数量', '历史最高'] : ['当前数量'],
textStyle: { color: '#fff' }
},
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#fff' } },
axisLabel: { color: '#fff' },
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
color: 'rgba(255, 255, 255, 0.2)'
}
}
},
yAxis: {
type: 'category',
data: researcherData.value.datax,
axisLine: { lineStyle: { color: '#fff' } },
axisLabel: { color: '#fff' },
splitLine: { show: false }
},
series: [
{
name: '当前数量',
type: 'bar',
stack: 'total',
data: researcherData.value.datay,
itemStyle: { color: '#a95df3' },
barWidth: '40%', // 减小柱宽度
label: {
show: true,
position: 'inside',
color: '#fff',
formatter: '{c}'
}
},
{
name: '历史最高',
type: 'bar',
stack: 'total',
data: researcherData.value.history,
itemStyle: { color: '#7b49ca' },
label: {
show: true,
position: 'inside',
color: '#fff',
formatter: '{c}'
}
}
]
})
// 教师服务与社会贡献项目分布
const teacherServiceChart = echarts.init(teacherServiceChartRef.value)
teacherServiceChart.setOption({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: {
data: ['当前数量'],
textStyle: { color: '#fff' }
},
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#fff' } },
axisLabel: { color: '#fff' },
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
color: 'rgba(255, 255, 255, 0.2)'
}
}
},
yAxis: {
type: 'category',
data: teacherServiceData.value.datax,
axisLine: { lineStyle: { color: '#fff' } },
axisLabel: { color: '#fff' },
splitLine: { show: false }
},
series: [
{
name: '当前数量',
type: 'bar',
stack: 'total',
data: teacherServiceData.value.datay,
itemStyle: { color: '#f39c12' }, // 更换颜色
barWidth: '40%',
label: {
show: true,
position: 'inside',
color: '#fff',
formatter: '{c}'
}
}
]
})
// 工程研究中心柱状图(调整为占满全部宽度)
const labBarChart = echarts.init(labBarChartRef.value)
labBarChart.setOption({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: {
data: labData.value.years.map(String),
textStyle: { color: '#fff' } //确保图例文字颜色
},
grid: {
left: '3%',
right: '4%',
top: '10%',
bottom: '15%', // 增加底部空间以容纳旋转的标签
containLabel: true
},
xAxis: {
type: 'category',
data: labData.value.datax,
axisLine: { lineStyle: { color: '#fff' } },
axisLabel: {
color: '#fff',
// 换行显示,每 4 个字符换行
formatter: function (params) {
return params.split('').reduce((acc, char, index) => {
if (index % 9 === 0 && index > 0) {
return acc + '\n' + char;
}
return acc + char;
}, '');
},
// 缩小字体大小
fontSize: 10,
}
},
yAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#fff' } },
axisLabel: { color: '#fff' },
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
color: 'rgba(255, 255, 255, 0.2)'
},
interval: 1
}
},
series: labData.value.series
})
// 工程研究中心折线图(调整为占满全部宽度)
const labLineChart = echarts.init(labLineChartRef.value)
labLineChart.setOption({
title: {
text: '近三年科研成果增长曲线',
textStyle: {
color: 'rgba(255, 255, 255, 0.8)',
fontSize: 14,
fontWeight: 'normal'
},
left: 'center',
bottom: '0%'
},
tooltip: { trigger: 'axis' },
grid: {
left: '3%',
right: '4%',
top: '10%',
bottom: '15%', // 为标题留出空间
containLabel: true
},
xAxis: {
type: 'category',
data: labLineData.value.datax,
axisLine: { lineStyle: { color: '#fff' } },
axisLabel: { color: '#fff' }
},
yAxis: {
type: 'value',
axisLine: { lineStyle: { color: '#fff' } },
axisLabel: { color: '#fff' },
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
color: 'rgba(255, 255, 255, 0.2)'
},
interval: 1
}
},
series: [
{
type: 'line',
data: labLineData.value.datay,
lineStyle: { color: '#ffd700', width: 3 },
itemStyle: { color: '#ffd700' },
symbolSize: 8
}
]
})
// 学术产出图表 - 横向柱状图(柱末端显示数字)
const outputChart = echarts.init(outputChartRef.value)
outputChart.setOption({
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
legend: {
data: ['论文总数', '专利总数'],
textStyle: { color: '#fff' },
selected: {
'论文总数': outputLegendStatus.value[0],
'专利总数': outputLegendStatus.value[1]
}
},
grid: { left: '3%', right: '12%', bottom: '3%', containLabel: true },
yAxis: { // 交换了 xAxis 和 yAxis
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月'],
axisLine: { lineStyle: { color: '#fff' } },
axisLabel: { color: '#fff' }
},
xAxis: { // 交换了 xAxis 和 yAxis
type: 'value',
axisLine: { lineStyle: { color: '#fff' } },
axisLabel: { color: '#fff' },
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
color: 'rgba(255, 255, 255, 0.2)'
}
}
},
series: [
{
name: '论文总数',
type: 'bar',
data: [140, 160, 180, 190, 210],
itemStyle: { color: '#4080ff' },
barWidth: '20%',
barGap: '20%',
label: {
show: true,
position: 'right',
color: '#fff',
formatter: '{c}'
}
},
{
name: '专利总数',
type: 'bar',
data: [100, 110, 120, 130, 150],
itemStyle: { color: 'rgb(107,187,196)' },
barWidth: '20%',
barGap: '20%',
label: {
show: true,
position: 'right',
color: '#fff',
formatter: '{c}'
}
}
]
})
// 学术奖项图表 - 金色雷达图
const awardsChart = echarts.init(awardsChartRef.value)
awardsChart.setOption({
radar: {
indicator: studyData.value.datax,
splitArea: { show: false },
axisLine: { lineStyle: { color: 'rgba(255, 255, 255, 0.2)' } },
splitLine: { lineStyle: { color: 'rgba(255, 255, 255, 0.2)' } },
name: { textStyle: { color: '#fff' } },
radius: '70%'
},
series: [
{
type: 'radar',
data: [
{
value: studyData.value.datay,
name: '奖项分布',
areaStyle: { opacity: 0 }, // 移除中间蒙版
lineStyle: { color: '#ffd700', width: 2 },
itemStyle: { color: '#ffd700' }
}
],
label: {
show: true, // 显示标签
position: 'top', // 标签位置
color: '#fff', // 标签颜色
fontSize: 10 // 标签字体大小
},
emphasis: {
label: {
show: true, // 鼠标悬停时也显示标签
fontSize: 12, // 鼠标悬停时字体变大
fontWeight: 'bold' // 鼠标悬停时字体加粗
}
}
}
]
})
// 研究经费图表 - 玫瑰饼图
const fundingChart = echarts.init(fundingChartRef.value)
fundingChart.setOption({
tooltip: { trigger: 'item' },
legend: {
orient: 'vertical',
left: '5%',
top: 'center',
textStyle: { color: '#fff' },
data: ['政府 50%', '企业 25%', '其他 25%'],
selected: {
'政府 50%': fundingLegendStatus.value[0],
'企业 25%': fundingLegendStatus.value[1],
'其他 25%': fundingLegendStatus.value[2]
}
},
series: [
{
type: 'pie',
radius: ['30%', '70%'],
center: ['60%', '50%'], // 将图表向右移动为图例留出空间
roseType: 'area',
label: {
show: true,
formatter: '{b}',
position: 'outside',
color: '#fff',
alignTo: 'labelLine',
distanceToLabelLine: 5
},
labelLine: {
show: true,
length: 15,
length2: 10,
lineStyle: {
color: 'rgba(255, 255, 255, 0.5)'
}
},
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
data: [
{ value: 50, name: '政府 50%', itemStyle: { color: '#9966ff' } },
{ value: 25, name: '企业 25%', itemStyle: { color: '#ff9933' } },
{ value: 25, name: '其他 25%', itemStyle: { color: '#3399ff' } }
]
}
]
})
// 响应窗口大小变化
window.addEventListener('resize', () => {
researcherChart.resize()
labBarChart.resize()
labLineChart.resize()
outputChart.resize()
awardsChart.resize()
fundingChart.resize()
teacherServiceChart.resize()
})
}
// 生命周期钩子 - 组件挂载后初始化
onMounted(async() => {
// 教研人才数据
await fetchTeacherbData1();
await fetchTeacherbData2();
await fetchTeacherbData3(); // 调用新的数据获取函数
// 工程研究中心数据
await fetchLabData();
await fetchLabData2();
// 获取仪表盘数据
await fetchDashboardData();
await fetchDashboardData2();
// 初始化图表
nextTick(() => {
initCharts();
});
});
// 监听仪表盘数据变化
watch(dashboardData, () => {
if (dashboardData.value) {
updateChartsWithData();
}
}, { deep: true });
// 更新图表数据
const updateChartsWithData = () => {
if (!dashboardData.value) return;
// 更新论文数量、专利数量、高影响力论文和关键项目数量
const paperCountEl = document.querySelector('.stat-card:nth-child(1) .stat-value');
const patentCountEl = document.querySelector('.stat-card:nth-child(2) .stat-value');
const highImpactPapersEl = document.querySelector('.stat-card:nth-child(3) .stat-value');
const keyProjectsEl = document.querySelector('.stat-card:nth-child(4) .stat-value');
if (paperCountEl) {
paperCountEl.innerHTML = `<span class="stat-prefix" style="font-size: 12px;">累计</span>${dashboardData.value.paperCount || 0}`;
}
if (patentCountEl) {
patentCountEl.innerHTML = `<span class="stat-prefix" style="font-size: 12px;">本年</span>${dashboardData.value.patentCount || 0}`;
}
if (highImpactPapersEl) {
highImpactPapersEl.innerHTML = `<span class="stat-prefix" style="font-size: 12px;">累计</span>${dashboardData.value.highImpactPapers || 0}`;
}
if (keyProjectsEl) {
keyProjectsEl.innerHTML = `<span class="stat-prefix" style="font-size: 12px;">国家重点</span>${dashboardData.value.keyProjects || 0}<span>项</span>`;
}
// 更新研究经费
const fundingTitle = document.querySelector('.dashboard-panel:nth-child(3) h2');
if (fundingTitle && dashboardData.value.fundingAmount) {
fundingTitle.textContent = `研究经费: ${dashboardData.value.fundingAmount}`;
}
};
// 定义newsData为ref使其可响应
const newsData = ref([]);
// 新闻滚动
let scrollInterval
const startNewsScroll = () => {
if (!newsListRef.value) return
scrollInterval = setInterval(() => {
if (newsListRef.value.scrollTop + newsListRef.value.clientHeight >= newsListRef.value.scrollHeight) {
// 如果已经滚动到底部,重新开始滚动
newsListRef.value.scrollTop = 0
} else {
// 否则继续滚动
newsListRef.value.scrollTop += 1
}
}, 50)
}
// 组件卸载时清理定时器
onUnmounted(() => {
// 移除 resize 事件监听器
window.removeEventListener('resize', () => {
// 这里可以添加移除所有图表实例的逻辑,或者在需要时手动销毁
});
})
</script>
<style>
@import './common.css';
/* 自定义滚动条样式 */
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1);
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: rgba(73, 134, 255, 0.5);
border-radius: 3px;
transition: all 0.3s ease;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: rgba(73, 134, 255, 0.8);
}
.custom-scrollbar {
scrollbar-width: thin;
scrollbar-color: rgba(73, 134, 255, 0.5) rgba(0, 0, 0, 0.1);
}
</style>
<style scoped>
/* 特定于Dashboard的样式 */
.dashboard {
color: white;
}
.dashboard-content {
display: flex;
padding: 15px;
gap: 15px;
}
.dashboard-column {
flex: 1;
display: flex;
flex-direction: column;
gap: 15px;
min-width: 0; /* 防止flex子项溢出 */
max-height: 100%;
}
.dashboard-panel {
background-color: rgba(36, 69, 142, 0.5);
border-radius: 10px;
padding: 15px;
display: flex;
flex-direction: column;
flex: 1;
min-height: 200px; /* 设置最小高度 */
position: relative;
overflow: hidden;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.panel-link {
background: none;
border: none;
color: #4080ff;
cursor: pointer;
font-size: 14px;
}
.chart-container {
width: 100%;
height: 100%;
min-height: 160px;
}
.year-selector {
color: white;
font-size: 18px;
}
.year-selector .el-dropdown-link {
color: white;
font-size: 18px;
display: flex;
align-items: center;
white-space: nowrap;
}
.research-stats {
display: flex;
gap: 10px;
}
.stat-card {
flex: 1;
background-color: rgba(64, 128, 255, 0.2);
border-radius: 5px;
padding: 15px;
text-align: center;
border: 1px solid rgba(73,134,255,1);
color: #ffffff;
}
.stat-card h3 {
margin: 0 0 10px 0;
font-size: 12px;
font-weight: normal;
white-space: nowrap;
}
.stat-value {
font-size: 17px;
font-weight: bold;
color: #4080ff;
display: flex;
justify-content: center;
align-items: baseline;
}
.stat-value .stat-prefix {
font-size: 12px !important;
margin-right: 5px;
white-space: nowrap;
}
/* 学术产出布局 */
.output-content {
display: flex;
flex: 1;
}
.chart-container-65 {
width: 65%;
height: 100%;
}
.international-impact {
width: 35%;
padding: 0 15px;
display: flex;
flex-direction: column;
justify-content: center;
}
.international-impact h3 {
text-align: center;
margin-bottom: 20px;
}
.journal-stat {
margin-bottom: 15px;
font-size: 16px;
text-align: right;
}
.journal-name {
font-weight: bold;
width: 90px;
display: inline-block;
}
.journal-name2{
font-weight: bold;
width: 25px;
display: inline-block;
}
.journal-count {
color: #4080ff;
font-size: 30px;
font-weight: bold;
width: 45px;
display: inline-block;
}
/* 研究经费布局 */
.chart-container-funding {
width: 100%;
height: 100%;
}
/* 图例项 */
.legend-item {
display: flex;
align-items: center;
cursor: pointer;
}
.legend-color {
width: 15px;
height: 15px;
margin-right: 5px;
border-radius: 2px;
}
.legend-inactive {
opacity: 0.5;
}
.legend-inactive .legend-color {
background-color: #666 !important;
}
/* 工程研究中心图表 */
.lab-charts {
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
}
.lab-chart-container {
height: 50%; /* 调整为均等高度 */
width: 100%; /* 确保占满全部宽度 */
position: relative;
margin: 5px 0;
}
.lab-line-chart {
width: 100%;
height: 100%;
}
/* 智能助手样式 */
.assistant-container {
flex: 1;
display: flex;
flex-direction: column;
height: 90%;
}
.assistant-header {
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.assistant-avatar {
height: 30px;
margin-right: 10px;
}
.assistant-header h3 {
margin: 0;
font-size: 16px;
}
.assistant-interface {
display: flex;
flex-direction: column;
overflow-y: auto;
flex: 1;
background-color: rgba(20, 40, 80, 0.3);
border-radius: 5px;
margin-top: 10px;
}
.assistant-messages {
flex: 1;
padding: 10px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 10px;
}
.message {
padding: 10px;
border-radius: 5px;
max-width: 90%;
word-wrap: break-word;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.assistant-message {
background-color: rgba(64, 128, 255, 0.2);
align-self: flex-start;
border-bottom-left-radius: 0;
position: relative;
border-left: 3px solid rgba(73, 134, 255, 0.8);
}
.assistant-message:after {
content: '';
position: absolute;
bottom: 0;
left: -5px;
width: 10px;
height: 10px;
background-color: rgba(64, 128, 255, 0.2);
clip-path: polygon(0 0, 100% 100%, 100% 0);
}
.user-message {
background-color: rgba(73, 134, 255, 0.5);
align-self: flex-end;
border-bottom-right-radius: 0;
color: white;
position: relative;
border-right: 3px solid rgba(73, 134, 255, 0.8);
}
.user-message:after {
content: '';
position: absolute;
bottom: 0;
right: -5px;
width: 10px;
height: 10px;
background-color: rgba(73, 134, 255, 0.5);
clip-path: polygon(0 0, 0 100%, 100% 100%);
}
.loading-message {
display: flex;
justify-content: center;
align-items: center;
padding: 15px;
border: none;
box-shadow: none;
}
.loading-message:after {
display: none;
}
.loading-dot {
width: 8px;
height: 8px;
background-color: #fff;
border-radius: 50%;
margin: 0 3px;
animation: bounce 1.4s infinite ease-in-out both;
}
.loading-dot:nth-child(1) {
animation-delay: -0.32s;
}
.loading-dot:nth-child(2) {
animation-delay: -0.16s;
}
@keyframes bounce {
0%, 80%, 100% {
transform: scale(0);
} 40% {
transform: scale(1.0);
}
}
.assistant-input {
display: flex;
padding: 10px;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.assistant-input input {
flex: 1;
background-color: rgba(255, 255, 255, 0.1);
border: none;
border-radius: 3px;
padding: 8px 12px;
color: white;
font-size: 14px;
transition: all 0.3s ease;
}
.assistant-input input:focus {
outline: none;
background-color: rgba(255, 255, 255, 0.15);
box-shadow: 0 0 0 2px rgba(73, 134, 255, 0.3);
}
.assistant-input input::placeholder {
color: rgba(255, 255, 255, 0.5);
}
.assistant-send {
background-color: #4080ff;
color: white;
border: none;
border-radius: 3px;
padding: 0 15px;
margin-left: 10px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.assistant-send:hover {
background-color: #5090ff;
}
.assistant-send:disabled {
background-color: rgba(64, 128, 255, 0.5);
cursor: not-allowed;
}
@media (max-width: 1200px) {
.output-content {
flex-direction: column;
}
.chart-container-65 {
width: 100%;
height: 200px;
}
.international-impact {
width: 100%;
padding: 15px 0;
}
}
</style>