dashboard/src/components/TalentDetail.vue
2025-06-09 14:59:40 +08:00

806 lines
21 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="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>