dashboard/src/components/TalentDetail.vue

806 lines
21 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">
<!-- 左侧维度设置 -->
<div class="dimension-sidebar">
<div class="sidebar-header">
<h1 class="sidebar-title">
<span class="home-link" @click="jumpToDashboard">首页</span>&nbsp;>&nbsp;教师科研人才评估
</h1>
</div>
<div class="dimension-content">
<h2 class="dimension-section-title">评估维度设置</h2>
<div class="dimension-list">
<div v-for="(dim, index) in dimensions" :key="index" class="dimension-item" >
<div class="dimension-checkbox">
<label :for="`dim-${index}`">{{ dim.name }}</label>
</div>
<div class="dimension-weight">
<span class="weight-label">W:</span>
<span class="weight-value">{{ dim.weight }}%</span>
</div>
</div>
<div class="dimension-add" @click="showAddDimensionDialog">
<span class="add-icon">+</span>
<span class="add-text">添加自定义维度</span>
</div>
</div>
</div>
</div>
<!-- 右侧内容区 -->
<div class="main-content">
<!-- 搜索和操作栏 -->
<div class="action-bar">
<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>
<button class="add-evaluation-btn" @click="openAddEvaluationDrawer">
新增评估
</button>
</div>
<!-- 教师卡片列表 -->
<div class="teacher-card-grid custom-scrollbar">
<div v-for="(teacher, index) in filteredTeachers" :key="index" class="teacher-card" style="height: 300px;" @click="openTeacherDetail(teacher)">
<div class="card-top">
<div class="teacher-left">
<div class="teacher-photo">
<img :src="teacher.photo" alt="教师照片" />
</div>
<div class="teacher-id">ID: {{ teacher.idcode || teacher.id }}</div>
</div>
<div class="teacher-info">
<div class="info-row">
<span class="info-label">姓名:</span>
<span class="info-value">{{ teacher.name }}</span>
</div>
<div class="info-row">
<span class="info-label">教育背景:</span>
<span class="info-value">{{ teacher.education }}</span>
</div>
<div class="info-row">
<span class="info-label">论文:</span>
<span class="info-value">{{ teacher.papers }}</span>
</div>
<div class="info-row">
<span class="info-label">项目:</span>
<span class="info-value">{{ teacher.projects }}</span>
</div>
</div>
</div>
<div class="evaluation-chart">
<div :id="`chart-${index}`" class="radar-chart"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 自定义维度对话框 -->
<el-dialog v-model="dimensionDialogVisible" :title="isEditingDimension ? '编辑维度' : '添加维度'" width="30%" custom-class="dimension-dialog">
<el-form :model="dimensionForm" :rules="dimensionRules" ref="dimensionFormRef" label-position="top">
<el-form-item label="维度名称" prop="name">
<el-input v-model="dimensionForm.name" placeholder="请输入维度名称" />
</el-form-item>
<el-form-item label="权重(W)" prop="weight">
<el-input-number v-model="dimensionForm.weight" :min="1" :max="100" :step="1" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dimensionDialogVisible = false">取消</el-button>
<el-button type="danger" v-if="isEditingDimension" @click="deleteDimension">删除</el-button>
<el-button type="primary" @click="saveDimension">确定</el-button>
</span>
</template>
</el-dialog>
<TalentDrawerDetail
v-model:visible="drawerVisible"
:is-edit="isEditMode"
:dimensions="dimensions"
:teacher-data="selectedTeacher"
@save="handleSaveEvaluation"
/>
<DimensionDrawer
v-model:visible="dimensionDrawerVisible"
:dimensions="dimensions"
category="talent"
@save="handleSaveDimensions"
/>
</template>
<script setup>
import { ref, onMounted, watch, nextTick } from 'vue';
import * as echarts from 'echarts/core';
import { RadarChart } from 'echarts/charts';
// 引入新组件
import DimensionDrawer from './DimensionDrawer.vue';
import TalentDrawerDetail from './TalentDrawerDetail.vue';
import {
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import axios from 'axios'; // 添加axios导入
import { getApiBaseUrl } from '../config'; // 导入API基础URL函数
import { ElMessage } from 'element-plus'; // 导入Element Plus的消息组件
// 注册必要的 echarts 组件
echarts.use([
RadarChart,
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
CanvasRenderer
]);
const drawerVisible = ref(false);
const isEditMode = ref(false);
const selectedTeacher = ref(null);
// 新增drawer可见性控制
const dimensionDrawerVisible = ref(false);
// 修改显示添加维度方法
const showAddDimensionDialog = () => {
// 不再使用dialog
// dimensionDialogVisible.value = true;
// 使用drawer
dimensionDrawerVisible.value = true;
};
// 处理保存维度
const handleSaveDimensions = (updatedDimensions) => {
// 更新维度列表
dimensions.value = updatedDimensions;
// 重新初始化雷达图
updateAllRadarCharts();
dimensionDrawerVisible.value = false;
// 添加保存成功提示
ElMessage.success('维度设置保存成功');
};
// Function to open the drawer for adding a new evaluation
const openAddEvaluationDrawer = () => {
isEditMode.value = false;
selectedTeacher.value = null;
drawerVisible.value = true;
};
// Function to handle save from drawer
const handleSaveEvaluation = (data) => {
if (data._deleted) {
// 处理删除操作
const index = teachers.value.findIndex(t => t.id === data.id);
if (index !== -1) {
teachers.value.splice(index, 1);
}
} else if (isEditMode.value) {
// Update existing teacher data
const index = teachers.value.findIndex(t => t.id === data.id);
if (index !== -1) {
teachers.value[index] = { ...data };
}
} else {
// Add new teacher
teachers.value.push({ ...data });
}
// Update filtered teachers
handleSearch();
};
// 向父组件发送页面切换事件
const emit = defineEmits(['navigate', 'back-to-dashboard', 'logout']);
// 处理Logo点击事件
const handleLogoClick = () => {
emit('logout');
};
// Jump to dashboard page function
const jumpToDashboard = () => {
emit('back-to-dashboard');
};
// 评估维度数据
const dimensions = ref([]); // 改为空数组从API获取数据
// 在组件挂载时获取维度数据
onMounted(async () => {
try {
// 获取教师评估维度数据
const response = await axios.get(`${getApiBaseUrl()}/dimensions/talent`);
dimensions.value = response.data;
// 加载教师数据
await loadTeachers();
// 初始化雷达图
handleSearch();
} catch (error) {
console.error('获取维度数据失败:', error);
ElMessage.error('获取维度数据失败');
}
});
// 加载教师数据
const loadTeachers = async () => {
try {
const response = await axios.get(`${getApiBaseUrl()}/talents`);
teachers.value = response.data;
// 确保每个教师都有照片和教育背景,并重新生成随机评估数据
teachers.value.forEach(teacher => {
// 确保每个教师都有ID
if (!teacher.id) {
teacher.id = 'T' + Math.floor(Math.random() * 10000).toString();
}
// 使用默认头像,不再使用随机头像
if (!teacher.photo) {
teacher.photo = '/image/人1.png';
}
// 如果没有教育背景,生成模拟数据
if (!teacher.educationBackground) {
const eduOptions = [
"清华大学 博士",
"北京大学 博士",
"北京理工大学 博士",
"上海交通大学 博士",
"中国科学院 博士",
"哈尔滨工业大学 博士",
"华中科技大学 博士",
"武汉大学 博士"
];
teacher.educationBackground = eduOptions[Math.floor(Math.random() * eduOptions.length)];
}
// 每次加载都重新生成随机评估数据
const baseValue = teacher.id ? parseInt(teacher.id.slice(-2)) : Math.floor(Math.random() * 30);
// 确保dimensions.value已加载
if (dimensions.value.length > 0) {
teacher.evaluationData = dimensions.value.map((_, dimIndex) => {
const offset = (dimIndex * 7 + baseValue) % 35;
return Math.min(95, Math.max(20, Math.floor(Math.random() * 40) + 30 + offset));
});
}
});
handleSearch();
} catch (error) {
console.error('获取教师数据失败:', error);
ElMessage.error('获取教师数据失败');
}
};
// 维度编辑相关
const dimensionDialogVisible = ref(false);
const isEditingDimension = ref(false);
const currentDimensionIndex = ref(-1);
const dimensionForm = ref({
name: '',
weight: 10
});
const dimensionFormRef = ref(null);
const dimensionRules = {
name: [{ required: true, message: '请输入维度名称', trigger: 'blur' }],
weight: [{ required: true, message: '请输入权重', trigger: 'blur' }]
};
// 编辑维度
const editDimension = (dim, index) => {
isEditingDimension.value = true;
dimensionForm.value = { ...dim };
currentDimensionIndex.value = index;
dimensionDialogVisible.value = true;
};
// 保存维度
const saveDimension = async () => {
if (!dimensionFormRef.value) return;
await dimensionFormRef.value.validate((valid) => {
if (valid) {
if (isEditingDimension.value && currentDimensionIndex.value >= 0) {
// 更新现有维度
dimensions.value[currentDimensionIndex.value] = { ...dimensionForm.value, enabled: true };
} else {
// 添加新维度
dimensions.value.push({ ...dimensionForm.value, enabled: true });
}
dimensionDialogVisible.value = false;
// 重新初始化雷达图
updateAllRadarCharts();
}
});
};
// 删除维度
const deleteDimension = () => {
if (currentDimensionIndex.value >= 0) {
dimensions.value.splice(currentDimensionIndex.value, 1);
dimensionDialogVisible.value = false;
// 重新初始化雷达图
updateAllRadarCharts();
}
};
// 更新所有雷达图
const updateAllRadarCharts = () => {
// 在下一个 tick 后更新图表,确保 DOM 已更新
nextTick(() => {
initRadarCharts();
});
};
// 搜索功能
const searchQuery = ref('');
// 教师数据
const teachers = ref([]);
// 根据搜索过滤教师
const filteredTeachers = ref([]);
// 处理搜索
const handleSearch = () => {
if (searchQuery.value === '') {
filteredTeachers.value = teachers.value;
} else {
filteredTeachers.value = teachers.value.filter(teacher =>
teacher.name.includes(searchQuery.value) ||
teacher.id.includes(searchQuery.value)
);
}
};
// 初始化雷达图
const initRadarCharts = () => {
filteredTeachers.value.forEach((teacher, index) => {
const chartDom = document.getElementById(`chart-${index}`);
if (!chartDom) return;
// 先清空已有的图表实例
echarts.dispose(chartDom);
const chart = echarts.init(chartDom);
// 生成雷达图所需的指标
const indicators = dimensions.value.map(dim => ({
name: dim.name,
max: 100
}));
// 为每个教师生成一个基准值,用于增加各教师数据的差异性
const baseValue = teacher.id ? parseInt(teacher.id.slice(-2)) : Math.floor(Math.random() * 30);
// 确保评估数据的长度与维度数量匹配
let evaluationData = teacher.evaluationData || [];
if (evaluationData.length !== dimensions.value.length) {
// 生成更加随机和独特的评估数据
evaluationData = dimensions.value.map((_, dimIndex) => {
// 使用教师ID、维度索引和基准值来生成差异更大的随机数
const offset = (dimIndex * 7 + baseValue) % 35;
// 生成20-95之间的随机数不同教师、不同维度有不同的随机范围
return Math.min(95, Math.max(20, Math.floor(Math.random() * 40) + 30 + offset));
});
// 更新教师的评估数据
teacher.evaluationData = evaluationData;
}
chart.setOption({
radar: {
indicator: indicators,
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', fontSize: 10 } },
radius: '70%'
},
series: [
{
type: 'radar',
data: [
{
value: evaluationData,
name: '评估结果',
areaStyle: { opacity: 0 }, // 移除半透明的底色
lineStyle: { color: 'rgb(63, 196, 15)', width: 2 }, // 设置线段颜色为RGB(63, 196, 15)
itemStyle: { color: 'rgb(63, 196, 15)' } // 设置点的颜色
}
]
}
]
});
// 添加窗口大小变化时的调整
window.addEventListener('resize', () => {
chart.resize();
});
});
};
// 监听过滤后的教师数据变化,重新初始化图表
watch(filteredTeachers, () => {
// 使用 nextTick 确保 DOM 已更新
nextTick(() => {
initRadarCharts();
});
}, { deep: true });
onMounted(() => {
// 初始化时显示所有教师
filteredTeachers.value = teachers.value;
// 需要延迟一下,确保 DOM 已经渲染
nextTick(() => {
initRadarCharts();
});
});
// 打开教师详情抽屉
const openTeacherDetail = (teacher) => {
selectedTeacher.value = teacher;
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; /* 防止页面整体出现滚动条 */
}
.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;
}
/* 特定于TalentDetail的样式 */
.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;
}
/* 教师卡片网格 */
.teacher-card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
overflow-y: auto; /* 允许卡片区域滚动 */
flex: 1;
padding: 20px;
background-color: #262F50;
border-radius: 10px;
}
/* 维度对话框样式 */
:deep(.dimension-dialog) {
background-color: #1f3266;
color: white;
border-radius: 10px;
}
:deep(.dimension-dialog .el-dialog__header) {
color: white;
border-bottom: 1px solid rgba(73,134,255,0.3);
}
:deep(.dimension-dialog .el-dialog__body) {
color: white;
}
:deep(.dimension-dialog .el-input__inner),
:deep(.dimension-dialog .el-input-number__decrease),
:deep(.dimension-dialog .el-input-number__increase) {
background-color: rgba(255,255,255,0.1);
border-color: rgba(73,134,255,0.3);
color: white;
}
:deep(.dimension-dialog .el-form-item__label) {
color: rgba(255,255,255,0.8);
}
:deep(.dimension-dialog .el-dialog__footer) {
border-top: 1px solid rgba(73,134,255,0.3);
}
@media (max-width: 1200px) {
.dimension-sidebar {
width: 100%;
height: auto;
max-height: 300px;
margin-bottom: 10px;
}
.sidebar-header {
height: auto;
padding: 10px 0;
}
}
</style>