<template>
|
<div class="player-detail">
|
<div class="page-header">
|
<el-button @click="goBack" type="primary" plain>
|
<el-icon><ArrowLeft /></el-icon>
|
返回列表
|
</el-button>
|
<h2>报名详情</h2>
|
</div>
|
|
<div v-if="loading" class="loading-container">
|
<el-skeleton :rows="8" animated />
|
</div>
|
|
<div v-else-if="playerData" class="detail-content">
|
<!-- 基本信息卡片 -->
|
<el-card class="info-card" shadow="hover">
|
<template #header>
|
<div class="card-header">
|
<span>基本信息</span>
|
</div>
|
</template>
|
|
<div class="basic-info">
|
<div class="avatar-section">
|
<el-avatar
|
:size="120"
|
:src="playerData.avatarUrl"
|
:icon="UserFilled"
|
class="player-avatar"
|
/>
|
</div>
|
|
<div class="info-grid">
|
<div class="info-item">
|
<label>姓名:</label>
|
<span>{{ playerData.name || '-' }}</span>
|
</div>
|
<div class="info-item">
|
<label>性别:</label>
|
<span>{{ getGenderText(playerData.gender) }}</span>
|
</div>
|
<div class="info-item">
|
<label>出生日期:</label>
|
<span>{{ formatDate(playerData.birthday) }}</span>
|
</div>
|
<div class="info-item">
|
<label>手机号:</label>
|
<span>{{ playerData.phone || '-' }}</span>
|
</div>
|
<div class="info-item">
|
<label>区域:</label>
|
<span>{{ playerData.regionName || '-' }}</span>
|
</div>
|
<div class="info-item">
|
<label>学历:</label>
|
<span>{{ getEducationText(playerData.education) }}</span>
|
</div>
|
</div>
|
</div>
|
|
<div v-if="playerData.introduction" class="introduction-section">
|
<h3>个人介绍</h3>
|
<div class="introduction-content">{{ playerData.introduction }}</div>
|
</div>
|
</el-card>
|
|
<!-- 参赛项目信息卡片 -->
|
<el-card class="info-card" shadow="hover">
|
<template #header>
|
<div class="card-header">
|
<span>参赛项目信息</span>
|
</div>
|
</template>
|
|
<div class="project-info">
|
<div class="info-item">
|
<label>比赛名称:</label>
|
<span>{{ activityPlayerData.activityName || '-' }}</span>
|
</div>
|
<div class="info-item">
|
<label>报名时间:</label>
|
<span>{{ formatDateTime(activityPlayerData.createTime) }}</span>
|
</div>
|
<div class="info-item">
|
<label>审核状态:</label>
|
<el-tag :type="getStatusType(activityPlayerData.state)">
|
{{ getStatusText(activityPlayerData.state) }}
|
</el-tag>
|
</div>
|
</div>
|
|
<!-- 项目名称单独一行 -->
|
<div v-if="activityPlayerData.projectName" class="project-name-section">
|
<h4>项目名称</h4>
|
<div class="project-name-content">{{ activityPlayerData.projectName }}</div>
|
</div>
|
|
<div v-if="activityPlayerData.description" class="description-section">
|
<h4>项目描述</h4>
|
<div class="description-content">{{ activityPlayerData.description }}</div>
|
</div>
|
|
<!-- 附件列表 -->
|
<div v-if="attachments.length > 0" class="attachments-section">
|
<h4>项目附件</h4>
|
<div class="attachments-list">
|
<div
|
v-for="attachment in attachments"
|
:key="attachment.id"
|
class="attachment-item"
|
>
|
<el-icon class="attachment-icon"><Document /></el-icon>
|
<span class="attachment-name">{{ attachment.originalName }}</span>
|
<el-button
|
type="primary"
|
size="small"
|
@click="downloadAttachment(attachment)"
|
>
|
下载
|
</el-button>
|
</div>
|
</div>
|
</div>
|
</el-card>
|
|
<!-- 审核功能卡片 -->
|
<el-card class="info-card review-card" shadow="hover">
|
<template #header>
|
<div class="card-header">
|
<span>审核管理</span>
|
</div>
|
</template>
|
|
<div class="review-section">
|
<div class="review-status">
|
<label>当前状态:</label>
|
<el-tag :type="getStatusType(activityPlayerData.state)" size="large">
|
{{ getStatusText(activityPlayerData.state) }}
|
</el-tag>
|
</div>
|
|
<div class="feedback-section">
|
<label>审核意见:</label>
|
<el-input
|
v-model="feedbackText"
|
type="textarea"
|
:rows="4"
|
placeholder="请输入审核意见..."
|
maxlength="500"
|
show-word-limit
|
class="feedback-input"
|
/>
|
</div>
|
|
<div class="review-actions">
|
<el-button
|
type="success"
|
@click="handleApprove"
|
:loading="approving"
|
:disabled="activityPlayerData.state === 1"
|
>
|
通过
|
</el-button>
|
<el-button
|
type="danger"
|
@click="handleReject"
|
:loading="rejecting"
|
:disabled="activityPlayerData.state === 2"
|
>
|
驳回
|
</el-button>
|
<el-button
|
type="primary"
|
@click="handleUpdateFeedback"
|
:loading="updating"
|
>
|
更新意见
|
</el-button>
|
<el-button
|
type="info"
|
@click="handleClose"
|
>
|
关闭
|
</el-button>
|
</div>
|
|
<div v-if="activityPlayerData.feedback" class="current-feedback">
|
<label>当前审核意见:</label>
|
<div class="feedback-content">{{ activityPlayerData.feedback }}</div>
|
</div>
|
</div>
|
</el-card>
|
</div>
|
|
<div v-else class="error-container">
|
<el-result
|
icon="warning"
|
title="数据加载失败"
|
sub-title="无法获取参赛人员详情信息"
|
>
|
<template #extra>
|
<el-button type="primary" @click="loadData">重新加载</el-button>
|
</template>
|
</el-result>
|
</div>
|
</div>
|
</template>
|
|
<script setup lang="ts">
|
import { ref, onMounted } from 'vue'
|
import { useRoute, useRouter } from 'vue-router'
|
import { graphqlRequest } from '@/config/api'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ArrowLeft, UserFilled, Document } from '@element-plus/icons-vue'
|
import { approveActivityPlayer, rejectActivityPlayer, updatePlayerFeedback } from '@/api/activityPlayer.js'
|
|
const route = useRoute()
|
const router = useRouter()
|
|
// 响应式数据
|
const loading = ref(true)
|
const playerData = ref<any>(null)
|
const activityPlayerData = ref<any>(null)
|
const attachments = ref<any[]>([])
|
|
// 审核相关数据
|
const feedbackText = ref('')
|
const approving = ref(false)
|
const rejecting = ref(false)
|
const updating = ref(false)
|
|
// 页面加载
|
onMounted(() => {
|
loadData()
|
})
|
|
// 加载数据
|
const loadData = async () => {
|
try {
|
loading.value = true
|
const playerId = route.params.id as string
|
|
// 这里应该调用API获取数据
|
// 暂时使用模拟数据
|
await loadPlayerData(playerId)
|
|
} catch (error) {
|
console.error('加载数据失败:', error)
|
ElMessage.error('加载数据失败')
|
} finally {
|
loading.value = false
|
}
|
}
|
|
// GraphQL查询
|
const ACTIVITY_PLAYER_DETAIL_QUERY = `
|
query ActivityPlayerDetail($id: ID!) {
|
activityPlayerDetail(id: $id) {
|
id
|
playerInfo {
|
id
|
name
|
phone
|
gender
|
birthday
|
education
|
introduction
|
description
|
avatarUrl
|
avatar {
|
id
|
name
|
path
|
fullUrl
|
fullThumbUrl
|
fileSize
|
fileExt
|
mediaType
|
}
|
}
|
regionInfo {
|
id
|
name
|
fullPath
|
}
|
activityName
|
projectName
|
description
|
feedback
|
state
|
submissionFiles {
|
id
|
name
|
url
|
fileExt
|
fileSize
|
mediaType
|
}
|
}
|
}
|
`
|
|
// 使用统一的GraphQL请求函数
|
|
// 加载所有数据
|
const loadPlayerData = async (playerId: string) => {
|
try {
|
const data = await graphqlRequest(ACTIVITY_PLAYER_DETAIL_QUERY, { id: playerId })
|
const detail = data.activityPlayerDetail
|
|
if (detail) {
|
// 设置player基本信息
|
playerData.value = {
|
id: detail.playerInfo.id,
|
name: detail.playerInfo.name,
|
phone: detail.playerInfo.phone,
|
gender: detail.playerInfo.gender,
|
birthday: detail.playerInfo.birthday,
|
education: detail.playerInfo.education,
|
introduction: detail.playerInfo.introduction,
|
description: detail.playerInfo.description,
|
avatarUrl: detail.playerInfo.avatar?.fullUrl || detail.playerInfo.avatarUrl,
|
regionName: detail.regionInfo?.fullPath || detail.regionInfo?.name || '-'
|
}
|
|
// 设置activity_player数据
|
activityPlayerData.value = {
|
id: detail.id,
|
activityName: detail.activityName,
|
projectName: detail.projectName || '-',
|
description: detail.description,
|
feedback: detail.feedback || '',
|
state: detail.state || 0,
|
createTime: new Date().toISOString()
|
}
|
|
// 设置附件数据
|
attachments.value = detail.submissionFiles.map((file: any) => ({
|
id: file.id,
|
originalName: file.name,
|
url: file.url,
|
fileSize: file.fileSize ? `${(file.fileSize / 1024 / 1024).toFixed(1)}MB` : '-'
|
}))
|
|
// 初始化审核意见
|
feedbackText.value = detail.feedback || ''
|
}
|
} catch (error) {
|
console.error('加载数据失败:', error)
|
throw error
|
}
|
}
|
|
// 移除单独的加载函数,统一在loadPlayerData中处理
|
const loadActivityPlayerData = async (playerId: string) => {
|
// 已在loadPlayerData中处理
|
}
|
|
const loadAttachments = async (playerId: string) => {
|
// 已在loadPlayerData中处理
|
}
|
|
// 返回列表
|
const goBack = () => {
|
router.push('/player')
|
}
|
|
// 格式化日期
|
const formatDate = (dateStr: string) => {
|
if (!dateStr) return '-'
|
return new Date(dateStr).toLocaleDateString('zh-CN')
|
}
|
|
// 格式化日期时间
|
const formatDateTime = (dateStr: string) => {
|
if (!dateStr) return '-'
|
return new Date(dateStr).toLocaleString('zh-CN')
|
}
|
|
// 获取性别文本
|
const getGenderText = (gender: number) => {
|
const genderMap: Record<number, string> = {
|
1: '男',
|
2: '女'
|
}
|
return genderMap[gender] || '-'
|
}
|
|
// 获取学历文本
|
const getEducationText = (education: number) => {
|
const educationMap: Record<number, string> = {
|
1: '高中',
|
2: '大专',
|
3: '本科',
|
4: '硕士',
|
5: '博士'
|
}
|
return educationMap[education] || '-'
|
}
|
|
// 获取状态文本
|
const getStatusText = (state: number) => {
|
const statusMap: Record<number, string> = {
|
0: '未审核',
|
1: '审核通过',
|
2: '审核驳回'
|
}
|
return statusMap[state] || '-'
|
}
|
|
// 获取状态类型
|
const getStatusType = (state: number) => {
|
const typeMap: Record<number, string> = {
|
0: 'warning',
|
1: 'success',
|
2: 'danger'
|
}
|
return typeMap[state] || 'info'
|
}
|
|
// 下载附件
|
const downloadAttachment = (attachment: any) => {
|
// TODO: 实现附件下载功能
|
window.open(attachment.url, '_blank')
|
}
|
|
// 审核通过
|
const handleApprove = async () => {
|
try {
|
await ElMessageBox.confirm('确认审核通过该报名申请?', '确认操作', {
|
confirmButtonText: '确认',
|
cancelButtonText: '取消',
|
type: 'warning'
|
})
|
|
approving.value = true
|
const result = await approveActivityPlayer(activityPlayerData.value.id, feedbackText.value)
|
|
if (result.approveActivityPlayer) {
|
ElMessage.success('审核通过成功')
|
activityPlayerData.value.state = 1
|
activityPlayerData.value.feedback = feedbackText.value
|
} else {
|
ElMessage.error('审核通过失败')
|
}
|
} catch (error) {
|
if (error !== 'cancel') {
|
console.error('审核通过失败:', error)
|
ElMessage.error('审核通过失败')
|
}
|
} finally {
|
approving.value = false
|
}
|
}
|
|
// 审核驳回
|
const handleReject = async () => {
|
if (!feedbackText.value.trim()) {
|
ElMessage.warning('驳回时必须填写审核意见')
|
return
|
}
|
|
try {
|
await ElMessageBox.confirm('确认驳回该报名申请?', '确认操作', {
|
confirmButtonText: '确认',
|
cancelButtonText: '取消',
|
type: 'warning'
|
})
|
|
rejecting.value = true
|
const result = await rejectActivityPlayer(activityPlayerData.value.id, feedbackText.value)
|
|
if (result.rejectActivityPlayer) {
|
ElMessage.success('审核驳回成功')
|
activityPlayerData.value.state = 2
|
activityPlayerData.value.feedback = feedbackText.value
|
} else {
|
ElMessage.error('审核驳回失败')
|
}
|
} catch (error) {
|
if (error !== 'cancel') {
|
console.error('审核驳回失败:', error)
|
ElMessage.error('审核驳回失败')
|
}
|
} finally {
|
rejecting.value = false
|
}
|
}
|
|
// 更新审核意见
|
const handleUpdateFeedback = async () => {
|
if (!feedbackText.value.trim()) {
|
ElMessage.warning('请填写审核意见')
|
return
|
}
|
|
try {
|
updating.value = true
|
const result = await updatePlayerFeedback(activityPlayerData.value.id, feedbackText.value)
|
|
if (result.updatePlayerFeedback) {
|
ElMessage.success('审核意见更新成功')
|
activityPlayerData.value.feedback = feedbackText.value
|
} else {
|
ElMessage.error('审核意见更新失败')
|
}
|
} catch (error) {
|
console.error('更新审核意见失败:', error)
|
ElMessage.error('更新审核意见失败')
|
} finally {
|
updating.value = false
|
}
|
}
|
|
// 关闭页面
|
const handleClose = () => {
|
goBack()
|
}
|
</script>
|
|
<style scoped lang="scss">
|
.player-detail {
|
padding: 20px;
|
|
.page-header {
|
display: flex;
|
align-items: center;
|
gap: 16px;
|
margin-bottom: 24px;
|
|
h2 {
|
margin: 0;
|
color: #303133;
|
}
|
}
|
|
.loading-container {
|
padding: 40px;
|
}
|
|
.detail-content {
|
display: flex;
|
flex-direction: column;
|
gap: 24px;
|
}
|
|
.info-card {
|
.card-header {
|
font-weight: 600;
|
color: #303133;
|
}
|
}
|
|
.basic-info {
|
display: flex;
|
gap: 32px;
|
margin-bottom: 24px;
|
|
.avatar-section {
|
flex-shrink: 0;
|
|
.player-avatar {
|
border: 2px solid #e4e7ed;
|
}
|
}
|
|
.info-grid {
|
flex: 1;
|
display: grid;
|
grid-template-columns: repeat(2, 1fr);
|
gap: 16px;
|
|
.info-item {
|
display: flex;
|
align-items: center;
|
|
label {
|
font-weight: 500;
|
color: #606266;
|
min-width: 80px;
|
}
|
|
span {
|
color: #303133;
|
}
|
}
|
}
|
}
|
|
.introduction-section, .description-section {
|
margin-top: 24px;
|
|
h4 {
|
margin: 0 0 12px 0;
|
color: #303133;
|
font-weight: 600;
|
}
|
|
.introduction-content, .description-content {
|
padding: 16px;
|
background-color: #f8f9fa;
|
border-radius: 6px;
|
line-height: 1.6;
|
color: #606266;
|
}
|
}
|
|
.project-info {
|
display: grid;
|
grid-template-columns: 1fr 1fr;
|
gap: 16px;
|
|
.info-item {
|
display: flex;
|
align-items: center;
|
|
label {
|
font-weight: 500;
|
color: #606266;
|
min-width: 100px;
|
}
|
|
span {
|
color: #303133;
|
}
|
}
|
}
|
|
.project-name-section {
|
margin-top: 24px;
|
|
h4 {
|
margin: 0 0 12px 0;
|
color: #303133;
|
font-weight: 600;
|
}
|
|
.project-name-content {
|
padding: 12px;
|
background-color: #f8f9fa;
|
border-radius: 6px;
|
border-left: 4px solid #409eff;
|
color: #303133;
|
font-weight: 500;
|
}
|
}
|
|
.attachments-section {
|
margin-top: 24px;
|
|
h4 {
|
margin: 0 0 16px 0;
|
color: #303133;
|
font-weight: 600;
|
}
|
|
.attachments-list {
|
display: flex;
|
flex-direction: column;
|
gap: 12px;
|
|
.attachment-item {
|
display: flex;
|
align-items: center;
|
gap: 12px;
|
padding: 12px;
|
background-color: #f8f9fa;
|
border-radius: 6px;
|
border: 1px solid #e4e7ed;
|
|
.attachment-icon {
|
color: #409eff;
|
font-size: 20px;
|
}
|
|
.attachment-name {
|
flex: 1;
|
color: #303133;
|
}
|
}
|
}
|
}
|
|
.error-container {
|
padding: 40px;
|
text-align: center;
|
}
|
|
.review-card {
|
margin-top: 24px;
|
|
.review-status {
|
margin-bottom: 16px;
|
|
.status-label {
|
font-weight: 600;
|
color: #303133;
|
margin-right: 8px;
|
}
|
}
|
|
.feedback-section {
|
margin-bottom: 16px;
|
|
.feedback-label {
|
display: block;
|
margin-bottom: 8px;
|
font-weight: 600;
|
color: #303133;
|
}
|
|
.feedback-textarea {
|
width: 100%;
|
min-height: 100px;
|
resize: vertical;
|
}
|
|
.char-count {
|
text-align: right;
|
margin-top: 4px;
|
font-size: 12px;
|
color: #909399;
|
}
|
}
|
|
.review-actions {
|
display: flex;
|
gap: 12px;
|
flex-wrap: wrap;
|
|
.el-button {
|
min-width: 80px;
|
}
|
}
|
|
.current-feedback {
|
margin-top: 16px;
|
padding: 12px;
|
background-color: #f8f9fa;
|
border-radius: 6px;
|
border-left: 4px solid #409eff;
|
|
.feedback-label {
|
font-weight: 600;
|
color: #303133;
|
margin-bottom: 8px;
|
}
|
|
.feedback-content {
|
color: #606266;
|
line-height: 1.6;
|
white-space: pre-wrap;
|
}
|
}
|
}
|
}
|
|
@media (max-width: 768px) {
|
.basic-info {
|
flex-direction: column;
|
align-items: center;
|
text-align: center;
|
|
.info-grid {
|
grid-template-columns: 1fr;
|
}
|
}
|
|
.project-info {
|
grid-template-columns: 1fr;
|
}
|
}
|
</style>
|