wuzhen update

This commit is contained in:
wuzhen committed 2025-08-12 20:12:25 +08:00
1 parent 715673dce9
commit b8da4c17bf
7 files changed
+41 -1737

No files matched your search

@@ -1,303 +0,0 @@
<template>
<div class="all-applications">
<!-- 搜索表单 -->
<el-card class="search-card" shadow="never">
<el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
<el-form-item label="岗位名称" prop="positionName">
<el-input v-model="queryParams.positionName" placeholder="请输入岗位名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="学生姓名" prop="studentName">
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable style="width: 200px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="学号" prop="studentNo">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable style="width: 200px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="申请状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择申请状态" clearable style="width: 200px">
<el-option v-for="option in statusOptions" :key="option.value" :label="option.label" :value="option.value" />
</el-select>
</el-form-item>
<el-form-item label="申请日期" prop="applicationDate">
<el-date-picker
v-model="queryParams.applicationDate"
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 240px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 数据表格 -->
<el-card class="table-card" shadow="never">
<div class="table-header">
<div class="table-title">
<el-icon><List /></el-icon>
所有申请列表
</div>
</div>
<el-table v-loading="loading" :data="applicationList" row-key="id" stripe>
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="岗位名称" prop="positionName" min-width="120" show-overflow-tooltip />
<el-table-column label="学生信息" min-width="160">
<template #default="{ row }">
<div class="student-info">
<div class="name">{{ row.studentName }}</div>
<div class="detail">{{ row.studentNo }} | {{ row.className }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="专业年级" min-width="140">
<template #default="{ row }">
<div>{{ row.majorName }}</div>
<div class="text-gray">{{ row.grade }}</div>
</template>
</el-table-column>
<el-table-column label="联系方式" min-width="120">
<template #default="{ row }">
<div>{{ row.contactPhone }}</div>
<div class="text-gray">{{ row.email }}</div>
</template>
</el-table-column>
<el-table-column label="申请理由" prop="applicationReason" min-width="200" show-overflow-tooltip />
<el-table-column label="申请状态" width="120" align="center">
<template #default="{ row }">
<el-tag :type="getStatusTagType(row.status)" effect="plain" size="small">
{{ getStatusText(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="申请时间" prop="applicationDate" width="110" align="center" />
<el-table-column label="审核时间" width="130" align="center">
<template #default="{ row }">
<span v-if="row.collegeReviewTime">{{ row.collegeReviewTime }}</span>
<span v-else class="text-gray">-</span>
</template>
</el-table-column>
<el-table-column label="审核意见" min-width="150" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.collegeReviewComment">{{ row.collegeReviewComment }}</span>
<span v-else class="text-gray">-</span>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="120" align="center">
<template #default="{ row }">
<el-button type="primary" size="small" icon="View" @click="handleDetail(row)"> 详情 </el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 详情对话框 -->
<ApplicationDetail v-model:visible="detailDialog.visible" :application-id="detailDialog.applicationId" @refresh="getList" />
</div>
</template>
<script setup lang="ts" name="AllApplications">
import { ref, reactive, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import { Search, List, View } from '@element-plus/icons-vue';
import {
listCollegeApplications,
getApplicationOptions,
type WorkStudyApplicationVO,
type WorkStudyApplicationQuery,
type OptionsVO
} from '@/api/jcbt/workStudyApplicationCollegeReview';
import ApplicationDetail from './ApplicationDetail.vue';
// 响应式数据
const loading = ref(false);
const applicationList = ref<WorkStudyApplicationVO[]>([]);
const total = ref(0);
const statusOptions = ref<Array<{ label: string; value: string }>>([]);
const queryRef = ref();
// 查询参数
const queryParams = ref<WorkStudyApplicationQuery>({
pageNum: 1,
pageSize: 10,
positionName: '',
studentName: '',
studentNo: '',
status: '',
applicationDate: ''
});
// 详情对话框数据
const detailDialog = reactive({
visible: false,
applicationId: ''
});
// 获取所有申请列表
const getList = async () => {
loading.value = true;
try {
const { data } = await listCollegeApplications(queryParams.value);
applicationList.value = data.rows || [];
total.value = data.total || 0;
} catch (error) {
console.error('获取申请列表失败:', error);
ElMessage.error('获取数据失败');
} finally {
loading.value = false;
}
};
// 获取选项数据
const getOptions = async () => {
try {
const { data } = await getApplicationOptions();
statusOptions.value = data.statusOptions || [];
} catch (error) {
console.error('获取选项数据失败:', error);
}
};
// 搜索
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
// 重置搜索
const resetQuery = () => {
queryRef.value.resetFields();
queryParams.value = {
pageNum: 1,
pageSize: 10,
positionName: '',
studentName: '',
studentNo: '',
status: '',
applicationDate: ''
};
getList();
};
// 查看详情
const handleDetail = (row: WorkStudyApplicationVO) => {
detailDialog.visible = true;
detailDialog.applicationId = row.id as string;
};
// 获取状态标签类型
const getStatusTagType = (status: string) => {
const typeMap: Record<string, string> = {
'SUBMITTED': 'info',
'COLLEGE_APPROVED': 'success',
'COLLEGE_REJECTED': 'danger',
'FINAL_APPROVED': 'success',
'FINAL_REJECTED': 'danger',
'CANCELLED': 'info'
};
return typeMap[status] || 'info';
};
// 获取状态文本
const getStatusText = (status: string) => {
const textMap: Record<string, string> = {
'SUBMITTED': '已提交',
'COLLEGE_APPROVED': '学院已通过',
'COLLEGE_REJECTED': '学院已拒绝',
'FINAL_APPROVED': '最终通过',
'FINAL_REJECTED': '最终拒绝',
'CANCELLED': '已取消'
};
return textMap[status] || status;
};
// 暴露方法给父组件调用
defineExpose({
getList
});
// 初始化
onMounted(() => {
getOptions();
getList();
});
</script>
<style scoped lang="scss">
.all-applications {
.search-card {
margin-bottom: 16px;
border: none;
:deep(.el-card__body) {
padding: 20px;
}
}
.search-form {
.el-form-item {
margin-bottom: 0;
}
}
.table-card {
border: none;
:deep(.el-card__body) {
padding: 0;
}
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 20px 16px;
border-bottom: 1px solid #ebeef5;
.table-title {
display: flex;
align-items: center;
font-size: 16px;
font-weight: 600;
color: #303133;
.el-icon {
margin-right: 8px;
color: #409eff;
}
}
}
.el-table {
border-top: none;
}
.student-info {
.name {
font-weight: 500;
color: #303133;
margin-bottom: 2px;
}
.detail {
font-size: 12px;
color: #909399;
}
}
.text-gray {
color: #909399;
font-size: 12px;
}
}
</style>
@@ -1,391 +0,0 @@
<template>
<el-dialog v-model="dialogVisible" title="申请详情" width="800px" append-to-body :close-on-click-modal="false" @closed="handleClose">
<div v-loading="loading" class="application-detail">
<template v-if="applicationData">
<!-- 基本信息 -->
<div class="detail-section">
<div class="section-title">
<el-icon><User /></el-icon>
基本信息
</div>
<div class="section-content">
<el-row :gutter="20">
<el-col :span="12">
<div class="info-item">
<label>学生姓名:</label>
<span>{{ applicationData.studentName }}</span>
</div>
</el-col>
<el-col :span="12">
<div class="info-item">
<label>学号:</label>
<span>{{ applicationData.studentNo }}</span>
</div>
</el-col>
<el-col :span="12">
<div class="info-item">
<label>学院:</label>
<span>{{ applicationData.collegeName }}</span>
</div>
</el-col>
<el-col :span="12">
<div class="info-item">
<label>专业:</label>
<span>{{ applicationData.majorName }}</span>
</div>
</el-col>
<el-col :span="12">
<div class="info-item">
<label>年级:</label>
<span>{{ applicationData.grade }}</span>
</div>
</el-col>
<el-col :span="12">
<div class="info-item">
<label>班级:</label>
<span>{{ applicationData.className }}</span>
</div>
</el-col>
<el-col :span="12">
<div class="info-item">
<label>联系电话:</label>
<span>{{ applicationData.contactPhone }}</span>
</div>
</el-col>
<el-col :span="12">
<div class="info-item">
<label>邮箱:</label>
<span>{{ applicationData.email }}</span>
</div>
</el-col>
</el-row>
</div>
</div>
<!-- 岗位信息 -->
<div class="detail-section">
<div class="section-title">
<el-icon><Briefcase /></el-icon>
岗位信息
</div>
<div class="section-content">
<el-row :gutter="20">
<el-col :span="24">
<div class="info-item">
<label>岗位名称:</label>
<span>{{ applicationData.positionName }}</span>
</div>
</el-col>
</el-row>
</div>
</div>
<!-- 申请信息 -->
<div class="detail-section">
<div class="section-title">
<el-icon><Document /></el-icon>
申请信息
</div>
<div class="section-content">
<div class="info-item full-width">
<label>工作经验:</label>
<div class="text-content">{{ applicationData.workExperience || '无' }}</div>
</div>
<div class="info-item full-width">
<label>申请理由:</label>
<div class="text-content">{{ applicationData.applicationReason || '无' }}</div>
</div>
<el-row :gutter="20">
<el-col :span="12">
<div class="info-item">
<label>申请状态:</label>
<el-tag :type="getStatusTagType(applicationData.status)" effect="plain">
{{ getStatusText(applicationData.status) }}
</el-tag>
</div>
</el-col>
<el-col :span="12">
<div class="info-item">
<label>申请时间:</label>
<span>{{ applicationData.applicationDate }}</span>
</div>
</el-col>
</el-row>
</div>
</div>
<!-- 审核记录 -->
<div class="detail-section">
<div class="section-title">
<el-icon><Clock /></el-icon>
审核记录
</div>
<div class="section-content">
<!-- 学院审核记录 -->
<div v-if="applicationData.collegeReviewTime" class="review-record">
<div class="review-header">
<div class="review-type">学院审核</div>
<div class="review-time">{{ applicationData.collegeReviewTime }}</div>
</div>
<div class="review-content">
<div class="reviewer">审核人:{{ applicationData.collegeReviewByName || '未知' }}</div>
<div class="review-comment">
<label>审核意见:</label>
<span>{{ applicationData.collegeReviewComment || '无' }}</span>
</div>
</div>
</div>
<!-- 最终审核记录 -->
<div v-if="applicationData.finalReviewTime" class="review-record">
<div class="review-header">
<div class="review-type">最终审核</div>
<div class="review-time">{{ applicationData.finalReviewTime }}</div>
</div>
<div class="review-content">
<div class="reviewer">审核人:{{ applicationData.finalReviewByName || '未知' }}</div>
<div class="review-comment">
<label>审核意见:</label>
<span>{{ applicationData.finalReviewComment || '无' }}</span>
</div>
</div>
</div>
<!-- 无审核记录 -->
<div v-if="!applicationData.collegeReviewTime && !applicationData.finalReviewTime" class="no-review">暂无审核记录</div>
</div>
</div>
</template>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">关闭</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts" name="ApplicationDetail">
import { ref, computed, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { User, Document, Clock, Briefcase } from '@element-plus/icons-vue';
import { getCollegeApplication, type WorkStudyApplicationVO } from '@/api/jcbt/workStudyApplicationCollegeReview';
// 定义属性
interface Props {
visible: boolean;
applicationId: string | number;
}
const props = defineProps<Props>();
// 定义事件
const emit = defineEmits<{
'update:visible': [value: boolean];
'refresh': [];
}>();
// 响应式数据
const loading = ref(false);
const applicationData = ref<WorkStudyApplicationVO>();
// 计算属性
const dialogVisible = computed({
get: () => props.visible,
set: (value) => emit('update:visible', value)
});
// 获取申请详情
const getApplicationDetail = async () => {
if (!props.applicationId) return;
loading.value = true;
try {
const { data } = await getCollegeApplication(props.applicationId);
applicationData.value = data;
} catch (error) {
console.error('获取申请详情失败:', error);
ElMessage.error('获取详情失败');
} finally {
loading.value = false;
}
};
// 获取状态标签类型
const getStatusTagType = (status: string) => {
const typeMap: Record<string, string> = {
'SUBMITTED': 'info',
'COLLEGE_APPROVED': 'success',
'COLLEGE_REJECTED': 'danger',
'FINAL_APPROVED': 'success',
'FINAL_REJECTED': 'danger',
'CANCELLED': 'info'
};
return typeMap[status] || 'info';
};
// 获取状态文本
const getStatusText = (status: string) => {
const textMap: Record<string, string> = {
'SUBMITTED': '已提交',
'COLLEGE_APPROVED': '学院已通过',
'COLLEGE_REJECTED': '学院已拒绝',
'FINAL_APPROVED': '最终通过',
'FINAL_REJECTED': '最终拒绝',
'CANCELLED': '已取消'
};
return textMap[status] || status;
};
// 关闭对话框
const handleClose = () => {
dialogVisible.value = false;
applicationData.value = undefined;
};
// 监听对话框显示状态
watch(
() => props.visible,
(visible) => {
if (visible && props.applicationId) {
getApplicationDetail();
}
}
);
</script>
<style scoped lang="scss">
.application-detail {
max-height: 600px;
overflow-y: auto;
}
.detail-section {
margin-bottom: 24px;
&:last-child {
margin-bottom: 0;
}
.section-title {
display: flex;
align-items: center;
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid #e4e7ed;
.el-icon {
margin-right: 8px;
color: #409eff;
}
}
.section-content {
.info-item {
display: flex;
align-items: flex-start;
margin-bottom: 12px;
&.full-width {
flex-direction: column;
}
label {
min-width: 80px;
color: #606266;
font-weight: 500;
margin-right: 12px;
flex-shrink: 0;
}
span {
color: #303133;
}
.text-content {
margin-top: 4px;
line-height: 1.6;
color: #303133;
padding: 8px 12px;
background: #f5f7fa;
border-radius: 4px;
border-left: 3px solid #409eff;
}
}
}
}
.review-record {
margin-bottom: 16px;
padding: 16px;
background: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #409eff;
&:last-child {
margin-bottom: 0;
}
.review-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
.review-type {
font-weight: 600;
color: #303133;
font-size: 14px;
}
.review-time {
color: #909399;
font-size: 12px;
}
}
.review-content {
.reviewer {
color: #606266;
font-size: 12px;
margin-bottom: 8px;
}
.review-comment {
display: flex;
align-items: flex-start;
label {
min-width: 70px;
color: #606266;
font-size: 13px;
margin-right: 8px;
flex-shrink: 0;
}
span {
color: #303133;
font-size: 13px;
line-height: 1.6;
}
}
}
}
.no-review {
text-align: center;
color: #c0c4cc;
font-size: 14px;
padding: 20px;
font-style: italic;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
}
</style>
@@ -1,252 +0,0 @@
<template>
<el-card class="debug-panel" shadow="never">
<template #header>
<div class="debug-header">
<el-icon><Tools /></el-icon>
<span>数据一致性调试面板</span>
<el-button type="primary" size="small" @click="runDebug" :loading="debugging">
{{ debugging ? '检查中...' : '开始检查' }}
</el-button>
</div>
</template>
<div v-if="debugResult" class="debug-content">
<el-row :gutter="20">
<!-- 统计API结果 -->
<el-col :span="12">
<div class="debug-section">
<h4>📊 统计API结果</h4>
<el-descriptions :column="1" size="small" border>
<el-descriptions-item label="待审核数量">
<el-tag :type="debugResult.statistics.pendingCount > 0 ? 'warning' : 'info'">
{{ debugResult.statistics.pendingCount }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="已通过数量">
<el-tag type="success">{{ debugResult.statistics.approvedCount }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="已拒绝数量">
<el-tag type="danger">{{ debugResult.statistics.rejectedCount }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="最终通过数量">
<el-tag type="primary">{{ debugResult.statistics.finalApprovedCount }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="总数量">
<el-tag>{{ debugResult.statistics.totalCount }}</el-tag>
</el-descriptions-item>
</el-descriptions>
</div>
</el-col>
<!-- 列表API结果 -->
<el-col :span="12">
<div class="debug-section">
<h4>📋 列表API结果</h4>
<el-descriptions :column="1" size="small" border>
<el-descriptions-item label="待审核列表总数">
<el-tag :type="debugResult.pendingList.total > 0 ? 'warning' : 'info'">
{{ debugResult.pendingList.total }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="所有申请总数">
<el-tag>{{ debugResult.allList.total }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="当前页记录数">
<el-tag type="info">{{ debugResult.pendingList.rows?.length || 0 }}</el-tag>
</el-descriptions-item>
</el-descriptions>
</div>
</el-col>
</el-row>
<!-- 一致性检查结果 -->
<div class="consistency-check">
<h4>🔍 一致性检查结果</h4>
<el-alert
:type="debugResult.isConsistent ? 'success' : 'error'"
:title="debugResult.isConsistent ? '数据一致' : '数据不一致'"
:description="debugResult.message"
show-icon
:closable="false"
/>
<div v-if="!debugResult.isConsistent" class="inconsistency-details">
<el-tag type="danger">
统计API待审核: {{ debugResult.statistics.pendingCount }}
</el-tag>
<span class="vs">VS</span>
<el-tag type="warning">
列表API待审核: {{ debugResult.pendingList.total }}
</el-tag>
</div>
</div>
<!-- 详细数据 -->
<el-collapse class="debug-details">
<el-collapse-item title="📊 统计API原始数据" name="stats">
<pre>{{ JSON.stringify(debugResult.statistics, null, 2) }}</pre>
</el-collapse-item>
<el-collapse-item title="📋 待审核列表API原始数据" name="pending">
<pre>{{ JSON.stringify(debugResult.pendingList, null, 2) }}</pre>
</el-collapse-item>
<el-collapse-item title="📄 所有申请列表API原始数据" name="all">
<pre>{{ JSON.stringify(debugResult.allList, null, 2) }}</pre>
</el-collapse-item>
</el-collapse>
</div>
<div v-else class="no-debug">
<el-empty description="点击"开始检查"按钮来诊断数据一致性问题" />
</div>
</el-card>
</template>
<script setup lang="ts" name="DebugPanel">
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
import { Tools } from '@element-plus/icons-vue';
import {
getCollegeStatistics,
listPendingApplications,
listCollegeApplications,
type StatisticsVO,
type WorkStudyApplicationVO
} from '@/api/jcbt/workStudyApplicationCollegeReview';
// 响应式数据
const debugging = ref(false);
const debugResult = ref<any>(null);
// 运行调试检查
const runDebug = async () => {
debugging.value = true;
debugResult.value = null;
try {
console.log('🔍 开始数据一致性调试...');
// 1. 获取统计数据
const statsResponse = await getCollegeStatistics();
const statistics = statsResponse.data;
// 2. 获取待审核列表数据
const pendingResponse = await listPendingApplications({
pageNum: 1,
pageSize: 10
});
const pendingList = pendingResponse.data;
// 3. 获取所有申请列表数据
const allResponse = await listCollegeApplications({
pageNum: 1,
pageSize: 1
});
const allList = allResponse.data;
// 4. 检查一致性
const isConsistent = statistics.pendingCount === pendingList.total;
const message = isConsistent
? '统计数据与列表数据一致,没有发现问题。'
: `数据不一致:统计API显示${statistics.pendingCount}条待审核记录,但列表API显示${pendingList.total}条记录。`;
debugResult.value = {
statistics,
pendingList,
allList,
isConsistent,
message,
timestamp: new Date().toLocaleString()
};
console.log('✅ 调试检查完成:', debugResult.value);
if (!isConsistent) {
ElMessage.warning('发现数据不一致问题,请查看详细信息');
} else {
ElMessage.success('数据一致性检查通过');
}
} catch (error) {
console.error('❌ 调试检查失败:', error);
ElMessage.error('调试检查失败,请查看控制台日志');
} finally {
debugging.value = false;
}
};
</script>
<style scoped lang="scss">
.debug-panel {
margin-bottom: 20px;
border: 2px dashed #e6a23c;
.debug-header {
display: flex;
align-items: center;
justify-content: space-between;
.el-icon {
margin-right: 8px;
color: #e6a23c;
}
span {
flex: 1;
font-weight: 600;
color: #606266;
}
}
.debug-content {
.debug-section {
margin-bottom: 20px;
h4 {
margin: 0 0 12px 0;
color: #303133;
font-size: 14px;
}
}
.consistency-check {
margin: 20px 0;
h4 {
margin: 0 0 12px 0;
color: #303133;
font-size: 14px;
}
.inconsistency-details {
margin-top: 12px;
display: flex;
align-items: center;
gap: 12px;
.vs {
color: #f56c6c;
font-weight: 600;
}
}
}
.debug-details {
margin-top: 20px;
pre {
background: #f5f7fa;
padding: 12px;
border-radius: 4px;
font-size: 12px;
max-height: 300px;
overflow-y: auto;
}
}
}
.no-debug {
text-align: center;
padding: 40px 0;
}
}
</style>
@@ -1,401 +0,0 @@
<template>
<div class="pending-applications">
<!-- 搜索表单 -->
<el-card class="search-card" shadow="never">
<el-form :model="queryParams" ref="queryRef" :inline="true" class="search-form">
<el-form-item label="岗位名称" prop="positionName">
<el-input v-model="queryParams.positionName" placeholder="请输入岗位名称" clearable style="width: 200px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="学生姓名" prop="studentName">
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable style="width: 200px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="学号" prop="studentNo">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable style="width: 200px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="申请日期" prop="applicationDate">
<el-date-picker
v-model="queryParams.applicationDate"
type="date"
placeholder="选择申请日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
style="width: 200px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 操作按钮和数据表格 -->
<el-card class="table-card" shadow="never">
<!-- 操作按钮 -->
<div class="table-header">
<div class="table-title">
<el-icon><Clock /></el-icon>
待审核申请列表
</div>
<div class="table-actions">
<el-button type="success" plain icon="Check" :disabled="multiple" @click="handleBatchReview('APPROVED')"> 批量通过 </el-button>
<el-button type="danger" plain icon="Close" :disabled="multiple" @click="handleBatchReview('REJECTED')"> 批量拒绝 </el-button>
</div>
</div>
<!-- 数据表格 -->
<el-table v-loading="loading" :data="applicationList" @selection-change="handleSelectionChange" row-key="id" stripe>
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="岗位名称" prop="positionName" min-width="120" show-overflow-tooltip />
<el-table-column label="学生信息" min-width="160">
<template #default="{ row }">
<div class="student-info">
<div class="name">{{ row.studentName }}</div>
<div class="detail">{{ row.studentNo }} | {{ row.className }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="专业年级" min-width="140">
<template #default="{ row }">
<div>{{ row.majorName }}</div>
<div class="text-gray">{{ row.grade }}</div>
</template>
</el-table-column>
<el-table-column label="联系方式" min-width="120">
<template #default="{ row }">
<div>{{ row.contactPhone }}</div>
<div class="text-gray">{{ row.email }}</div>
</template>
</el-table-column>
<el-table-column label="申请理由" prop="applicationReason" min-width="200" show-overflow-tooltip />
<el-table-column label="申请时间" prop="applicationDate" width="110" align="center" />
<el-table-column label="操作" fixed="right" width="200" align="center">
<template #default="{ row }">
<el-button type="primary" size="small" icon="View" @click="handleDetail(row)"> 详情 </el-button>
<el-button type="success" size="small" icon="Check" @click="handleReview(row, 'APPROVED')"> 通过 </el-button>
<el-button type="danger" size="small" icon="Close" @click="handleReview(row, 'REJECTED')"> 拒绝 </el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 审核对话框 -->
<el-dialog v-model="reviewDialog.visible" :title="reviewDialog.title" width="600px" append-to-body :close-on-click-modal="false">
<el-form ref="reviewFormRef" :model="reviewDialog.form" :rules="reviewRules" label-width="100px">
<el-form-item label="审核结果" prop="reviewResult">
<el-radio-group v-model="reviewDialog.form.reviewResult">
<el-radio value="APPROVED">
<el-icon color="#67c23a"><Check /></el-icon>
通过
</el-radio>
<el-radio value="REJECTED">
<el-icon color="#f56c6c"><Close /></el-icon>
拒绝
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="审核意见" prop="reviewComment">
<el-input v-model="reviewDialog.form.reviewComment" type="textarea" :rows="4" placeholder="请输入审核意见(必填)" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="reviewDialog.visible = false">取消</el-button>
<el-button type="primary" :loading="reviewDialog.submitting" @click="submitReview"> 确认审核 </el-button>
</div>
</template>
</el-dialog>
<!-- 详情对话框 -->
<ApplicationDetail v-model:visible="detailDialog.visible" :application-id="detailDialog.applicationId" @refresh="getList" />
</div>
</template>
<script setup lang="ts" name="PendingApplications">
import { ref, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Search, Clock, Check, Close, View } from '@element-plus/icons-vue';
import {
listPendingApplications,
reviewApplication,
batchReviewApplications,
type WorkStudyApplicationVO,
type WorkStudyApplicationQuery,
type ReviewForm
} from '@/api/jcbt/workStudyApplicationCollegeReview';
import ApplicationDetail from './ApplicationDetail.vue';
// 定义事件
const emit = defineEmits<{
'refresh-statistics': [];
}>();
// 响应式数据
const loading = ref(false);
const applicationList = ref<WorkStudyApplicationVO[]>([]);
const total = ref(0);
const ids = ref<Array<string | number>>([]);
const multiple = ref(true);
const queryRef = ref();
const reviewFormRef = ref();
// 查询参数
const queryParams = ref<WorkStudyApplicationQuery>({
pageNum: 1,
pageSize: 10,
positionName: '',
studentName: '',
studentNo: '',
applicationDate: ''
});
// 审核对话框数据
const reviewDialog = reactive({
visible: false,
title: '',
type: 'single', // single | batch
submitting: false,
form: {
applicationId: '',
reviewResult: '',
reviewComment: ''
}
});
// 详情对话框数据
const detailDialog = reactive({
visible: false,
applicationId: ''
});
// 审核表单验证规则
const reviewRules = {
reviewResult: [{ required: true, message: '请选择审核结果', trigger: 'change' }],
reviewComment: [{ required: true, message: '请输入审核意见', trigger: 'blur' }]
};
// 获取待审核申请列表
const getList = async () => {
loading.value = true;
try {
console.log('🔍 开始获取待审核申请列表...');
console.log('📝 查询参数:', queryParams.value);
const { data } = await listPendingApplications(queryParams.value);
console.log('📋 列表API返回数据:', data);
applicationList.value = data.rows || [];
total.value = data.total || 0;
console.log('✅ 待审核申请列表获取成功 - 总数:', total.value, '当前页数据:', applicationList.value.length);
// 如果列表为空但有查询条件,提示用户
if (total.value === 0 && applicationList.value.length === 0) {
const hasSearchConditions = queryParams.value.positionName ||
queryParams.value.studentName ||
queryParams.value.studentNo ||
queryParams.value.applicationDate;
if (hasSearchConditions) {
console.log('📝 当前有搜索条件,列表为空可能是正常的');
} else {
console.log('📝 无搜索条件但列表为空,可能确实没有待审核数据');
}
}
} catch (error) {
console.error('❌ 获取待审核申请列表失败:', error);
ElMessage.error('获取数据失败');
} finally {
loading.value = false;
}
};
// 搜索
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
// 重置搜索
const resetQuery = () => {
queryRef.value.resetFields();
queryParams.value = {
pageNum: 1,
pageSize: 10,
positionName: '',
studentName: '',
studentNo: '',
applicationDate: ''
};
getList();
};
// 多选框选中数据
const handleSelectionChange = (selection: WorkStudyApplicationVO[]) => {
ids.value = selection.map((item) => item.id!);
multiple.value = !selection.length;
};
// 查看详情
const handleDetail = (row: WorkStudyApplicationVO) => {
detailDialog.visible = true;
detailDialog.applicationId = row.id as string;
};
// 单个审核
const handleReview = (row: WorkStudyApplicationVO, result: string) => {
reviewDialog.visible = true;
reviewDialog.title = `审核申请 - ${row.studentName}`;
reviewDialog.type = 'single';
reviewDialog.form = {
applicationId: row.id as string,
reviewResult: result,
reviewComment: ''
};
};
// 批量审核
const handleBatchReview = (result: string) => {
if (!ids.value.length) {
ElMessage.warning('请选择要审核的申请');
return;
}
reviewDialog.visible = true;
reviewDialog.title = `批量${result === 'APPROVED' ? '通过' : '拒绝'}申请`;
reviewDialog.type = 'batch';
reviewDialog.form = {
applicationId: '',
reviewResult: result,
reviewComment: ''
};
};
// 提交审核
const submitReview = async () => {
if (!reviewFormRef.value) return;
const valid = await reviewFormRef.value.validate();
if (!valid) return;
try {
reviewDialog.submitting = true;
if (reviewDialog.type === 'single') {
await reviewApplication(reviewDialog.form);
} else {
await batchReviewApplications(ids.value, reviewDialog.form.reviewResult, reviewDialog.form.reviewComment);
}
ElMessage.success('审核成功');
reviewDialog.visible = false;
getList();
emit('refresh-statistics');
} catch (error) {
console.error('审核失败:', error);
ElMessage.error('审核失败');
} finally {
reviewDialog.submitting = false;
}
};
// 暴露方法给父组件调用
defineExpose({
getList
});
// 初始化
onMounted(() => {
getList();
});
</script>
<style scoped lang="scss">
.pending-applications {
.search-card {
margin-bottom: 16px;
border: none;
:deep(.el-card__body) {
padding: 20px;
}
}
.search-form {
.el-form-item {
margin-bottom: 0;
}
}
.table-card {
border: none;
:deep(.el-card__body) {
padding: 0;
}
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 20px 16px;
border-bottom: 1px solid #ebeef5;
.table-title {
display: flex;
align-items: center;
font-size: 16px;
font-weight: 600;
color: #303133;
.el-icon {
margin-right: 8px;
color: #e6a23c;
}
}
.table-actions {
.el-button + .el-button {
margin-left: 8px;
}
}
}
.el-table {
border-top: none;
}
.student-info {
.name {
font-weight: 500;
color: #303133;
margin-bottom: 2px;
}
.detail {
font-size: 12px;
color: #909399;
}
}
.text-gray {
color: #909399;
font-size: 12px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
.el-button + .el-button {
margin-left: 12px;
}
}
}
</style>
@@ -1,360 +0,0 @@
<template>
<div class="app-container">
<!-- 页面标题 -->
<div class="page-header">
<h2 class="page-title">
<el-icon class="page-icon"><User /></el-icon>
学院审核
</h2>
<p class="page-description">管理本学院学生的勤工俭学申请审核</p>
</div>
<!-- 统计卡片 -->
<el-row :gutter="20" class="statistics-row">
<el-col :span="6">
<el-card class="statistics-card pending" shadow="hover">
<div class="card-content">
<div class="card-icon">
<el-icon><Clock /></el-icon>
</div>
<div class="card-info">
<div class="card-title">待审核</div>
<div class="card-value">{{ statistics.pendingCount || 0 }}</div>
<div class="card-desc">需要处理的申请</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="statistics-card approved" shadow="hover">
<div class="card-content">
<div class="card-icon">
<el-icon><Check /></el-icon>
</div>
<div class="card-info">
<div class="card-title">已通过</div>
<div class="card-value">{{ statistics.approvedCount || 0 }}</div>
<div class="card-desc">学院审核通过</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="statistics-card rejected" shadow="hover">
<div class="card-content">
<div class="card-icon">
<el-icon><Close /></el-icon>
</div>
<div class="card-info">
<div class="card-title">已拒绝</div>
<div class="card-value">{{ statistics.rejectedCount || 0 }}</div>
<div class="card-desc">学院审核拒绝</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="statistics-card final-approved" shadow="hover">
<div class="card-content">
<div class="card-icon">
<el-icon><Trophy /></el-icon>
</div>
<div class="card-info">
<div class="card-title">最终通过</div>
<div class="card-value">{{ statistics.finalApprovedCount || 0 }}</div>
<div class="card-desc">最终审核通过</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 内容标签页 -->
<el-card class="main-content" shadow="never">
<el-tabs v-model="activeTab" @tab-change="handleTabChange" class="review-tabs">
<el-tab-pane name="pending">
<template #label>
<span class="tab-label">
<el-icon><Clock /></el-icon>
待审核申请
<el-badge v-if="statistics.pendingCount" :value="statistics.pendingCount" class="tab-badge" />
</span>
</template>
<PendingApplications ref="pendingRef" @refresh-statistics="getStatistics" />
</el-tab-pane>
<el-tab-pane name="all">
<template #label>
<span class="tab-label">
<el-icon><List /></el-icon>
所有申请
</span>
</template>
<AllApplications ref="allRef" />
</el-tab-pane>
<el-tab-pane name="debug" v-if="showDebugPanel">
<template #label>
<span class="tab-label">
<el-icon><Tools /></el-icon>
调试面板
</span>
</template>
<DebugPanel />
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</template>
<script setup lang="ts" name="CollegeReview">
import { ref, onMounted } from 'vue';
import { Clock, Check, Close, Trophy, User, List, Tools } from '@element-plus/icons-vue';
import { getCollegeStatistics, getCollegeStatisticsByList, listCollegeApplications, type StatisticsVO } from '@/api/jcbt/workStudyApplicationCollegeReview';
import PendingApplications from './components/PendingApplications.vue';
import AllApplications from './components/AllApplications.vue';
import DebugPanel from './components/DebugPanel.vue';
// 响应式数据
const activeTab = ref('pending');
const statistics = ref<StatisticsVO>({});
const pendingRef = ref();
const allRef = ref();
const showDebugPanel = ref(false); // Added ref for debug panel visibility
// 获取统计数据
const getStatistics = async () => {
try {
console.log('🔍 开始获取学院审核统计数据...');
// 方案1:使用原始统计API
const { data } = await getCollegeStatistics();
console.log('📊 统计API返回数据:', data);
// 方案2:通过列表API验证待审核数量
try {
const listResult = await listCollegeApplications({
pageNum: 1,
pageSize: 1,
status: 'SUBMITTED'
});
console.log('📋 列表API验证 - 待审核数量:', listResult.data.total);
// 如果统计API的待审核数量与列表API不一致,使用列表API的数据
if (data.pendingCount !== listResult.data.total) {
console.warn('⚠️ 统计数据不一致!统计API:', data.pendingCount, '列表API:', listResult.data.total);
data.pendingCount = listResult.data.total;
console.log('✅ 已使用列表API数据修正待审核数量:', data.pendingCount);
}
} catch (listError) {
console.warn('📋 列表API验证失败:', listError);
}
statistics.value = data;
console.log('✅ 最终统计数据:', statistics.value);
} catch (error) {
console.error('❌ 获取统计数据失败:', error);
statistics.value = {
pendingCount: 0,
approvedCount: 0,
rejectedCount: 0,
finalApprovedCount: 0,
totalCount: 0
};
}
};
// 标签页切换
const handleTabChange = (tabName: string) => {
activeTab.value = tabName;
if (tabName === 'pending' && pendingRef.value) {
pendingRef.value.getList();
} else if (tabName === 'all' && allRef.value) {
allRef.value.getList();
}
};
// 页面初始化
onMounted(() => {
getStatistics();
});
</script>
<style scoped lang="scss">
.app-container {
padding: 24px;
background: #f5f7fa;
min-height: calc(100vh - 84px);
}
.page-header {
margin-bottom: 24px;
.page-title {
display: flex;
align-items: center;
margin: 0 0 8px 0;
font-size: 24px;
font-weight: 600;
color: #303133;
.page-icon {
margin-right: 12px;
color: #409eff;
}
}
.page-description {
margin: 0;
color: #909399;
font-size: 14px;
}
}
.statistics-row {
margin-bottom: 24px;
}
.statistics-card {
border: none;
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
}
.card-content {
display: flex;
align-items: center;
padding: 20px 0;
}
.card-icon {
margin-right: 16px;
width: 56px;
height: 56px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
}
.card-info {
flex: 1;
}
.card-title {
font-size: 14px;
color: #909399;
margin-bottom: 4px;
}
.card-value {
font-size: 28px;
font-weight: 600;
margin-bottom: 2px;
line-height: 1;
}
.card-desc {
font-size: 12px;
color: #c0c4cc;
}
&.pending {
.card-icon {
background: linear-gradient(135deg, #ffeaa7 0%, #fab1a0 100%);
color: #d63031;
}
.card-value {
color: #e17055;
}
}
&.approved {
.card-icon {
background: linear-gradient(135deg, #55efc4 0%, #00b894 100%);
color: #00695c;
}
.card-value {
color: #00b894;
}
}
&.rejected {
.card-icon {
background: linear-gradient(135deg, #fd79a8 0%, #e84393 100%);
color: #ad1457;
}
.card-value {
color: #e84393;
}
}
&.final-approved {
.card-icon {
background: linear-gradient(135deg, #a29bfe 0%, #6c5ce7 100%);
color: #4527a0;
}
.card-value {
color: #6c5ce7;
}
}
}
.main-content {
border: none;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
:deep(.el-card__body) {
padding: 0;
}
}
.review-tabs {
:deep(.el-tabs__header) {
padding: 20px 20px 0;
margin: 0;
background: #fff;
}
:deep(.el-tabs__nav-wrap::after) {
background: #e4e7ed;
}
:deep(.el-tabs__item) {
padding: 0 20px;
font-size: 16px;
color: #606266;
&.is-active {
color: #409eff;
font-weight: 600;
}
}
:deep(.el-tabs__content) {
padding: 20px;
}
.tab-label {
display: flex;
align-items: center;
.el-icon {
margin-right: 6px;
}
.tab-badge {
margin-left: 8px;
:deep(.el-badge__content) {
font-size: 11px;
height: 16px;
line-height: 16px;
padding: 0 5px;
}
}
}
}
</style>
@@ -131,12 +131,12 @@ watch(dialogVisible, (newVal) => {
// 获取详情 // 获取详情
const getDetail = async () => { const getDetail = async () => {
if (!props.applicationId) return; if (!props.applicationId) return;
loading.value = true; loading.value = true;
try { try {
const response = await getCollegeApplication(props.applicationId); const response = await getCollegeApplication(props.applicationId);
console.log('详情API响应:', response); console.log('详情API响应:', response);
// 处理不同的响应格式 // 处理不同的响应格式
if (response.data) { if (response.data) {
if (response.data.data) { if (response.data.data) {
@@ -186,13 +186,13 @@ const getStatusType = (status: string) => {
font-weight: bold; font-weight: bold;
color: #303133; color: #303133;
} }
.content-text { .content-text {
line-height: 1.6; line-height: 1.6;
white-space: pre-wrap; white-space: pre-wrap;
word-break: break-all; word-break: break-all;
} }
.mb-20 { .mb-20 {
margin-bottom: 20px; margin-bottom: 20px;
} }
@@ -170,41 +170,52 @@ const reviewFormRef = ref();
const getList = async () => { const getList = async () => {
loading.value = true; loading.value = true;
try { try {
console.log('🔍 [调试] 开始获取待审核申请列表');
console.log('🔍 [调试] 查询参数:', queryParams);
const response = await listPendingApplications(queryParams); const response = await listPendingApplications(queryParams);
console.log('API响应:', response); console.log('🔍 [调试] API原始响应:', response);
// 处理不同的响应格式 // 根据实际响应格式处理数据
if (response.data) { // 从调试输出看,response直接包含{total: 1, rows: Array(1), code: 200, msg: '查询成功'}
// 如果是标准的TableDataInfo格式 if (response && typeof response === 'object') {
if (response.data.rows !== undefined) { console.log('🔍 [调试] 响应对象键名:', Object.keys(response));
applicationList.value = response.data.rows || [];
total.value = response.data.total || 0; if (response.rows !== undefined && response.total !== undefined) {
} console.log('🔍 [调试] ✅ 识别为TableDataInfo格式');
// 如果是包装在R<TableDataInfo>格式中 console.log('🔍 [调试] rows数据:', response.rows);
else if (response.data.data && response.data.data.rows !== undefined) { console.log('🔍 [调试] total数量:', response.total);
applicationList.value = response.data.data.rows || []; applicationList.value = response.rows || [];
total.value = response.data.data.total || 0; total.value = response.total || 0;
} } else {
// 如果直接是数组数据 console.warn('🔍 [调试] ❌ 响应格式不匹配,缺少rows或total字段');
else if (Array.isArray(response.data)) {
applicationList.value = response.data;
total.value = response.data.length;
}
// 其他情况
else {
console.warn('未识别的响应格式:', response.data);
applicationList.value = []; applicationList.value = [];
total.value = 0; total.value = 0;
} }
} else { } else {
console.warn('🔍 [调试] ❌ 响应不是对象类型');
applicationList.value = []; applicationList.value = [];
total.value = 0; total.value = 0;
} }
} catch (error) {
console.error('获取待审核申请列表失败:', error); console.log('🔍 [调试] 最终设置的applicationList长度:', applicationList.value.length);
if (error.message.includes('404')) { console.log('🔍 [调试] 最终设置的total:', total.value);
if (applicationList.value.length > 0) {
console.log('🔍 [调试] 第一条数据示例:', applicationList.value[0]);
} else {
console.log('🔍 [调试] ⚠️ 列表为空!');
}
} catch (error: any) {
console.error('🔍 [调试] 获取待审核申请列表失败:', error);
if (error?.response?.data) {
console.error('🔍 [调试] 错误详情:', error.response.data);
}
if (error?.message?.includes('404')) {
ElMessage.error('无法获取待审核申请列表,请检查您是否有学院审核权限'); ElMessage.error('无法获取待审核申请列表,请检查您是否有学院审核权限');
} else if (error.message.includes('403')) { } else if (error?.message?.includes('403')) {
ElMessage.error('权限不足,无法访问学院审核功能'); ElMessage.error('权限不足,无法访问学院审核功能');
} else { } else {
ElMessage.error('获取待审核申请列表失败,请稍后重试'); ElMessage.error('获取待审核申请列表失败,请稍后重试');