dashboard/src/components/LabDetail.vue

895 lines
22 KiB
Vue
Raw Normal View History

2025-06-09 14:59:40 +08:00
<template>
<div class="evaluation-page">
<!-- 顶部导航栏 -->
<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>
</header>
<!-- 主内容区域 -->
<div class="content-container">
<!-- 左侧维度设置 -->
2025-06-13 16:21:35 +08:00
<!-- <div class="dimension-sidebar">
2025-06-09 14:59:40 +08:00
<div class="dimension-content">
<h2 class="dimension-section-title">评估维度设置</h2>
<div class="dimension-list custom-scrollbar">
<div v-for="(dim, index) in dimensions" :key="index" class="dimension-item primary-dimension">
<div class="dimension-header" @click="editDimension(dim, index)">
<div class="dimension-name">
<label>{{ dim.name }}</label>
</div>
<div class="dimension-expand">
<span class="expand-icon"></span>
</div>
</div>
<div class="sub-dimensions">
<div v-for="(subDim, subIndex) in dim.subDimensions" :key="`${index}-${subIndex}`" class="dimension-item sub-dimension">
<div class="dimension-name">
<label>{{ subDim.name }}</label>
</div>
<div class="dimension-weight">
<span class="weight-label">W:</span>
<span class="weight-value">{{ subDim.weight }}%</span>
</div>
</div>
</div>
</div>
<div class="dimension-add" @click="openDimensionDrawer">
<span class="add-icon">+</span>
<span class="add-text">添加自定义维度</span>
</div>
</div>
</div>
2025-06-13 16:21:35 +08:00
</div> -->
2025-06-09 14:59:40 +08:00
<!-- 右侧内容区 -->
<div class="main-content">
<!-- 搜索和操作栏 -->
<div class="action-bar">
2025-06-13 16:21:35 +08:00
<div class="sidebar-header">
<h1 class="sidebar-title">
<span class="home-link" @click="jumpToDashboard">首页</span>&nbsp;>&nbsp;工程研究中心评估
</h1>
</div>
2025-06-09 14:59:40 +08:00
<div class="search-box">
<input
type="text"
placeholder="请输入工程研究中心名称或ID号"
v-model="searchQuery"
@input="handleSearch"
/>
<button class="search-button">
<svg class="search-icon" viewBox="0 0 24 24" width="20" height="20">
<path fill="white" d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
</svg>
</button>
</div>
2025-06-13 16:21:35 +08:00
<!-- <button class="add-evaluation-btn" @click="openAddEvaluationDrawer">
2025-06-09 14:59:40 +08:00
新增评估
2025-06-13 16:21:35 +08:00
</button> -->
2025-06-09 14:59:40 +08:00
</div>
<!-- 工程研究中心卡片列表 -->
<div class="lab-card-grid custom-scrollbar">
2025-06-13 16:21:35 +08:00
<div v-for="(lab, index) in filteredLabs" :key="index" class="lab-card" style="height: 440px;">
<!-- <div v-for="(lab, index) in filteredLabs" :key="index" class="lab-card" style="height: 440px;" @click="openLabDetail(lab)"> -->
2025-06-09 14:59:40 +08:00
<div class="card-header">
<span class="lab-id">ID: {{ lab.idcode || lab.id }}</span>
<!-- <span class="total-score">综合评估分数: <span class="score-value">{{ lab.score }}</span></span> -->
</div>
<div class="card-content">
<div class="lab-image">
<img :src="lab.image" alt="工程研究中心图片" />
</div>
<div class="lab-info">
<div class="info-item">
<span class="info-label">研究中心名称:</span>
<span class="info-value">{{ lab.name }}</span>
</div>
<div class="info-item">
<span class="info-label">所属领域:</span>
<span class="info-value">{{ lab.field }}</span>
</div>
<div class="info-item">
<span class="info-label">所属学校:</span>
<span class="info-value">{{ lab.school }}</span>
</div>
<div class="info-item">
<span class="info-label">主管部门:</span>
<span class="info-value">{{ lab.department }}</span>
</div>
</div>
</div>
<div class="evaluation-chart">
<div :id="`lab-chart-${index}`" class="radar-chart"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 维度设置抽屉 -->
<LabDimensionDrawer
v-model:visible="dimensionDrawerVisible"
:dimensions="dimensions"
@save="handleSaveDimensions"
/>
<LabDrawerDetail
v-model:visible="drawerVisible"
:is-edit="isEditMode"
:dimensions="dimensions"
:lab-data="selectedLab"
@save="handleSaveEvaluation"
/>
</template>
<script setup>
import { ref, onMounted, watch, nextTick } from 'vue';
import * as echarts from 'echarts/core';
import { RadarChart } from 'echarts/charts';
import LabDrawerDetail from './LabDrawerDetail.vue';
import LabDimensionDrawer from './LabDimensionDrawer.vue';
import {
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import axios from 'axios';
import { ElMessage } from 'element-plus';
import { getApiBaseUrl } from '../config'; // 导入API基础URL函数
// 注册必要的 echarts 组件
echarts.use([
RadarChart,
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
CanvasRenderer
]);
const drawerVisible = ref(false);
const isEditMode = ref(false);
const selectedLab = ref(null);
const dimensionDrawerVisible = ref(false);
// 打开添加评估抽屉
const openAddEvaluationDrawer = () => {
isEditMode.value = false;
selectedLab.value = null;
drawerVisible.value = true;
};
// 打开维度设置抽屉
const openDimensionDrawer = () => {
dimensionDrawerVisible.value = true;
};
// 处理保存评估
const handleSaveEvaluation = async (data) => {
// 接收到保存事件后,直接从服务器重新加载最新数据
// 而不是手动更新本地数据
await loadLabs();
// 关闭抽屉
drawerVisible.value = false;
// 显示成功消息
ElMessage.success(isEditMode.value ? '工程研究中心评估数据更新成功' : '工程研究中心评估数据新增成功');
};
// 处理保存维度
const handleSaveDimensions = (newDimensions) => {
dimensions.value = newDimensions;
// 更新所有雷达图
updateAllRadarCharts();
};
// 向父组件发送页面切换事件
const emit = defineEmits(['navigate', 'back-to-dashboard', 'logout']);
// 跳转到仪表盘页面
const jumpToDashboard = () => {
emit('back-to-dashboard');
};
// 处理Logo点击事件
const handleLogoClick = () => {
emit('logout');
};
// 评估维度数据
const dimensions = ref([]);
// 工程研究中心数据
const labs = ref([]);
// 根据搜索过滤工程研究中心
const filteredLabs = ref([]);
// 为每个工程研究中心分配唯一的图片
function assignUniqueLabImages() {
// 确保每个工程研究中心都有不同的图片
labs.value.forEach((lab, index) => {
// 计算1-6之间的序号确保均匀分布
const photoIndex = (index % 6) + 1;
lab.image = `/image/实验室${photoIndex}.png`;
});
}
// 在组件挂载时获取维度数据
onMounted(async () => {
try {
// 获取工程研究中心评估维度数据
2025-06-13 16:21:35 +08:00
const response = await axios.get(`${getApiBaseUrl()}/admin-api/pg/J-dimensions/lab`);
dimensions.value = response.data.data;
dimensions.value.forEach(dim => {
if (typeof dim.subDimensions === 'string') {
try {
dim.subDimensions = JSON.parse(dim.subDimensions);
} catch (error) {
console.error(`解析 subDimensions 失败: ${dim.name}`, error);
dim.subDimensions = []; // 解析失败时设为空数组
}
}
});
2025-06-09 14:59:40 +08:00
// 加载工程研究中心数据
await loadLabs();
// 在此不需要调用handleSearch因为loadLabs中已经调用了
} catch (error) {
console.error('获取维度数据失败:', error);
ElMessage.error('获取维度数据失败');
}
});
// 加载工程研究中心数据
const loadLabs = async () => {
try {
2025-06-13 16:21:35 +08:00
const response = await axios.get(`${getApiBaseUrl()}/admin-api/pg/J-labs/labs`);
labs.value = response.data.data;
2025-06-09 14:59:40 +08:00
// 为工程研究中心分配唯一的图片
assignUniqueLabImages();
// 确保每个工程研究中心都有分数
labs.value.forEach(lab => {
// 确保有分数
if (!lab.score && lab.evaluationData && lab.evaluationData.length > 0) {
// 计算平均分作为综合得分
lab.score = Math.round(lab.evaluationData.reduce((a, b) => a + b, 0) / lab.evaluationData.length);
}
});
// 先更新过滤的工程研究中心列表
handleSearch();
// 给一个短暂的延时确保DOM已经更新
setTimeout(() => {
updateAllRadarCharts();
}, 100);
} catch (error) {
console.error('获取工程研究中心数据失败:', error);
ElMessage.error('获取工程研究中心数据失败');
}
};
// 编辑维度
const editDimension = (dim, index) => {
// 打开维度设置抽屉
openDimensionDrawer();
};
// 更新所有雷达图
const updateAllRadarCharts = () => {
nextTick(() => {
filteredLabs.value.forEach((lab, index) => {
const chartDom = document.getElementById(`lab-chart-${index}`);
if (!chartDom) return;
// 先清空之前的图表实例,避免重复创建
echarts.dispose(chartDom);
const chart = echarts.init(chartDom);
// 从二级维度生成指标
const indicators = [];
if (dimensions.value && dimensions.value.length > 0) {
dimensions.value.forEach(dim => {
if (dim.subDimensions && dim.subDimensions.length > 0) {
dim.subDimensions.forEach(subDim => {
indicators.push({
name: subDim.name,
max: 100
});
});
}
});
}
// 如果没有维度,使用默认维度
if (indicators.length === 0) {
indicators.push(
{ name: '创新水平', max: 100 },
{ name: '研究能力', max: 100 },
{ name: '成果转化', max: 100 },
{ name: '学科建设', max: 100 },
{ name: '行业贡献', max: 100 },
{ name: '发展潜力', max: 100 }
);
}
// 准备雷达图数据
let radarData = [];
// 检查是否有二级维度评估数据
let hasSubDimensionData = false;
if (lab.sub_dimension_evaluations && Object.keys(lab.sub_dimension_evaluations).length > 0) {
dimensions.value.forEach(dim => {
if (dim.subDimensions && dim.subDimensions.length > 0 &&
lab.sub_dimension_evaluations[dim.name]) {
dim.subDimensions.forEach(subDim => {
if (lab.sub_dimension_evaluations[dim.name][subDim.name] !== undefined) {
hasSubDimensionData = true;
radarData.push(lab.sub_dimension_evaluations[dim.name][subDim.name]);
}
});
}
});
}
// 如果没有二级维度数据使用传统的evaluationData
if (!hasSubDimensionData) {
// 确保评估数据与指标数量匹配
if (!lab.evaluationData || lab.evaluationData.length !== indicators.length) {
// 生成随机评估数据范围在60-90之间
lab.evaluationData = indicators.map(() => Math.floor(Math.random() * 31) + 60);
}
radarData = lab.evaluationData;
}
chart.setOption({
radar: {
indicator: indicators,
splitArea: {
show: false
},
axisLine: {
lineStyle: {
color: 'rgba(211, 253, 250, 0.8)'
}
},
splitLine: {
lineStyle: {
color: 'rgba(211, 253, 250, 0.8)'
}
},
name: {
textStyle: {
color: '#fff',
fontSize: 10
}
}
},
series: [
{
type: 'radar',
data: [
{
value: radarData,
name: '评估结果',
areaStyle: {
color: 'rgba(255, 0, 255, 0.3)'
},
lineStyle: {
color: 'rgba(255, 0, 255, 0.8)',
width: 1
},
itemStyle: {
color: 'rgba(255, 0, 255, 0.8)'
}
}
]
}
]
});
// 添加窗口大小变化时的图表调整
window.addEventListener('resize', () => {
chart && chart.resize();
});
});
});
};
// 搜索功能
const searchQuery = ref('');
// 处理搜索
const handleSearch = () => {
if (searchQuery.value === '') {
filteredLabs.value = labs.value;
} else {
filteredLabs.value = labs.value.filter(lab =>
lab.name.includes(searchQuery.value) ||
(lab.idcode && lab.idcode.includes(searchQuery.value))
);
}
// 在过滤后调用更新雷达图
nextTick(() => {
updateAllRadarCharts();
});
};
// 打开工程研究中心详情抽屉
const openLabDetail = (lab) => {
selectedLab.value = lab;
isEditMode.value = true;
drawerVisible.value = true;
};
</script>
<style>
@import './common.css';
</style>
<style scoped>
.evaluation-page {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
display: flex;
flex-direction: column;
background-color: #0c1633;
color: white;
overflow: hidden; /* 防止页面整体出现滚动条 */
}
/* 自定义滚动条样式 */
.custom-scrollbar {
scrollbar-width: thin;
scrollbar-color: rgba(73,134,255,0.5) rgba(38,47,80,0.3);
}
.custom-scrollbar::-webkit-scrollbar {
width: 8px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: rgba(38,47,80,0.3);
border-radius: 4px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background-color: rgba(73,134,255,0.5);
border-radius: 4px;
border: 2px solid rgba(38,47,80,0.3);
}
.dashboard-header {
height: 60px;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
}
.logo {
display: flex;
align-items: center;
position: relative;
width: 100%;
}
.logo img {
height: 40px;
margin-right: 10px;
}
.main-title {
position: absolute;
left: 50%;
transform: translateX(-50%);
font-size: 28px;
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
display: flex;
align-items: center;
white-space: nowrap;
}
.title-line {
border: 2px solid rgba(73,134,255,1);
width: 150px;
}
.title-text {
margin: 0 30px;
}
.content-container {
display: flex;
flex: 1;
padding: 20px;
overflow: hidden;
gap: 20px;
}
/* 特定于LabDetail的样式 */
.sidebar-header {
height: 64px;
display: flex;
align-items: center;
justify-content: left;
background-color: transparent;
}
.sidebar-title {
font-size: 22px;
font-weight: bold;
color: white;
margin: 0;
text-align: left;
}
.home-link {
text-decoration: underline;
cursor: pointer;
color: #4986ff;
}
.dimension-sidebar {
width: 280px;
display: flex;
flex-direction: column;
max-height: calc(100vh - 100px); /* 限制最大高度 */
}
.dimension-content {
flex: 1;
background-color: #262F50;
border-radius: 10px;
display: flex;
flex-direction: column;
overflow: hidden; /* 加上这个防止内容溢出 */
}
.dimension-section-title {
margin: 15px;
font-size: 16px;
text-align: center;
padding-bottom: 10px;
border-bottom: 1px solid rgba(73,134,255,0.3);
}
.dimension-list {
padding: 0 15px 15px 15px;
display: flex;
flex-direction: column;
gap: 15px;
overflow-y: auto; /* 允许列表滚动 */
flex: 1; /* 让列表占满剩余空间 */
}
.dimension-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 10px;
background-color: rgba(73,134,255,0.1);
border-radius: 4px;
border-left: 3px solid #4986ff;
cursor: pointer; /* 添加指针样式,提示可点击 */
transition: background-color 0.2s;
}
.dimension-item:hover {
background-color: rgba(73,134,255,0.2);
}
.dimension-checkbox {
display: flex;
align-items: center;
}
.dimension-checkbox input[type="checkbox"] {
margin-right: 8px;
accent-color: #4986ff;
}
.dimension-weight {
display: flex;
align-items: center;
color: #4986ff;
}
.weight-label {
margin-right: 5px;
}
.weight-value {
font-weight: bold;
}
.dimension-add {
display: flex;
align-items: center;
justify-content: center;
padding: 10px;
background-color: rgba(73,134,255,0.1);
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
transition: background-color 0.2s;
}
.dimension-add:hover {
background-color: rgba(73,134,255,0.2);
}
.add-icon {
font-size: 18px;
margin-right: 5px;
color: #4986ff;
}
.add-text {
color: #4986ff;
font-weight: bold;
}
/* 右侧内容区 */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
padding: 0 10px;
overflow: hidden;
max-height: calc(100vh - 100px); /* 限制最大高度 */
}
/* 搜索和操作栏 */
.action-bar {
height: 64px;
display: flex;
justify-content: space-between;
align-items: center;
}
.search-box {
display: flex;
align-items: center;
width: 300px;
background-color: rgba(255,255,255,0.1);
border-radius: 20px;
overflow: hidden;
}
.search-box input {
flex: 1;
background: transparent;
border: none;
padding: 10px 15px;
color: white;
outline: none;
}
.search-box input::placeholder {
color: rgba(255,255,255,0.5);
}
.search-button {
background: transparent;
border: none;
color: white;
padding: 0 15px;
cursor: pointer;
display: flex;
align-items: center;
}
.search-icon {
fill: white;
}
.add-evaluation-btn {
background-color: rgba(14,62,167,1);
color: rgba(255,255,255,1);
border: 1px solid rgba(73,134,255,1);
border-radius: 10px;
padding: 8px 15px;
font-size: 14px;
text-align: center;
font-family: PingFangSC-regular;
cursor: pointer;
}
/* 工程研究中心卡片网格 */
.lab-card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));
gap: 20px;
overflow-y: auto; /* 允许卡片区域滚动 */
flex: 1;
padding: 20px;
background-color: #262F50;
border-radius: 10px;
}
.lab-card {
background-color: #1f3266;
border-radius: 8px;
overflow: hidden;
border: 1px solid rgba(73,134,255,0.3);
display: flex;
flex-direction: column;
min-height: 350px; /* 调整最小高度,确保雷达图有足够空间 */
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.lab-card:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
background-color: rgba(73,134,255,0.1);
border-bottom: 1px solid rgba(73,134,255,0.3);
}
.lab-id {
font-size: 14px;
color: rgba(255,255,255,0.7);
}
.total-score {
font-size: 14px;
color: rgba(255,255,255,0.7);
}
.score-value {
font-size: 18px;
font-weight: bold;
color: rgb(63, 196, 15);
}
.card-content {
padding: 15px;
display: flex;
gap: 15px;
}
.lab-image {
width: 40%;
height: 120px;
background-color: rgba(73,134,255,0.1);
border-radius: 4px;
overflow: hidden;
}
.lab-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.lab-info {
width: 60%;
display: flex;
flex-direction: column;
gap: 10px;
}
.info-item {
margin-bottom: 5px;
}
.info-label {
color: rgba(255,255,255,0.7);
margin-right: 5px;
}
.info-value {
color: white;
font-weight: 500;
}
.evaluation-chart {
flex: 1; /* 让雷达图占据剩余空间 */
min-height: 200px; /* 确保雷达图最小高度 */
padding: 10px 15px 15px;
display: flex;
align-items: center;
justify-content: center;
}
.radar-chart {
width: 100%;
height: 100%;
min-height: 180px;
}
/* 二级维度样式 */
.primary-dimension {
flex-direction: column;
align-items: stretch;
margin-bottom: 10px;
}
.dimension-header {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.dimension-name {
font-weight: 500;
}
.dimension-expand {
color: #4986ff;
font-size: 12px;
}
.sub-dimensions {
margin-top: 5px;
margin-left: 15px;
}
.sub-dimension {
background-color: rgba(255,255,255,0.05);
border-left: 2px solid rgba(73,134,255,0.6);
margin-bottom: 5px;
}
@media (max-width: 1200px) {
.lab-card-grid {
grid-template-columns: 1fr;
}
.dimension-sidebar {
width: 100%;
height: auto;
max-height: 300px;
margin-bottom: 10px;
}
.sidebar-header {
height: auto;
padding: 10px 0;
}
.card-content {
flex-direction: column;
}
.lab-image, .lab-info {
width: 100%;
}
}
</style>