This commit is contained in:
zlwu committed 2025-09-07 20:32:55 +08:00
commit 3e3a503fff
86 files changed
+2101 -1849

No files matched your search

@@ -1,6 +1,7 @@
package org.dromara.cw.mapper;
import org.dromara.cw.domain.CwProjectAuditor;
import org.dromara.cw.domain.UserWithDeptInfo;
import org.dromara.cw.domain.vo.CwProjectAuditorVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.apache.ibatis.annotations.Param;
@@ -25,4 +26,9 @@ public interface CwProjectAuditorMapper extends BaseMapperPlus<CwProjectAuditor,
*/
List<CwProjectAuditorVo> selectAuditorStatistics();
/**
* 查询完整的用户信息(包含部门信息)用于批量添加
*/
List<UserWithDeptInfo> selectUsersWithDeptInfo(@Param("userIds") List<Long> userIds);
}
@@ -14,6 +14,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.dromara.cw.domain.bo.CwProjectAuditorBo;
import org.dromara.cw.domain.vo.CwProjectAuditorVo;
import org.dromara.cw.domain.CwProjectAuditor;
import org.dromara.cw.domain.UserWithDeptInfo;
import org.dromara.cw.mapper.CwProjectAuditorMapper;
import org.dromara.cw.service.ICwProjectAuditorService;
import org.dromara.system.service.ISysUserService;
@@ -150,8 +151,8 @@ public class CwProjectAuditorServiceImpl implements ICwProjectAuditorService {
return false;
}
// 1. 获取用户信息
List<SysUserVo> users = sysUserService.selectUserByIds(userIds, null);
// 1. 获取完整的用户信息(包含部门信息)
List<UserWithDeptInfo> users = baseMapper.selectUsersWithDeptInfo(userIds);
if (users.isEmpty()) {
return false;
}
@@ -163,7 +164,7 @@ public class CwProjectAuditorServiceImpl implements ICwProjectAuditorService {
// 3. 构建新增审核人列表
List<CwProjectAuditor> auditors = new ArrayList<>();
for (SysUserVo user : users) {
for (UserWithDeptInfo user : users) {
if (!existingUserIds.contains(user.getUserId())) {
CwProjectAuditor auditor = new CwProjectAuditor();
auditor.setUserId(user.getUserId());
@@ -111,4 +111,25 @@
and deleted = 0
</update>
<!-- 查询完整的用户信息(包含部门信息)用于批量添加 -->
<select id="selectUsersWithDeptInfo" resultType="org.dromara.cw.domain.UserWithDeptInfo">
select
u.user_id as userId,
u.user_name as userName,
u.nick_name as nickName,
u.dept_id as deptId,
d.dept_name as deptName,
u.phonenumber as phonenumber,
u.email as email,
u.status as status
from sys_user u
left join sys_dept d on u.dept_id = d.dept_id
where u.status = '0'
and u.user_id in
<foreach item="userId" collection="userIds" open="(" separator="," close=")">
#{userId}
</foreach>
order by u.dept_id, u.user_name
</select>
</mapper>
@@ -1,6 +1,12 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { AssessmentApprovalQuery, AssessmentApprovalVO, AssessmentApprovalDetailVO, AssessmentApprovalSubmitBO, EvaluationLevelStatsVO } from './types';
import {
AssessmentApprovalQuery,
AssessmentApprovalVO,
AssessmentApprovalDetailVO,
AssessmentApprovalSubmitBO,
EvaluationLevelStatsVO
} from './types';
/**
* 查询班主任考核审核列表
@@ -224,11 +224,12 @@ export interface AssessmentApprovalSpecialWorkSubmitVO {
* 评价等级统计数据
*/
export interface EvaluationLevelStatsVO {
totalTeachers: number; // 总班主任数量
totalTeachers: number; // 总班主任数量
scoreRankings: ScoreRankingVO[]; // 分数排名列表
evaluationLevelRules: { // 评价等级规则
aLevelMaxPercentage: number; // A等最大占比(如20%)
passScore: number; // 及格分数(如60分)
evaluationLevelRules: {
// 评价等级规则
aLevelMaxPercentage: number; // A等最大占比(如20%)
passScore: number; // 及格分数(如60分)
};
}
@@ -236,8 +237,8 @@ export interface EvaluationLevelStatsVO {
* 分数排名VO
*/
export interface ScoreRankingVO {
score: number; // 考核得分
ranking: number; // 排名(从1开始)
percentage: number; // 百分比排名(前X%)
score: number; // 考核得分
ranking: number; // 排名(从1开始)
percentage: number; // 百分比排名(前X%)
evaluatedUserId?: string | number; // 被评价用户ID(可选,用于标识当前用户)
}
+1
View File
@@ -61,6 +61,7 @@ export interface CwApprovalRecordVO {
*/
export interface CwCustomApprovalRequest {
applicationId: string | number;
applicationNo: string;
auditorIds: Array<string | number>;
processName: string;
submitterId: string | number;
+30
View File
@@ -63,6 +63,22 @@ export interface BatchAddRequest {
remark?: string;
}
/**
* 批量添加审核人详细请求对象(包含完整用户信息)
*/
export interface BatchAddDetailRequest {
users: Array<{
userId: string | number;
userName: string;
nickName: string;
deptId: string | number;
deptName: string;
phoneNumber: string; // 注意:这里使用驼峰命名,确保与数据库字段映射
email: string;
}>;
remark?: string;
}
/**
* 状态更新请求对象
*/
@@ -149,6 +165,20 @@ export function batchAddProjectAuditor(data: BatchAddRequest): AxiosPromise<void
});
}
// 注释掉:后端暂不支持带完整用户信息的批量添加接口
// 使用 batchAddProjectAuditor 接口,后端会自动查询用户完整信息
/**
* 批量添加审核人(带完整用户信息)- 已弃用
* 改用 batchAddProjectAuditor,后端会自动获取用户完整信息
*/
// export function batchAddProjectAuditorWithDetails(data: BatchAddDetailRequest): AxiosPromise<void> {
// return request({
// url: '/cw/projectAuditor/batchAddWithDetails',
// method: 'post',
// data: data
// });
// }
/**
* 获取可用审核人列表
*/
+1 -1
View File
@@ -59,4 +59,4 @@ export function getTeamsByActivityId(activityId: string | number) {
url: '/stgl/activityteaminfo/activity/' + activityId,
method: 'get'
});
}
}
+1 -1
View File
@@ -238,4 +238,4 @@ export interface StglActivityTeamInfoVo {
* 更新时间
*/
updateTime?: string;
}
}
+1 -1
View File
@@ -61,4 +61,4 @@ export function exportStglClass(query: StglClassQuery) {
method: 'get',
params: query
});
}
}
+1 -1
View File
@@ -299,4 +299,4 @@ export interface StglClassDetailVO {
* 年级
*/
grade: string;
}
}
@@ -1,5 +1,10 @@
import request from '@/utils/request';
import { XskhAssessmentApplicationApprovalQuery, XskhAssessmentApplicationApprovalBo, XskhAssessmentApplicationApprovalVo, AvailableScheduleOption } from './types';
import {
XskhAssessmentApplicationApprovalQuery,
XskhAssessmentApplicationApprovalBo,
XskhAssessmentApplicationApprovalVo,
AvailableScheduleOption
} from './types';
// 查询学生考核申请列表
export function listAssessmentApplication(query: XskhAssessmentApplicationApprovalQuery) {
@@ -91,4 +96,4 @@ export function getIndicatorsByScheduleId(scheduleId: string | number) {
url: `/assessment/application/indicators/${scheduleId}`,
method: 'get'
});
}
}
@@ -71,4 +71,4 @@ export function getAvailableAssessmentSchedules() {
url: '/assessment/approval/available',
method: 'get'
});
}
}
@@ -138,4 +138,4 @@ export interface RewardTypeOption {
typeCode: string;
/** 类型名称 */
typeName: string;
}
}
@@ -1,7 +1,12 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { TableDataInfo, R } from '@/api/types';
import { XskhAssessmentIndicatorTypeVo, XskhAssessmentIndicatorTypeBo, XskhAssessmentIndicatorTypeQuery, XskhAssessmentIndicatorTypeForm } from './types';
import {
XskhAssessmentIndicatorTypeVo,
XskhAssessmentIndicatorTypeBo,
XskhAssessmentIndicatorTypeQuery,
XskhAssessmentIndicatorTypeForm
} from './types';
/**
* 查询学生考核指标类型列表
@@ -75,4 +80,4 @@ export function getEnabledAssessmentIndicatorTypes(): AxiosPromise<R<XskhAssessm
url: '/assessment/indicatorType/enabled',
method: 'get'
});
}
}
@@ -86,4 +86,4 @@ export interface XskhAssessmentIndicatorTypeForm extends BaseEntity {
status?: string;
/** 排序号 */
sortOrder?: number;
}
}
@@ -75,4 +75,4 @@ export function getEnabledAssessmentPlans(): AxiosPromise<R<XskhAssessmentPlanVo
url: '/assessment/plan/enabled',
method: 'get'
});
}
}
+1 -1
View File
@@ -140,4 +140,4 @@ export interface AssessmentIndicatorOption {
indicatorDescription: string;
/** 最高分值 */
maxScore: number;
}
}
@@ -124,4 +124,4 @@ export function getEnabledAssessmentPlans(): AxiosPromise<R<any[]>> {
url: '/assessment/plan/enabled',
method: 'get'
});
}
}
+1 -1
View File
@@ -142,4 +142,4 @@ export interface AssessmentPlanOption {
planName: string;
/** 方案说明 */
planDescription: string;
}
}
+5 -5
View File
@@ -15,9 +15,9 @@
<el-row :gutter="2" align="middle">
<el-col :span="5">
<el-form-item label="姓名" :prop="`advisors.${index}.name`">
<TeacherSelector
:model-value="advisor.teacher"
placeholder="选择指导老师"
<TeacherSelector
:model-value="advisor.teacher"
placeholder="选择指导老师"
@update:model-value="(teacher) => handleTeacherChange(advisor, teacher)"
/>
</el-form-item>
@@ -119,7 +119,7 @@ const removeAdvisor = (index: number) => {
// 监听teacher选择变化,自动填充相关信息
const handleTeacherChange = (advisor: AdvisorVO, teacher: TeacherVO | null) => {
console.log('handleTeacherChange called with teacher:', teacher);
if (teacher) {
// 自动填充老师信息
advisor.name = teacher.nickName || '';
@@ -127,7 +127,7 @@ const handleTeacherChange = (advisor: AdvisorVO, teacher: TeacherVO | null) => {
advisor.dept = teacher.deptName || '';
advisor.email = teacher.email || '暂无邮箱';
advisor.userId = teacher.userId?.toString() || '';
console.log('Updated advisor:', {
name: advisor.name,
phone: advisor.phone,
+14 -5
View File
@@ -14,7 +14,12 @@
value-key="uniqueKey"
style="width: 100%"
>
<el-option v-for="option in options" :key="option.uniqueKey || `${option.relatedType}-${option.relatedClubId}`" :label="formatOptionLabel(option)" :value="option">
<el-option
v-for="option in options"
:key="option.uniqueKey || `${option.relatedType}-${option.relatedClubId}`"
:label="formatOptionLabel(option)"
:value="option"
>
<div class="option-content">
<span class="option-name">{{ option.relatedClubName }}</span>
<span class="option-type">{{ getTypeLabel(option.relatedType) }}</span>
@@ -98,9 +103,13 @@ const selectedOption = ref<ClubOption | null>(null);
// 监听 modelValue 变化
import { watch } from 'vue';
watch(() => props.modelValue, (newValue) => {
console.log('ClubSelector - modelValue changed:', newValue);
}, { immediate: true });
watch(
() => props.modelValue,
(newValue) => {
console.log('ClubSelector - modelValue changed:', newValue);
},
{ immediate: true }
);
// 格式化选项标签
const formatOptionLabel = (option: ClubOption): string => {
@@ -172,7 +181,7 @@ const loadOptions = async () => {
uniqueKey: `${item.relatedType}-${item.relatedClubId}`
}));
options.value = [...clubOptions.value, ...volunteerOptions.value];
console.log('ClubSelector - 加载选项数据完成:', {
clubCount: clubOptions.value.length,
volunteerCount: volunteerOptions.value.length,
@@ -267,11 +267,11 @@
<el-row>
<el-col :span="12">
<el-form-item label="自我总体评价">
<el-input
:value="monthlyScoreForm.selfOverallComment || '暂无自我总体评价'"
type="textarea"
:rows="4"
readonly
<el-input
:value="monthlyScoreForm.selfOverallComment || '暂无自我总体评价'"
type="textarea"
:rows="4"
readonly
placeholder="暂无自我总体评价内容"
/>
</el-form-item>
@@ -457,7 +457,7 @@ const loadApprovalDetail = async () => {
if (approvalDetail.value?.routineWorkIndicators && Array.isArray(approvalDetail.value.routineWorkIndicators)) {
routineWorkScores.value = approvalDetail.value.routineWorkIndicators.map((item) => {
const mappedItem = {
...item,
...item,
// 确保数值字段不为空
selfScore: Number(item.selfScore) || 0,
superiorScore: Number(item.superiorScore) || Number(item.selfScore) || 0, // 默认使用自评分数
@@ -517,7 +517,7 @@ const loadApprovalDetail = async () => {
if (approvalDetail.value?.specialWorkIndicators && Array.isArray(approvalDetail.value.specialWorkIndicators)) {
specialWorkScores.value = approvalDetail.value.specialWorkIndicators.map((item) => {
const mappedItem = {
...item,
...item,
// 自评分数字段处理 - 兼容不同字段名,确保数值类型
score: Number(item.score || item.selfScore) || 0,
selfScore: Number(item.selfScore || item.score) || 0,
@@ -557,18 +557,17 @@ const loadApprovalDetail = async () => {
approvalForm.auditComment = '';
console.log('=== 审批详情数据加载完成 ===');
// 验证数据完整性
if (routineWorkScores.value.length === 0) {
console.warn('⚠️ 警告:班主任常规工作数据为空,可能存在数据问题');
proxy?.$modal.msgWarning('班主任常规工作数据为空,请检查数据是否正确');
}
if (specialWorkScores.value.length === 0) {
console.warn('⚠️ 警告:特色工作数据为空,可能存在数据问题');
proxy?.$modal.msgWarning('特色工作数据为空,请检查数据是否正确');
}
} catch (error) {
console.error('加载审批详情失败:', error);
proxy?.$modal.msgError('加载审批详情失败');
@@ -241,15 +241,13 @@
</template>
<el-descriptions :column="3" border>
<el-descriptions-item label="评价等级">
<div style="display: flex; align-items: center; gap: 8px;">
<div style="display: flex; align-items: center; gap: 8px">
<dict-tag :options="getEvaluationLevelOptions()" :value="calculateEvaluationLevel()" />
<el-tooltip placement="top">
<template #content>
<div>
<div>{{ evaluationStats ? '基于占比规则的精准评级' : '基于分数阈值的简化评级' }}</div>
<div v-if="isDictDataMissing" style="color: #f56c6c; margin-top: 4px;">
⚠️ 字典数据缺失,使用默认数据
</div>
<div v-if="isDictDataMissing" style="color: #f56c6c; margin-top: 4px">⚠️ 字典数据缺失,使用默认数据</div>
</div>
</template>
<el-icon :color="evaluationStats ? '#67c23a' : '#e6a23c'" size="16">
@@ -259,9 +257,7 @@
</el-tooltip>
</div>
</el-descriptions-item>
<el-descriptions-item label="考核得分">
{{ calculateMonthlyScore().toFixed(2) }}分
</el-descriptions-item>
<el-descriptions-item label="考核得分"> {{ calculateMonthlyScore().toFixed(2) }}分 </el-descriptions-item>
<el-descriptions-item label="审批时间">
{{ parseTime(currentRow?.createTime, '{y}-{m}-{d} {h}:{i}:{s}') || '-' }}
</el-descriptions-item>
@@ -310,9 +306,10 @@ const getEvaluationLevelOptions = (): DictDataOption[] => {
console.log('✅ 使用数据库字典数据:', bmzr_evaluation_level.value);
return bmzr_evaluation_level.value;
}
// 字典数据未加载或为空时的处理
if (visible.value) { // 只在弹窗打开时输出警告
if (visible.value) {
// 只在弹窗打开时输出警告
console.warn('⚠️ 字典数据为空,使用备用数据,当前状态:', {
弹窗是否打开: visible.value,
字典对象存在: !!bmzr_evaluation_level.value,
@@ -321,7 +318,7 @@ const getEvaluationLevelOptions = (): DictDataOption[] => {
字典数据内容: bmzr_evaluation_level.value
});
}
return fallbackEvaluationLevelOptions.value;
};
@@ -363,10 +360,10 @@ const dialogTitle = computed(() => {
const isDictDataMissing = computed(() => {
// 如果还没有尝试加载字典数据,不显示警告
if (!visible.value) return false;
// 如果字典数据已经加载成功,不显示警告
if (isDictLoaded.value) return false;
// 只有在弹窗打开且字典数据为空时才显示警告
return !bmzr_evaluation_level.value || bmzr_evaluation_level.value.length === 0;
});
@@ -434,7 +431,7 @@ const calculateEvaluationLevel = () => {
console.log('💯 考核得分:', score);
console.log('📈 是否有统计数据:', !!evaluationStats.value);
console.log('👤 当前用户ID:', currentRow.value?.evaluatedUserId);
if (evaluationStats.value) {
console.log('📊 统计数据详情:', {
总教师数: evaluationStats.value.totalTeachers,
@@ -443,15 +440,17 @@ const calculateEvaluationLevel = () => {
及格分数: evaluationStats.value.evaluationLevelRules?.passScore
});
}
let level = '';
let calculationMethod = '';
// 如果有统计数据且用户ID存在,使用严格的占比规则
if (evaluationStats.value &&
evaluationStats.value.totalTeachers > 0 &&
evaluationStats.value.scoreRankings.length > 0 &&
currentRow.value?.evaluatedUserId) {
if (
evaluationStats.value &&
evaluationStats.value.totalTeachers > 0 &&
evaluationStats.value.scoreRankings.length > 0 &&
currentRow.value?.evaluatedUserId
) {
level = calculateEvaluationLevelByStats(score, currentRow.value.evaluatedUserId);
calculationMethod = '📈 严格占比规则评级';
} else {
@@ -464,17 +463,17 @@ const calculateEvaluationLevel = () => {
level = 'B'; // B等(合格):60-85分
}
calculationMethod = '🔢 简化分数评级';
// 输出使用简化评级的原因
const reasons = [];
if (!evaluationStats.value) reasons.push('统计数据为空');
if (evaluationStats.value?.totalTeachers === 0) reasons.push('总教师数为0');
if (evaluationStats.value?.scoreRankings?.length === 0) reasons.push('排名数据为空');
if (!currentRow.value?.evaluatedUserId) reasons.push('用户ID缺失');
console.warn('⚠️ 使用简化评级的原因:', reasons.join('、'));
}
console.log('🎯 计算方法:', calculationMethod);
console.log('📋 最终评价等级:', level);
console.log('=== 📊 评价等级计算调试结束 ===');
@@ -486,32 +485,30 @@ const calculateEvaluationLevelByStats = (score: number, evaluatedUserId: string
const stats = evaluationStats.value;
if (!stats) {
console.warn('统计数据不可用,使用简单评级');
return score < 60 ? 'C' : (score >= 85 ? 'A' : 'B');
return score < 60 ? 'C' : score >= 85 ? 'A' : 'B';
}
const { totalTeachers, scoreRankings, evaluationLevelRules } = stats;
const { aLevelMaxPercentage, passScore } = evaluationLevelRules;
// C等:不及格
if (score < passScore) {
return 'C';
}
// 查找当前用户的排名
const currentRanking = scoreRankings.find(item =>
item.evaluatedUserId === evaluatedUserId || item.score === score
);
const currentRanking = scoreRankings.find((item) => item.evaluatedUserId === evaluatedUserId || item.score === score);
if (!currentRanking) {
console.warn('找不到当前用户的排名信息,使用简单评级');
return score < 60 ? 'C' : (score >= 85 ? 'A' : 'B');
return score < 60 ? 'C' : score >= 85 ? 'A' : 'B';
}
// A等:及格且排名在前20%内
if (score >= passScore && currentRanking.percentage <= aLevelMaxPercentage) {
return 'A';
}
// B等:其余及格的
return 'B';
};
@@ -525,28 +522,28 @@ const open = async (row: AssessmentApprovalVO) => {
currentRow.value = row;
visible.value = true;
// 检查字典数据加载状态
console.log('🔍 打开弹窗时字典数据状态:', {
字典对象存在: !!bmzr_evaluation_level.value,
字典数据长度: bmzr_evaluation_level.value?.length || 0,
字典数据内容: bmzr_evaluation_level.value
});
// 等待字典数据加载完成(最多等待2秒)
let waitCount = 0;
while ((!bmzr_evaluation_level.value || bmzr_evaluation_level.value.length === 0) && waitCount < 20) {
await new Promise(resolve => setTimeout(resolve, 100));
await new Promise((resolve) => setTimeout(resolve, 100));
waitCount++;
}
if (bmzr_evaluation_level.value && bmzr_evaluation_level.value.length > 0) {
console.log('✅ 字典数据加载完成:', bmzr_evaluation_level.value);
isDictLoaded.value = true;
} else {
console.warn('⚠️ 字典数据加载超时,使用备用数据');
}
await loadViewDetail();
};
@@ -577,11 +574,15 @@ const loadViewDetail = async () => {
if (statsRes.status === 'fulfilled') {
evaluationStats.value = statsRes.value.data;
console.log('✅ 评价等级统计数据加载成功:', evaluationStats.value);
// 检查统计数据的完整性
if (!evaluationStats.value.totalTeachers || evaluationStats.value.scoreRankings.length === 0) {
console.warn('⚠️ 统计数据不完整,总教师数:', evaluationStats.value.totalTeachers,
'排名数据条数:', evaluationStats.value.scoreRankings.length);
console.warn(
'⚠️ 统计数据不完整,总教师数:',
evaluationStats.value.totalTeachers,
'排名数据条数:',
evaluationStats.value.scoreRankings.length
);
proxy?.$modal.msgWarning('统计数据不完整,可能影响等级评定准确性');
}
} else {
@@ -696,18 +697,17 @@ const loadViewDetail = async () => {
approvalData.auditComment = '';
console.log('=== 查看详情数据加载完成 ===');
// 验证数据完整性
if (routineWorkScores.value.length === 0) {
console.warn('⚠️ 警告:班主任常规工作数据为空,可能存在数据问题');
proxy?.$modal.msgWarning('班主任常规工作数据为空,请检查数据是否正确');
}
if (specialWorkScores.value.length === 0) {
console.warn('⚠️ 警告:特色工作数据为空,可能存在数据问题');
proxy?.$modal.msgWarning('特色工作数据为空,请检查数据是否正确');
}
} catch (error) {
console.error('加载查看详情失败:', error);
proxy?.$modal.msgError('加载查看详情失败');
@@ -724,8 +724,8 @@ const handleClose = () => {
routineWorkScores.value = [];
specialWorkScores.value = [];
evaluationStats.value = undefined;
isDictLoaded.value = false; // 重置字典加载状态
isDictLoaded.value = false; // 重置字典加载状态
Object.assign(managementEffectivenessData, {
classDeduction: 0,
classDeductionHalf: 0,
@@ -802,7 +802,7 @@ defineExpose({
max-height: 150px;
overflow-y: auto;
}
.el-table__row {
height: auto;
}
@@ -7,12 +7,22 @@
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="指标类型编码" prop="indicatorTypeCode">
<el-input v-model="queryParams.indicatorTypeCode" placeholder="请输入指标类型编码(支持模糊搜索)" clearable @keyup.enter="handleQuery" />
<el-input
v-model="queryParams.indicatorTypeCode"
placeholder="请输入指标类型编码(支持模糊搜索)"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="指标类型名称" prop="indicatorTypeName">
<el-input v-model="queryParams.indicatorTypeName" placeholder="请输入指标类型名称(支持模糊搜索)" clearable @keyup.enter="handleQuery" />
<el-input
v-model="queryParams.indicatorTypeName"
placeholder="请输入指标类型名称(支持模糊搜索)"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
</el-col>
<el-col :span="8">
@@ -194,7 +204,7 @@ const handleQuery = () => {
pageNum: queryParams.value.pageNum,
pageSize: queryParams.value.pageSize
});
queryParams.value.pageNum = 1;
getList();
};
@@ -202,24 +212,26 @@ const handleQuery = () => {
/** 查询班主任考核指标类型列表 */
const getList = async () => {
loading.value = true;
// 构建查询参数,正确处理空字符串和undefined/null值
const queryData = {
pageNum: queryParams.value.pageNum,
pageSize: queryParams.value.pageSize,
// 修复:正确处理空字符串,只过滤undefined和null
...(queryParams.value.indicatorTypeCode !== undefined && queryParams.value.indicatorTypeCode !== null && { indicatorTypeCode: queryParams.value.indicatorTypeCode }),
...(queryParams.value.indicatorTypeName !== undefined && queryParams.value.indicatorTypeName !== null && { indicatorTypeName: queryParams.value.indicatorTypeName }),
...(queryParams.value.indicatorTypeCode !== undefined &&
queryParams.value.indicatorTypeCode !== null && { indicatorTypeCode: queryParams.value.indicatorTypeCode }),
...(queryParams.value.indicatorTypeName !== undefined &&
queryParams.value.indicatorTypeName !== null && { indicatorTypeName: queryParams.value.indicatorTypeName }),
...(queryParams.value.status !== undefined && queryParams.value.status !== null && { status: queryParams.value.status })
};
console.log('📋 发送查询请求,参数:', queryData);
console.log('🔍 原始查询参数:', {
indicatorTypeCode: `"${queryParams.value.indicatorTypeCode}"`,
indicatorTypeName: `"${queryParams.value.indicatorTypeName}"`,
status: `"${queryParams.value.status}"`
});
try {
const res = await listAssessmentIndicatorType(queryData);
console.log('📊 查询结果:', {
@@ -227,7 +239,7 @@ const getList = async () => {
dataCount: res.rows?.length,
firstItem: res.rows?.[0]
});
assessmentIndicatorTypeList.value = res.rows;
total.value = res.total;
} catch (error) {
@@ -253,23 +265,23 @@ const reset = () => {
/** 重置按钮操作 */
const resetQuery = () => {
console.log('🔄 重置查询参数');
// 重置表单字段
queryFormRef.value?.resetFields();
// 手动重置查询参数对象,确保清空所有搜索条件
queryParams.value.indicatorTypeCode = undefined;
queryParams.value.indicatorTypeName = undefined;
queryParams.value.status = undefined;
queryParams.value.pageNum = 1;
console.log('🔄 重置后的查询参数:', {
indicatorTypeCode: queryParams.value.indicatorTypeCode,
indicatorTypeName: queryParams.value.indicatorTypeName,
status: queryParams.value.status,
pageNum: queryParams.value.pageNum
});
// 重新查询
getList();
};
@@ -349,7 +361,7 @@ onMounted(() => {
pageNum: queryParams.value.pageNum,
pageSize: queryParams.value.pageSize
});
getList();
});
</script>
+2 -2
View File
@@ -454,7 +454,7 @@ const submitForm = () => {
/** 删除按钮操作 */
const handleDelete = async (row?: AssessmentPlanVO) => {
const _ids = row?.id || ids.value;
let confirmMessage = '';
if (row) {
const semesterText = row.semester === '1' ? '上学期' : '下学期';
@@ -462,7 +462,7 @@ const handleDelete = async (row?: AssessmentPlanVO) => {
} else {
confirmMessage = `是否确认删除选中的${ids.value.length}条考核计划?`;
}
await proxy?.$modal.confirm(confirmMessage).then(async () => {
await delAssessmentPlan(_ids).then(() => {
getList();
@@ -382,14 +382,14 @@ const submitForm = () => {
/** 删除按钮操作 */
const handleDelete = async (row?: BmzrAssessmentSchemeVO) => {
const _ids = row?.id || ids.value;
let confirmMessage = '';
if (row) {
confirmMessage = `是否确认删除考核方案"${row.schemeName}"(${row.academicYear}学年第${row.semester}学期)?`;
} else {
confirmMessage = `是否确认删除选中的${ids.value.length}条考核方案?`;
}
try {
await proxy?.$modal.confirm(confirmMessage);
await delBmzrAssessmentScheme(_ids);
+22 -24
View File
@@ -95,14 +95,14 @@
<!-- 未提交状态、草稿状态或审核不通过状态:显示提交考核按钮 -->
<template v-if="!scope.row.submitted || scope.row.auditStatus === '9' || scope.row.auditStatus === '2'">
<el-tooltip content="提交考核" placement="top">
<el-button
link
type="primary"
icon="Edit"
:disabled="loading || !isDataReady"
@click="handleSubmitAssessment(scope.row)"
v-hasPermi="['bzrkh:teacherAssessment:submit']"
></el-button>
<el-button
link
type="primary"
icon="Edit"
:disabled="loading || !isDataReady"
@click="handleSubmitAssessment(scope.row)"
v-hasPermi="['bzrkh:teacherAssessment:submit']"
></el-button>
</el-tooltip>
</template>
<!-- 待审核状态:显示查看考核和撤销按钮 -->
@@ -155,7 +155,7 @@
<!-- 考核提交弹窗 -->
<SubmitDialog ref="submitDialogRef" :plan-id="currentPlanId" :class-id="currentClassId" @success="handleSubmitSuccess" />
<!-- 考核查看弹窗 -->
<ViewDialog ref="viewDialogRef" :plan-id="currentPlanId" :class-id="currentClassId" />
</div>
@@ -241,13 +241,13 @@ const getTeacherClasses = async () => {
queryParams.value.planName = undefined; // 确保搜索条件被重置
queryParams.value.pageNum = 1;
queryParams.value.classId = activeTab.value;
console.log('🔄 初始化完成:', {
activeTab: activeTab.value,
classId: queryParams.value.classId,
planName: queryParams.value.planName
});
// 等待activeTab设置完成后再获取列表
await nextTick();
await getList();
@@ -270,9 +270,9 @@ const getList = async () => {
teacherId: userStore.userId, // 确保包含teacherId
planStatus: '1' // 确保包含planStatus
};
console.log('📋 查询参数:', queryData);
const res = await listTeacherAssessmentPlans(queryData);
console.log('📊 查询结果:', {
total: res.total,
@@ -280,7 +280,7 @@ const getList = async () => {
firstItemStatus: res.rows?.[0]?.auditStatus,
firstItemSubmitted: res.rows?.[0]?.submitted
});
assessmentPlanList.value = res.rows || [];
total.value = res.total || 0;
} catch (error) {
@@ -298,18 +298,18 @@ const handleTabChange = async (tabName: string) => {
activeTab: activeTab.value,
oldPlanName: queryParams.value.planName
});
// 重置分页和搜索条件,设置新的班级ID
queryParams.value.pageNum = 1;
queryParams.value.planName = undefined; // 重置搜索条件,避免标签页间共享
queryParams.value.classId = tabName;
console.log('🔄 参数设置完成:', {
classId: queryParams.value.classId,
activeTab: activeTab.value,
planName: queryParams.value.planName
});
// 等待参数设置完成后再查询
await nextTick();
getList();
@@ -332,20 +332,20 @@ const handleQuery = () => {
/** 重置按钮操作 */
const resetQuery = () => {
console.log('🔄 开始重置查询参数');
try {
// 保存当前班级ID和计划状态(这些不应该被重置)
const currentClassId = queryParams.value.classId;
const currentPlanStatus = queryParams.value.planStatus;
// 手动重置查询参数到初始状态
queryParams.value.planName = undefined;
queryParams.value.pageNum = 1;
// 恢复不应该被重置的参数
queryParams.value.classId = currentClassId;
queryParams.value.planStatus = currentPlanStatus;
console.log('🔄 重置后的查询参数:', {
planName: queryParams.value.planName,
pageNum: queryParams.value.pageNum,
@@ -353,7 +353,7 @@ const resetQuery = () => {
planStatus: queryParams.value.planStatus,
teacherId: userStore.userId
});
// 等待DOM更新完成后再刷新数据
nextTick(() => {
console.log('🔄 开始强制刷新数据');
@@ -367,8 +367,6 @@ const resetQuery = () => {
}
};
/** 排序触发事件 */
const sortChange = (column: any) => {
const { prop, order } = column;
@@ -185,7 +185,7 @@
<span>特色工作(加20分)</span>
</div>
</template>
<el-form :model="specialWorkForm" class="special-work-form">
<el-form :model="specialWorkForm" class="special-work-form">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="及时并妥善处理好学生违纪事件" label-width="240px" class="no-wrap-label">
@@ -203,7 +203,15 @@
</el-col>
<el-col :span="12">
<el-form-item label="自评意见" label-width="80px">
<el-input v-model="specialWorkForm.comment1" type="textarea" :rows="1" placeholder="请输入自评意见" maxlength="200" show-word-limit class="special-comment-input" />
<el-input
v-model="specialWorkForm.comment1"
type="textarea"
:rows="1"
placeholder="请输入自评意见"
maxlength="200"
show-word-limit
class="special-comment-input"
/>
</el-form-item>
</el-col>
</el-row>
@@ -224,7 +232,15 @@
</el-col>
<el-col :span="12">
<el-form-item label="自评意见" label-width="80px">
<el-input v-model="specialWorkForm.comment2" type="textarea" :rows="1" placeholder="请输入自评意见" maxlength="200" show-word-limit class="special-comment-input" />
<el-input
v-model="specialWorkForm.comment2"
type="textarea"
:rows="1"
placeholder="请输入自评意见"
maxlength="200"
show-word-limit
class="special-comment-input"
/>
</el-form-item>
</el-col>
</el-row>
@@ -245,7 +261,15 @@
</el-col>
<el-col :span="12">
<el-form-item label="自评意见" label-width="80px">
<el-input v-model="specialWorkForm.comment3" type="textarea" :rows="1" placeholder="请输入自评意见" maxlength="200" show-word-limit class="special-comment-input" />
<el-input
v-model="specialWorkForm.comment3"
type="textarea"
:rows="1"
placeholder="请输入自评意见"
maxlength="200"
show-word-limit
class="special-comment-input"
/>
</el-form-item>
</el-col>
</el-row>
@@ -266,15 +290,23 @@
</el-col>
<el-col :span="12">
<el-form-item label="自评意见" label-width="80px">
<el-input v-model="specialWorkForm.comment4" type="textarea" :rows="1" placeholder="请输入自评意见" maxlength="200" show-word-limit class="special-comment-input" />
<el-input
v-model="specialWorkForm.comment4"
type="textarea"
:rows="1"
placeholder="请输入自评意见"
maxlength="200"
show-word-limit
class="special-comment-input"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="总分值" label-width="240px">
<el-input :value="specialWorkForm.totalScore?.toFixed(2) || '0.00'" readonly style="width: 150px; text-align: center" />
</el-form-item>
<el-input :value="specialWorkForm.totalScore?.toFixed(2) || '0.00'" readonly style="width: 150px; text-align: center" />
</el-form-item>
</el-col>
</el-row>
</el-form>
@@ -316,7 +348,9 @@
<template #footer>
<div class="dialog-footer">
<el-button :loading="saveLoading" type="primary" @click="handleSubmit">保存</el-button>
<el-button :loading="submitLoading" type="success" @click="handleSubmitForApproval" v-hasPermi="['bzrkh:teacherAssessment:submit']">提交</el-button>
<el-button :loading="submitLoading" type="success" @click="handleSubmitForApproval" v-hasPermi="['bzrkh:teacherAssessment:submit']"
>提交</el-button
>
<el-button @click="handleClose">取消</el-button>
</div>
</template>
@@ -525,7 +559,7 @@ const calculateMonthlyScore = () => {
// 检查是否有实际评分数据(避免空白状态显示50分)
const hasAnyScore = routineWorkTotal > 0 || deductionHalf > 0 || specialWorkTotal > 0;
if (!hasAnyScore || totalCoefficient === 0) {
return 0; // 没有评分数据时显示0
}
@@ -534,7 +568,7 @@ const calculateMonthlyScore = () => {
const classDeductionMaxScore = 50;
// 修正后的考核得分公式 = 总系数(K)* ((班主任常规工作自评分之和 + 班级扣分项满分值) - 按班级扣分50%计的分值 + 特色工作总分值)
return totalCoefficient * ((routineWorkTotal + classDeductionMaxScore) - deductionHalf + specialWorkTotal);
return totalCoefficient * (routineWorkTotal + classDeductionMaxScore - deductionHalf + specialWorkTotal);
};
/** 打开弹窗 */
@@ -935,7 +969,7 @@ defineExpose({
font-weight: 400;
text-align: center;
}
/* 覆盖small尺寸的字体大小,与特色工作输入框保持一致 */
.el-input-number--small.score-input .el-input-number__input {
font-size: 14px !important;
@@ -55,9 +55,7 @@
</template>
<el-descriptions :column="2" border>
<el-descriptions-item label="班级总人数">{{ getCurrentClassDetail()?.totalStudentCount || 0 }}人</el-descriptions-item>
<el-descriptions-item label="人数系数">{{
formatNumber(getCurrentCoefficient()?.studentCountCoefficient, 3)
}}</el-descriptions-item>
<el-descriptions-item label="人数系数">{{ formatNumber(getCurrentCoefficient()?.studentCountCoefficient, 3) }}</el-descriptions-item>
</el-descriptions>
</el-card>
</el-col>
@@ -70,9 +68,7 @@
</template>
<el-descriptions :column="2" border>
<el-descriptions-item label="住校生人数">{{ getCurrentClassDetail()?.boardingStudentCount || 0 }}人</el-descriptions-item>
<el-descriptions-item label="住校生系数">{{
formatNumber(getCurrentCoefficient()?.boardingCoefficient, 3)
}}</el-descriptions-item>
<el-descriptions-item label="住校生系数">{{ formatNumber(getCurrentCoefficient()?.boardingCoefficient, 3) }}</el-descriptions-item>
</el-descriptions>
</el-card>
</el-col>
@@ -165,10 +161,10 @@
</el-col>
<el-col :span="8">
<el-form-item label="上级评分">
<el-input
:value="approvalManagementEffectivenessData.superiorScore?.toFixed(2) || '-'"
readonly
style="width: 100%; text-align: center"
<el-input
:value="approvalManagementEffectivenessData.superiorScore?.toFixed(2) || '-'"
readonly
style="width: 100%; text-align: center"
/>
</el-form-item>
</el-col>
@@ -176,12 +172,12 @@
<el-row>
<el-col :span="12">
<el-form-item label="自评意见">
<el-input :value="approvalManagementEffectivenessData.selfComment" readonly style="height: 24px;" />
<el-input :value="approvalManagementEffectivenessData.selfComment" readonly style="height: 24px" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="审批意见">
<el-input :value="approvalManagementEffectivenessData.superiorComment" readonly style="height: 24px;" />
<el-input :value="approvalManagementEffectivenessData.superiorComment" readonly style="height: 24px" />
</el-form-item>
</el-col>
</el-row>
@@ -280,17 +276,17 @@
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="考核得分">
<el-input :value="calculateMonthlyScore().toFixed(2)" readonly style="width: 100%; text-align: center; height: 24px;" />
<el-input :value="calculateMonthlyScore().toFixed(2)" readonly style="width: 100%; text-align: center; height: 24px" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="自我总体评价">
<el-input :value="approvalMonthlyScoreData.selfOverallComment" readonly style="height: 24px;" />
<el-input :value="approvalMonthlyScoreData.selfOverallComment" readonly style="height: 24px" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="上级总体评价">
<el-input :value="approvalMonthlyScoreData.superiorOverallComment" readonly style="height: 24px;" />
<el-input :value="approvalMonthlyScoreData.superiorOverallComment" readonly style="height: 24px" />
</el-form-item>
</el-col>
</el-row>
@@ -307,15 +303,13 @@
</template>
<el-descriptions :column="3" border>
<el-descriptions-item label="评价等级">
<div style="display: flex; align-items: center; gap: 8px;">
<div style="display: flex; align-items: center; gap: 8px">
<dict-tag :options="getEvaluationLevelOptions()" :value="calculateEvaluationLevel()" />
<el-tooltip placement="top">
<template #content>
<div>
<div>{{ evaluationStats ? '基于占比规则的精准评级' : '基于分数阈值的简化评级' }}</div>
<div v-if="isDictDataMissing" style="color: #f56c6c; margin-top: 4px;">
⚠️ 字典数据缺失,使用默认数据
</div>
<div v-if="isDictDataMissing" style="color: #f56c6c; margin-top: 4px">⚠️ 字典数据缺失,使用默认数据</div>
</div>
</template>
<el-icon :color="evaluationStats ? '#67c23a' : '#e6a23c'" size="16">
@@ -325,16 +319,14 @@
</el-tooltip>
</div>
</el-descriptions-item>
<el-descriptions-item label="考核得分">
{{ calculateMonthlyScore().toFixed(2) }}分
</el-descriptions-item>
<el-descriptions-item label="考核得分"> {{ calculateMonthlyScore().toFixed(2) }}分 </el-descriptions-item>
<el-descriptions-item label="审批时间">
{{ parseTime(approvalDetail?.submitTime, '{y}-{m}-{d} {h}:{i}:{s}') || '-' }}
</el-descriptions-item>
</el-descriptions>
<el-form label-width="100px" style="margin-top: 20px" v-if="approvalMonthlyScoreData.superiorOverallComment">
<el-form-item label="审批意见">
<el-input :value="approvalMonthlyScoreData.superiorOverallComment" readonly style="height: 24px;" />
<el-input :value="approvalMonthlyScoreData.superiorOverallComment" readonly style="height: 24px" />
</el-form-item>
</el-form>
</el-card>
@@ -434,11 +426,7 @@
import { InfoFilled, WarningFilled } from '@element-plus/icons-vue';
import { getAssessmentSubmitDetail, getClassStudentRecords } from '@/api/bzrkh/teacherAssessment';
import { getAssessmentApprovalDetail, getEvaluationLevelStats } from '@/api/bzrkh/assessmentApproval';
import {
AssessmentDetailVO,
AssessmentIndicatorVO,
ClassStudentRecordsVO
} from '@/api/bzrkh/teacherAssessment/types';
import { AssessmentDetailVO, AssessmentIndicatorVO, ClassStudentRecordsVO } from '@/api/bzrkh/teacherAssessment/types';
import {
AssessmentApprovalDetailVO,
AssessmentApprovalIndicatorVO,
@@ -471,9 +459,10 @@ const getEvaluationLevelOptions = (): DictDataOption[] => {
console.log('✅ 使用数据库字典数据:', bmzr_evaluation_level.value);
return bmzr_evaluation_level.value;
}
// 字典数据未加载或为空时的处理
if (visible.value) { // 只在弹窗打开时输出警告
if (visible.value) {
// 只在弹窗打开时输出警告
console.warn('⚠️ 字典数据为空,使用备用数据,当前状态:', {
弹窗是否打开: visible.value,
字典对象存在: !!bmzr_evaluation_level.value,
@@ -482,7 +471,7 @@ const getEvaluationLevelOptions = (): DictDataOption[] => {
字典数据内容: bmzr_evaluation_level.value
});
}
return fallbackEvaluationLevelOptions.value;
};
@@ -511,10 +500,10 @@ const isDictLoaded = ref(false);
const isDictDataMissing = computed(() => {
// 如果还没有尝试加载字典数据,不显示警告
if (!visible.value) return false;
// 如果字典数据已经加载成功,不显示警告
if (isDictLoaded.value) return false;
// 只有在弹窗打开且字典数据为空时才显示警告
return !bmzr_evaluation_level.value || bmzr_evaluation_level.value.length === 0;
});
@@ -556,16 +545,16 @@ const specialWorkForm = reactive({
totalScore: 0
});
// 考核得分表单(只读)
const monthlyScoreForm = reactive({
overallComment: ''
});
// 考核得分表单(只读)
const monthlyScoreForm = reactive({
overallComment: ''
});
// 审批模式下的考核得分数据
const approvalMonthlyScoreData = reactive({
selfOverallComment: '',
superiorOverallComment: ''
});
// 审批模式下的考核得分数据
const approvalMonthlyScoreData = reactive({
selfOverallComment: '',
superiorOverallComment: ''
});
// 学生违纪记录和奖励记录弹窗相关
const studentRecordsVisible = ref(false);
@@ -666,7 +655,7 @@ const calculateMonthlyScore = () => {
// 检查是否有实际评分数据(避免空白状态显示50分)
const hasAnyScore = routineWorkTotal > 0 || deductionHalf > 0 || specialWorkTotal > 0;
if (!hasAnyScore || totalCoefficient === 0) {
return 0; // 没有评分数据时显示0
}
@@ -675,7 +664,7 @@ const calculateMonthlyScore = () => {
const classDeductionMaxScore = 50;
// 修正后的考核得分公式 = 总系数(K)* ((班主任常规工作自评分之和 + 班级扣分项满分值) - 按班级扣分50%计的分值 + 特色工作总分值)
return totalCoefficient * ((routineWorkTotal + classDeductionMaxScore) - deductionHalf + specialWorkTotal);
return totalCoefficient * (routineWorkTotal + classDeductionMaxScore - deductionHalf + specialWorkTotal);
}
};
@@ -685,7 +674,7 @@ const calculateEvaluationLevel = () => {
console.log('=== 📊 评价等级计算调试 ===');
console.log('💯 考核得分:', score);
console.log('📈 是否有统计数据:', !!evaluationStats.value);
if (evaluationStats.value) {
console.log('📊 统计数据详情:', {
总教师数: evaluationStats.value.totalTeachers,
@@ -694,14 +683,12 @@ const calculateEvaluationLevel = () => {
及格分数: evaluationStats.value.evaluationLevelRules?.passScore
});
}
let level = '';
let calculationMethod = '';
// 如果有统计数据,使用严格的占比规则
if (evaluationStats.value &&
evaluationStats.value.totalTeachers > 0 &&
evaluationStats.value.scoreRankings.length > 0) {
if (evaluationStats.value && evaluationStats.value.totalTeachers > 0 && evaluationStats.value.scoreRankings.length > 0) {
level = calculateEvaluationLevelByStats(score);
calculationMethod = '📈 严格占比规则评级';
} else {
@@ -714,16 +701,16 @@ const calculateEvaluationLevel = () => {
level = 'B'; // B等(合格):60-85分
}
calculationMethod = '🔢 简化分数评级';
// 输出使用简化评级的原因
const reasons = [];
if (!evaluationStats.value) reasons.push('统计数据为空');
if (evaluationStats.value?.totalTeachers === 0) reasons.push('总教师数为0');
if (evaluationStats.value?.scoreRankings?.length === 0) reasons.push('排名数据为空');
console.warn('⚠️ 使用简化评级的原因:', reasons.join('、'));
}
console.log('🎯 计算方法:', calculationMethod);
console.log('📋 最终评价等级:', level);
console.log('=== 📊 评价等级计算调试结束 ===');
@@ -735,30 +722,30 @@ const calculateEvaluationLevelByStats = (score: number) => {
const stats = evaluationStats.value;
if (!stats) {
console.warn('统计数据不可用,使用简单评级');
return score < 60 ? 'C' : (score >= 85 ? 'A' : 'B');
return score < 60 ? 'C' : score >= 85 ? 'A' : 'B';
}
const { totalTeachers, scoreRankings, evaluationLevelRules } = stats;
const { aLevelMaxPercentage, passScore } = evaluationLevelRules;
// C等:不及格
if (score < passScore) {
return 'C';
}
// 查找当前分数的排名
const currentRanking = scoreRankings.find(item => item.score === score);
const currentRanking = scoreRankings.find((item) => item.score === score);
if (!currentRanking) {
console.warn('找不到当前分数的排名信息,使用简单评级');
return score < 60 ? 'C' : (score >= 85 ? 'A' : 'B');
return score < 60 ? 'C' : score >= 85 ? 'A' : 'B';
}
// A等:及格且排名在前20%内
if (score >= passScore && currentRanking.percentage <= aLevelMaxPercentage) {
return 'A';
}
// B等:其余及格的
return 'B';
};
@@ -783,7 +770,7 @@ const open = async (auditStatus?: string) => {
routineWorkScores.value = [];
approvalRoutineWorkScores.value = [];
approvalSpecialWorkScores.value = [];
Object.assign(managementEffectivenessForm, {
classDeduction: 0,
classDeductionHalf: 0,
@@ -816,7 +803,7 @@ const open = async (auditStatus?: string) => {
});
visible.value = true;
// 检查字典数据加载状态
console.log('🔍 打开弹窗时字典数据状态:', {
字典对象存在: !!bmzr_evaluation_level.value,
@@ -824,21 +811,21 @@ const open = async (auditStatus?: string) => {
字典加载状态: isDictLoaded.value,
字典数据内容: bmzr_evaluation_level.value
});
// 等待字典数据加载完成(最多等待2秒)
let waitCount = 0;
while ((!bmzr_evaluation_level.value || bmzr_evaluation_level.value.length === 0) && waitCount < 20) {
await new Promise(resolve => setTimeout(resolve, 100));
await new Promise((resolve) => setTimeout(resolve, 100));
waitCount++;
}
if (bmzr_evaluation_level.value && bmzr_evaluation_level.value.length > 0) {
console.log('✅ 字典数据加载完成:', bmzr_evaluation_level.value);
isDictLoaded.value = true;
} else {
console.warn('⚠️ 字典数据加载超时,使用备用数据');
}
// 根据显示模式加载不同的数据
if (viewMode.value === 'approval') {
await loadApprovalDetail();
@@ -854,7 +841,7 @@ const loadApprovalDetail = async () => {
// 从userStore获取当前用户ID作为被评价用户ID
const userStore = useUserStore();
const evaluatedUserId = userStore.userId;
if (!evaluatedUserId) {
proxy?.$modal.msgError('获取用户信息失败');
return;
@@ -925,7 +912,6 @@ const loadApprovalDetail = async () => {
}
console.log('=== 审批详情数据加载完成 ===');
} catch (error) {
console.error('加载审批详情失败:', error);
proxy?.$modal.msgError('加载审批详情失败,请检查网络连接或刷新页面重试');
@@ -981,7 +967,7 @@ const loadAssessmentDetail = async () => {
break;
}
});
// 计算特色工作总分
specialWorkForm.totalScore =
(specialWorkForm.score1 || 0) + (specialWorkForm.score2 || 0) + (specialWorkForm.score3 || 0) + (specialWorkForm.score4 || 0);
@@ -1033,8 +1019,8 @@ const handleClose = () => {
approvalRoutineWorkScores.value = [];
approvalSpecialWorkScores.value = [];
evaluationStats.value = undefined;
isDictLoaded.value = false; // 重置字典加载状态
isDictLoaded.value = false; // 重置字典加载状态
Object.assign(managementEffectivenessForm, {
classDeduction: 0,
classDeductionHalf: 0,
@@ -1128,13 +1114,13 @@ defineExpose({
.total-score {
font-size: 16px;
font-weight: 600;
color: #409EFF;
color: #409eff;
}
.monthly-score {
font-size: 18px;
font-weight: 600;
color: #67C23A;
color: #67c23a;
}
/* 表格样式 */
@@ -1183,7 +1169,7 @@ defineExpose({
max-height: 150px;
overflow-y: auto;
}
.el-table__row {
height: auto;
}
@@ -1280,4 +1266,4 @@ defineExpose({
padding: 16px;
}
}
</style>
</style>
+24 -1
View File
@@ -33,6 +33,7 @@ web/src/api/cw/
## 🚀 核心功能
### 1. 项目审批人配置管理 (`projectAuditor/`)
- **审批人池管理**: 从系统用户中选择并配置审批人员
- **批量添加**: 通过部门树选择用户,支持批量添加审批人
- **跨部门选择**: 支持在不同部门间选择用户,选择状态会自动保持
@@ -40,23 +41,27 @@ web/src/api/cw/
- **权限控制**: 基于RBAC的细粒度权限控制
### 2. 项目审批流程管理 (`projectApproval/`)
- **流程监控**: 查看所有审批流程状态和进度(待实现)
- **流程详情**: 查看审批记录和时间轴展示(待实现)
- **流程终止**: 管理员可终止进行中的审批流程(待实现)
### 3. 我的待审批 (`projectApproval/pending.vue`)
- **待审批列表**: 显示当前用户需要审批的项目
- **项目详情**: 查看项目完整信息和审批历史
- **审批操作**: 支持通过/拒绝,必填审批意见
- **批量审批**: 支持批量通过或拒绝多个项目(待实现)
### 4. 审批流程选择组件 (`components/ApprovalProcessSelector.vue`)
- **自定义流程配置**: 支持用户从审批人池中选择审核人
- **可视化流程预览**: 直观展示审批顺序和流程走向
- **审核人管理**: 支持添加、删除、排序审核人
- **流程验证**: 验证审批流程的合理性和完整性
### 5. 项目申报集成 (`application/form.vue` - 已修改)
- **流程选择对话框**: 在提交时弹出审批流程选择界面
- **数据保存**: 先保存申报数据,再配置审批流程
- **流程启动**: 自动启动配置的审批流程
@@ -65,6 +70,7 @@ web/src/api/cw/
## 📋 实现特色
### 🔧 技术特色
1. **Vue 3.5.13 Composition API**: 使用最新的组合式API,代码结构清晰
2. **TypeScript支持**: 完整的类型定义,提供良好的开发体验
3. **Element Plus UI**: 统一的UI组件库,符合后勤管理模块设计规范
@@ -72,6 +78,7 @@ web/src/api/cw/
5. **组件化开发**: 高度复用的组件设计,易于维护和扩展
### 🎨 用户体验特色
1. **跨部门选择**: 审批人选择支持在多个部门间切换,保持选择状态
2. **可视化流程**: 审批流程以图形化方式展示,直观易懂
3. **实时验证**: 表单验证和流程验证,及时提供反馈
@@ -79,6 +86,7 @@ web/src/api/cw/
5. **状态恢复**: 切换部门后自动恢复之前的选择状态
### 🚀 性能特色
1. **按需加载**: 组件和API按需加载,优化性能
2. **智能缓存**: 合理的数据缓存策略,减少重复请求
3. **批量操作**: 支持批量添加、删除等操作,提升效率
@@ -87,16 +95,19 @@ web/src/api/cw/
## 🔄 核心流程
### 审批人配置流程
```
管理员登录 → 进入审批人配置 → 选择部门 → 批量选择用户 → 确认添加 → 配置完成
```
### 项目申报审批流程
```
填写申报信息 → 点击提交 → 选择审批流程 → 配置审核人 → 确认提交 → 流程启动
```
### 审批执行流程
```
审核人收到通知 → 查看待审批列表 → 查看项目详情 → 执行审批操作 → 填写意见 → 提交审批
```
@@ -104,16 +115,19 @@ web/src/api/cw/
## 🛠️ 后端支持
### 数据库设计
1. **cw_project_auditor**: 项目审核人配置表
2. **cw_project_approval_process**: 项目审批流程表
3. **cw_approval_record**: 审批记录表(已扩展支持自定义流程)
### API接口
1. **审核人配置**: 16个标准API接口(CRUD + 业务功能 + 辅助功能)
2. **审批流程**: 10个核心API接口(流程管理 + 审批执行)
3. **权限控制**: 基于Sa-Token的权限管理
### 数据安全
1. **多租户隔离**: 严格的数据隔离机制
2. **权限控制**: 细粒度的功能权限控制
3. **操作日志**: 完整的操作记录和审计
@@ -121,18 +135,21 @@ web/src/api/cw/
## 📊 技术规范
### 前端规范
- **组件命名**: PascalCase,清晰的语义化命名
- **文件组织**: 按功能模块组织,层次分明
- **样式管理**: Scoped CSS,避免样式冲突
- **状态管理**: Reactive响应式状态,简洁高效
### 后端规范
- **接口设计**: RESTful风格,统一响应格式
- **数据验证**: Bean Validation参数校验
- **异常处理**: 统一的异常处理机制
- **事务管理**: 合理的事务边界控制
### 代码质量
- **TypeScript**: 完整的类型检查
- **ESLint**: 代码风格检查
- **Prettier**: 代码格式化
@@ -141,18 +158,21 @@ web/src/api/cw/
## 🎯 后续优化
### 功能扩展
1. **审批流程管理页面**: 完整的流程监控和管理界面
2. **批量审批功能**: 支持批量处理多个项目
3. **审批历史查询**: 完整的审批记录查询功能
4. **移动端支持**: 基于uni-app的移动端审批应用
### 性能优化
1. **虚拟滚动**: 大数据量列表的性能优化
2. **缓存策略**: 更智能的数据缓存机制
3. **懒加载**: 按需加载组件和数据
4. **CDN集成**: 静态资源CDN加速
### 用户体验
1. **快捷操作**: 键盘快捷键支持
2. **拖拽排序**: 审核人拖拽排序功能
3. **消息通知**: 实时消息推送
@@ -161,18 +181,21 @@ web/src/api/cw/
## 🚀 部署说明
### 开发环境
1. 确保Node.js 20.15.x+,npm 10.7.x+
2. 安装依赖:`npm install`
3. 启动开发服务器:`npm run dev`
4. 访问:http://localhost
### 生产环境
1. 构建项目:`npm run build`
2. 部署到Nginx或其他Web服务器
3. 配置反向代理到后端API
4. 确保数据库脚本已执行
### 数据库部署
1. 执行数据库脚本:`docs/数据库修复脚本.sql`
2. 配置权限菜单和角色权限
3. 创建测试用户和审核人数据
@@ -187,4 +210,4 @@ web/src/api/cw/
**📝 维护日期**: 2025-01-03
**📁 文件**: 完整的前后端实现 + 数据库脚本 + API文档
**🔄 状态**: 核心功能完成,待进一步测试和优化
**🔄 状态**: 核心功能完成,待进一步测试和优化
+1 -1
View File
@@ -1158,7 +1158,7 @@ const confirmApprovalSubmit = async () => {
// 构建审批流程数据
const approvalData = {
applicationId: form.id,
applicationNo: form.applicationNo, // 新增:申报编号
applicationNo: form.applicationNo, // 新增:申报编号
auditorIds: processConfig.auditorIds,
processName: processConfig.processName || `${form.projectName}审批流程`,
submitterId: userStore.userId,
+132 -40
View File
@@ -122,10 +122,14 @@
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['cw:application:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['cw:application:remove']">删除</el-button>
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['cw:application:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Upload" @click="handleSubmit" :disabled="multiple" v-hasPermi="['cw:application:submit']">提交审批</el-button>
<el-button type="info" plain icon="Upload" @click="handleSubmit" :disabled="multiple" v-hasPermi="['cw:application:submit']"
>提交审批</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['cw:application:export']">导出</el-button>
@@ -135,7 +139,15 @@
</template>
<div class="table-container">
<el-table v-loading="loading" border :data="applicationList" @selection-change="handleSelectionChange" style="width: 100%" :scroll-x="true" class="scroll-table">
<el-table
v-loading="loading"
border
:data="applicationList"
@selection-change="handleSelectionChange"
style="width: 100%"
:scroll-x="true"
class="scroll-table"
>
<el-table-column type="selection" width="55" align="center" fixed="left" />
<el-table-column label="申报编号" align="center" prop="applicationNo" min-width="140" />
<el-table-column label="项目名称" align="center" prop="projectName" min-width="180" show-overflow-tooltip />
@@ -169,31 +181,13 @@
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['cw:application:edit']" />
</el-tooltip>
<el-tooltip content="提交审批" placement="top" v-if="['draft', 'returned'].includes(scope.row.approvalStatus)">
<el-button
link
type="warning"
icon="Upload"
@click="handleSubmit(scope.row)"
v-hasPermi="['cw:application:submit']"
/>
<el-button link type="warning" icon="Upload" @click="handleSubmit(scope.row)" v-hasPermi="['cw:application:submit']" />
</el-tooltip>
<el-tooltip content="撤回" placement="top" v-if="scope.row.approvalStatus === 'submitted'">
<el-button
link
type="info"
icon="RefreshLeft"
@click="handleWithdraw(scope.row)"
v-hasPermi="['cw:application:withdraw']"
/>
<el-button link type="info" icon="RefreshLeft" @click="handleWithdraw(scope.row)" v-hasPermi="['cw:application:withdraw']" />
</el-tooltip>
<el-tooltip content="删除" placement="top" v-if="['draft', 'cancelled', 'returned'].includes(scope.row.approvalStatus)">
<el-button
link
type="danger"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['cw:application:remove']"
/>
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['cw:application:remove']" />
</el-tooltip>
</div>
</template>
@@ -234,6 +228,20 @@
</div>
</template>
</el-dialog>
<!-- 审批流程选择对话框 -->
<el-dialog title="选择审批流程" v-model="approvalProcessDialog.visible" width="800px" append-to-body :close-on-click-modal="false">
<ApprovalProcessSelector
ref="approvalProcessSelectorRef"
v-model="approvalProcessDialog.selectedAuditors"
/>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelSubmitApproval">取 消</el-button>
<el-button type="primary" @click="confirmSubmitApproval">确认提交</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
@@ -247,8 +255,11 @@ import {
submitApplication,
getApplicationStatistics
} from '@/api/cw/projectApplication';
import { startCustomApprovalProcess, type CwCustomApprovalRequest } from '@/api/cw/projectApproval';
import { getInfo } from '@/api/login';
import ApplicationForm from './form.vue';
import ApplicationSummary from './summary.vue';
import ApprovalProcessSelector from '../projectApproval/components/ApprovalProcessSelector.vue';
import Pagination from '@/components/Pagination/index.vue';
export default {
@@ -256,6 +267,7 @@ export default {
components: {
ApplicationForm,
ApplicationSummary,
ApprovalProcessSelector,
Pagination
},
data() {
@@ -300,7 +312,14 @@ export default {
totalCount: 0,
approvedCount: 0,
pendingCount: 0,
draftCount: 0
draftCount: 0,
// 审批流程选择对话框
approvalProcessDialog: {
visible: false,
applicationId: null,
applicationNo: null,
selectedAuditors: []
}
};
},
created() {
@@ -476,21 +495,92 @@ export default {
},
/** 提交审批 */
handleSubmit(row) {
const applications = row?.applicationNo
? [row.applicationNo]
: this.applicationList.filter((item) => this.ids.includes(item.id)).map((item) => item.applicationNo);
if (row) {
// 单个提交
this.approvalProcessDialog.applicationId = row.id;
this.approvalProcessDialog.applicationNo = row.applicationNo;
this.approvalProcessDialog.visible = true;
this.approvalProcessDialog.selectedAuditors = [];
} else {
// 批量提交 - 暂不支持
this.$modal.msgWarning('批量提交审批功能暂未开放,请单个提交');
}
},
/** 确认提交审批 */
async confirmSubmitApproval() {
try {
const processSelector = this.$refs.approvalProcessSelectorRef;
if (!processSelector) {
this.$modal.msgError('审批流程选择组件未加载');
return;
}
this.$modal
.confirm('是否确认提交申报编号为"' + applications.join('", "') + '"的数据项?')
.then(() => {
return submitApplication(row?.id || this.ids);
})
.then(() => {
this.getList();
this.getStatistics(); // 提交后更新统计
this.$modal.msgSuccess('提交成功');
})
.catch(() => {});
// 验证审批流程
const validation = processSelector.validateProcess();
if (!validation.valid) {
this.$modal.msgError(validation.message);
return;
}
// 获取当前用户信息
let userInfo = null;
try {
const userResponse = await getInfo();
if (userResponse.code === 200 && userResponse.data && userResponse.data.user) {
userInfo = userResponse.data.user;
}
} catch (userError) {
console.error('获取用户信息失败:', userError);
this.$modal.msgError('获取用户信息失败,请重新登录');
return;
}
if (!userInfo || !userInfo.userId) {
this.$modal.msgError('用户信息不完整,请重新登录');
return;
}
// 获取审批流程配置
const processConfig = processSelector.getProcessConfig();
// 构建请求数据
const approvalData: CwCustomApprovalRequest = {
applicationId: this.approvalProcessDialog.applicationId,
applicationNo: this.approvalProcessDialog.applicationNo,
processName: processConfig.processName,
auditorIds: processConfig.auditorIds,
submitterId: userInfo.userId,
submitterName: userInfo.nickName || userInfo.userName
};
console.log('启动审批流程数据:', approvalData);
// 启动审批流程
const result = await startCustomApprovalProcess(approvalData);
console.log('启动审批流程结果:', result);
// 关闭对话框
this.approvalProcessDialog.visible = false;
this.approvalProcessDialog.applicationId = null;
this.approvalProcessDialog.applicationNo = null;
this.approvalProcessDialog.selectedAuditors = [];
// 刷新列表和统计
this.getList();
this.getStatistics();
this.$modal.msgSuccess('审批流程启动成功');
} catch (error) {
console.error('启动审批流程失败:', error);
this.$modal.msgError('启动审批流程失败:' + (error.message || '未知错误'));
}
},
/** 取消提交审批 */
cancelSubmitApproval() {
this.approvalProcessDialog.visible = false;
this.approvalProcessDialog.applicationId = null;
this.approvalProcessDialog.applicationNo = null;
this.approvalProcessDialog.selectedAuditors = [];
},
/** 撤回申请 */
handleWithdraw(row) {
@@ -642,7 +732,9 @@ export default {
/* 卡片样式 */
:deep(.el-card) {
border: 1px solid #e4e7ed;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
box-shadow:
0 2px 4px rgba(0, 0, 0, 0.12),
0 0 6px rgba(0, 0, 0, 0.04);
}
/* 表格卡片头部样式 */
+12 -2
View File
@@ -98,7 +98,15 @@
</el-table>
</div>
<div class="pagination-container">
<pagination v-show="total > 0" :total="total" :page="queryParams.pageNum" :limit="queryParams.pageSize" @update:page="queryParams.pageNum = $event" @update:limit="queryParams.pageSize = $event" @pagination="getList" />
<pagination
v-show="total > 0"
:total="total"
:page="queryParams.pageNum"
:limit="queryParams.pageSize"
@update:page="queryParams.pageNum = $event"
@update:limit="queryParams.pageSize = $event"
@pagination="getList"
/>
</div>
</el-card>
@@ -377,7 +385,9 @@ export default {
.table-card {
margin-top: 0;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
box-shadow:
0 2px 4px rgba(0, 0, 0, 0.12),
0 0 6px rgba(0, 0, 0, 0.04);
}
.table-card :deep(.el-card__body) {
+2 -2
View File
@@ -157,8 +157,8 @@
</el-form-item>
<el-form-item label="审批决定" prop="approvalResult" required>
<el-radio-group v-model="approvalForm.approvalResult">
<el-radio label="approve">通过</el-radio>
<el-radio label="reject">退回申请人</el-radio>
<el-radio value="approve">通过</el-radio>
<el-radio value="reject">退回申请人</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="审批意见" prop="approvalComment">
+17 -2
View File
@@ -7,23 +7,27 @@
## 主要功能
### 1. 审批流程管理
- 查询审批流程列表(支持分页)
- 按申报编号、流程状态、提交人等条件筛选
- 显示审批进度和流程状态
- 支持终止正在进行的审批流程
### 2. 待我审批
- 查看分配给当前用户的待审批任务
- 执行审批操作(通过/打回)
- 填写审批意见
- 查看申报详情和审批记录
### 3. 我的已审批
- 查看当前用户已完成的审批记录
- 显示审批动作和意见
- 统计审批效率和处理时长
### 4. 审批记录跟踪
- 完整的审批时间线展示
- 每个审批节点的详细信息
- 审批意见和处理结果记录
@@ -39,21 +43,25 @@ web/src/views/cw/projectApproval/
## 功能特性
### 1. 多标签页设计
- **审批流程管理**: 查看所有审批流程的整体情况
- **待我审批**: 专注于当前用户需要处理的审批任务
- **我的已审批**: 查看历史审批记录和统计
### 2. 统计卡片
- 总流程数、等待开始、进行中、已完成等状态统计
- 平均处理时长、待我审批数量等关键指标
- 本月完成数量、已终止流程等趋势数据
### 3. 审批操作
- 支持批量审批和单个审批
- 必填审批意见,确保审批质量
- 实时更新审批状态和流程进度
### 4. 流程可视化
- 进度条显示审批进度
- 时间线展示审批历史
- 状态标签清晰标识各阶段
@@ -61,6 +69,7 @@ web/src/views/cw/projectApproval/
## API接口
使用的主要API接口:
- `listProjectApproval` - 查询审批流程列表
- `listPendingApprovals` - 查询待我审批列表
- `listProcessedApprovals` - 查询我的已审批列表
@@ -72,6 +81,7 @@ web/src/views/cw/projectApproval/
## 权限控制
页面功能对应的权限标识:
- `cw:projectApproval:list` - 查询权限
- `cw:projectApproval:add` - 启动流程权限
- `cw:projectApproval:approve` - 审批权限
@@ -81,18 +91,21 @@ web/src/views/cw/projectApproval/
## 状态说明
### 流程状态
- `waiting` - 等待开始
- `running` - 进行中
- `running` - 进行中
- `completed` - 已完成
- `terminated` - 已终止
### 节点状态
- `waiting` - 等待审批
- `approved` - 已通过
- `rejected` - 已拒绝
- `pending` - 待处理
### 审批动作
- `approve` - 审批通过
- `reject` - 打回申请
@@ -114,6 +127,7 @@ web/src/views/cw/projectApproval/
## 使用说明
### 审批人员操作流程
1. 登录系统,进入项目审批管理页面
2. 点击"待我审批"标签页查看待处理任务
3. 点击具体记录查看申报详情
@@ -121,6 +135,7 @@ web/src/views/cw/projectApproval/
5. 提交审批结果,系统自动推进流程
### 管理员操作流程
1. 在"审批流程管理"标签页查看所有流程状态
2. 可以终止异常或不需要的审批流程
3. 通过统计卡片监控审批效率和积压情况
@@ -131,4 +146,4 @@ web/src/views/cw/projectApproval/
1. 审批意见为必填项,且至少5个字符
2. 审批操作不可撤销,请谨慎操作
3. 终止流程会影响项目申报状态,需谨慎使用
4. 导出功能需要相应权限,请联系管理员配置
4. 导出功能需要相应权限,请联系管理员配置
@@ -2,7 +2,7 @@
<div class="approval-process-selector">
<el-form-item label="审批流程" required>
<el-radio-group v-model="processType" @change="handleProcessTypeChange">
<el-radio label="custom">自定义审批流程</el-radio>
<el-radio value="custom">自定义审批流程</el-radio>
</el-radio-group>
</el-form-item>
+2 -2
View File
@@ -307,11 +307,11 @@
<el-form ref="approvalFormRef" :model="approvalForm" :rules="approvalRules" label-width="120px">
<el-form-item label="审批操作" prop="approveAction" required>
<el-radio-group v-model="approvalForm.approveAction">
<el-radio label="approve">
<el-radio value="approve">
<el-icon><CircleCheck /></el-icon>
审批通过
</el-radio>
<el-radio label="reject">
<el-radio value="reject">
<el-icon><CircleClose /></el-icon>
打回申请
</el-radio>
+8 -38
View File
@@ -21,11 +21,7 @@
<!-- 数据表格 -->
<el-card shadow="never" class="table-card">
<el-table
v-loading="loading"
:data="pendingList"
@row-dblclick="handleView"
>
<el-table v-loading="loading" :data="pendingList" @row-dblclick="handleView">
<el-table-column label="序号" type="index" width="80" align="center" />
<el-table-column label="申报编号" prop="applicationNo" width="160" />
<el-table-column label="项目名称" prop="projectName" show-overflow-tooltip />
@@ -64,13 +60,7 @@
</el-card>
<!-- 审批操作对话框 -->
<el-dialog
title="审批操作"
v-model="approvalDialog.visible"
width="600px"
append-to-body
destroy-on-close
>
<el-dialog title="审批操作" v-model="approvalDialog.visible" width="600px" append-to-body destroy-on-close>
<div class="approval-content">
<!-- 项目信息 -->
<div class="project-info">
@@ -87,25 +77,18 @@
<el-form ref="approvalFormRef" :model="approvalForm" :rules="approvalRules" label-width="100px">
<el-form-item label="审批操作" prop="action">
<el-radio-group v-model="approvalForm.action">
<el-radio label="approve">
<el-radio value="approve">
<el-icon><Check /></el-icon>
审批通过
</el-radio>
<el-radio label="reject">
<el-radio value="reject">
<el-icon><Close /></el-icon>
审批拒绝
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="审批意见" prop="opinion">
<el-input
v-model="approvalForm.opinion"
type="textarea"
:rows="4"
placeholder="请输入审批意见"
maxlength="500"
show-word-limit
/>
<el-input v-model="approvalForm.opinion" type="textarea" :rows="4" placeholder="请输入审批意见" maxlength="500" show-word-limit />
</el-form-item>
</el-form>
</div>
@@ -113,27 +96,15 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="approvalDialog.visible = false">取消</el-button>
<el-button type="primary" @click="submitApproval" :loading="approvalDialog.submitting">
确认审批
</el-button>
<el-button type="primary" @click="submitApproval" :loading="approvalDialog.submitting"> 确认审批 </el-button>
</div>
</template>
</el-dialog>
<!-- 详情查看对话框 -->
<el-dialog
title="项目申报详情"
v-model="detailDialog.visible"
width="1200px"
append-to-body
destroy-on-close
top="20px"
>
<el-dialog title="项目申报详情" v-model="detailDialog.visible" width="1200px" append-to-body destroy-on-close top="20px">
<div v-loading="detailDialog.loading">
<ApplicationSummary
v-if="detailDialog.visible && currentApplicationId"
:applicationId="currentApplicationId"
/>
<ApplicationSummary v-if="detailDialog.visible && currentApplicationId" :applicationId="currentApplicationId" />
</div>
<template #footer>
@@ -283,7 +254,6 @@ const submitApproval = async () => {
// 重新加载列表
getList();
} catch (error) {
ElMessage.error('审批操作失败');
} finally {
@@ -1,16 +1,19 @@
# 项目审批人批量添加功能Bug修复报告
## 🐛 问题描述
用户反馈:批量添加审批人时,选择用户后点击提交,页面提示添加成功,但后台数据库中没有新增数据。
## 🔍 问题分析
### 根本原因
1. **前端使用模拟API函数**:`index.vue` 中第354-378行定义了模拟的API函数,而不是真实的API调用
2. **缺少正确的API导入**:没有导入真实的 `batchAddProjectAuditor` 等API函数
3. **数据格式不匹配**:前端传递的数据格式与后端期望格式存在差异
### 问题代码
```javascript
// 错误:使用模拟函数
const batchAddProjectAuditor = (data: any) => {
@@ -21,18 +24,20 @@ const batchAddProjectAuditor = (data: any) => {
## 🔧 修复方案
### 1. 导入真实API函数
```javascript
import {
listProjectAuditor,
getProjectAuditor,
updateProjectAuditor,
delProjectAuditor,
batchAddProjectAuditor
import {
listProjectAuditor,
getProjectAuditor,
updateProjectAuditor,
delProjectAuditor,
batchAddProjectAuditor
} from '@/api/cw/projectAuditor'
import type { CwProjectAuditorVO, CwProjectAuditorForm, CwProjectAuditorQuery, CwProjectAuditorBatchBo } from '@/api/cw/types'
```
### 2. 修复数据类型定义
```typescript
// 修正数据类型
const auditorList = ref<CwProjectAuditorVO[]>([])
@@ -43,6 +48,7 @@ const editDialog = reactive({
```
### 3. 修复批量添加数据格式
```javascript
const confirmBatchAdd = async () => {
const userIds = batchDialog.selectedUsers.map((user) => user.userId as number)
@@ -50,23 +56,25 @@ const confirmBatchAdd = async () => {
userIds: userIds,
remark: '批量添加'
}
const response = await batchAddProjectAuditor(data)
// ... 处理响应
}
```
### 4. 修复后端数据解析
```javascript
const response = await listProjectAuditor(queryParams.value)
const response = await listProjectAuditor(queryParams.value);
// 后端返回的是TableDataInfo格式
auditorList.value = response.data?.rows || []
total.value = response.data?.total || 0
auditorList.value = response.data?.rows || [];
total.value = response.data?.total || 0;
```
## ✅ 修复内容清单
### 前端修复
- [x] 移除模拟API函数,导入真实API
- [x] 修复TypeScript类型定义
- [x] 修复批量添加数据格式
@@ -75,6 +83,7 @@ total.value = response.data?.total || 0
- [x] 修复按钮事件类型问题
### 后端验证
- [x] 确认后端API接口正常工作
- [x] 确认数据库表结构正确
- [x] 确认权限控制配置
@@ -82,13 +91,16 @@ total.value = response.data?.total || 0
## 🧪 测试验证
### 测试用例
1. **批量添加功能测试**
- 选择部门
- 选择多个用户
- 点击确认添加
- 验证数据库是否成功插入数据
2. **查询列表功能测试**
- 验证API调用正常
- 验证数据格式解析正确
- 验证分页功能
@@ -99,11 +111,13 @@ total.value = response.data?.total || 0
- 测试权限不足情况
### 测试脚本
提供了 `test-api.js` 文件用于功能验证测试。
## 📋 技术规范遵循
### 符合项目规范
- ✅ **前端开发规范**:符合Vue 3.5.13 + TypeScript + Element Plus架构
- ✅ **API接口规范**:使用统一的request工具和错误处理
- ✅ **UI设计规范**:继承后勤管理模块的样式风格和颜色主题
@@ -111,6 +125,7 @@ total.value = response.data?.total || 0
- ✅ **权限控制**:使用@SaCheckPermission注解控制接口权限
### 代码质量
- ✅ **类型安全**:完整的TypeScript类型定义
- ✅ **错误处理**:完善的异常捕获和用户提示
- ✅ **用户体验**:优化的加载状态和反馈信息
@@ -119,12 +134,14 @@ total.value = response.data?.total || 0
## 🚀 部署说明
### 验证步骤
1. 确保后端服务正常运行
2. 检查数据库表 `cw_project_auditor` 是否存在
3. 验证用户权限配置 `cw:projectAuditor:add`
4. 测试前端页面功能
### 预期结果
- 批量添加功能正常工作
- 数据成功写入数据库
- 页面显示准确的成功/失败提示
@@ -132,16 +149,17 @@ total.value = response.data?.total || 0
## 📝 修复日志
| 时间 | 修复内容 | 影响范围 |
|------|----------|----------|
| 2025-01-03 | 修复API调用问题 | 批量添加功能 |
| 2025-01-03 | 修复数据格式问题 | 前后端数据传递 |
| 时间 | 修复内容 | 影响范围 |
| ---------- | ---------------- | ------------------ |
| 2025-01-03 | 修复API调用问题 | 批量添加功能 |
| 2025-01-03 | 修复数据格式问题 | 前后端数据传递 |
| 2025-01-03 | 修复类型定义问题 | TypeScript类型安全 |
| 2025-01-03 | 修复响应解析问题 | 列表查询功能 |
| 2025-01-03 | 修复响应解析问题 | 列表查询功能 |
## 🎯 总结
本次修复解决了批量添加审批人功能的根本问题,确保:
1. **功能正常**:批量添加能够真实地向后端发送请求并保存数据
2. **类型安全**:完整的TypeScript类型定义和检查
3. **用户体验**:准确的成功/失败提示和数据刷新
@@ -7,18 +7,21 @@
## 主要功能
### 1. 审批人列表管理
- 查询审批人列表(支持分页)
- 按用户姓名、部门、状态等条件筛选
- 显示审批人基本信息(姓名、部门、联系方式等)
- 支持审批人状态管理(正常/停用)
### 2. 批量添加审批人
- 通过部门树选择部门
- 展示部门下的可用用户列表
- 支持多选用户批量添加为审批人
- 自动过滤已存在的审批人
### 3. 审批人信息维护
- 修改审批人联系信息
- 更新审批人状态
- 添加备注信息
@@ -35,6 +38,7 @@ web/src/views/cw/projectAuditor/
## API接口
使用的主要API接口:
- `listProjectAuditor` - 查询审批人列表
- `getProjectAuditor` - 获取审批人详情
- `updateProjectAuditor` - 更新审批人信息
@@ -45,6 +49,7 @@ web/src/views/cw/projectAuditor/
## 权限控制
页面功能对应的权限标识:
- `cw:projectAuditor:query` - 查询权限
- `cw:projectAuditor:add` - 新增权限
- `cw:projectAuditor:edit` - 编辑权限
+3 -5
View File
@@ -5,7 +5,7 @@
<el-card>
<template #header>
<span>测试审核人列表API</span>
<el-button @click="testAuditorList" type="primary" style="float: right;">测试API</el-button>
<el-button @click="testAuditorList" type="primary" style="float: right">测试API</el-button>
</template>
<div v-if="loading">加载中...</div>
@@ -32,10 +32,10 @@
</div>
</el-card>
<el-card style="margin-top: 20px;">
<el-card style="margin-top: 20px">
<template #header>
<span>测试部门数据API</span>
<el-button @click="testDeptList" type="success" style="float: right;">测试部门API</el-button>
<el-button @click="testDeptList" type="success" style="float: right">测试部门API</el-button>
</template>
<div v-if="deptLoading">加载中...</div>
@@ -101,7 +101,6 @@ const testAuditorList = async () => {
}
console.log('处理后的审核人列表:', auditorList.value);
} catch (error) {
console.error('测试审核人API失败:', error);
} finally {
@@ -126,7 +125,6 @@ const testDeptList = async () => {
deptList.value = Array.isArray(deptData) ? deptData : [];
console.log('处理后的部门列表:', deptList.value);
} catch (error) {
console.error('测试部门API失败:', error);
} finally {
+206 -137
View File
@@ -83,7 +83,6 @@
</div>
</transition>
<!-- 表格区域 -->
<el-card shadow="never" class="button-card table-card">
<template #header>
@@ -106,7 +105,7 @@
<el-col :span="1.5">
<el-button icon="Download" @click="handleExport" v-hasPermi="['cw:projectAuditor:export']">导出</el-button>
</el-col>
<right-toolbar :show-search="showSearch" @update:show-search="(val) => showSearch = val" @queryTable="getList"></right-toolbar>
<right-toolbar :show-search="showSearch" @update:show-search="(val) => (showSearch = val)" @queryTable="getList"></right-toolbar>
</el-row>
</template>
@@ -166,10 +165,22 @@
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['cw:projectAuditor:remove']"></el-button>
</el-tooltip>
<el-tooltip v-if="scope.row.status === '1'" content="停用" placement="top">
<el-button link type="warning" icon="CircleClose" @click="handleDisable(scope.row)" v-hasPermi="['cw:projectAuditor:edit']"></el-button>
<el-button
link
type="warning"
icon="CircleClose"
@click="handleDisable(scope.row)"
v-hasPermi="['cw:projectAuditor:edit']"
></el-button>
</el-tooltip>
<el-tooltip v-else content="启用" placement="top">
<el-button link type="success" icon="CircleCheck" @click="handleEnable(scope.row)" v-hasPermi="['cw:projectAuditor:edit']"></el-button>
<el-button
link
type="success"
icon="CircleCheck"
@click="handleEnable(scope.row)"
v-hasPermi="['cw:projectAuditor:edit']"
></el-button>
</el-tooltip>
</template>
</el-table-column>
@@ -181,15 +192,15 @@
:total="total"
:page="queryParams.pageNum"
:limit="queryParams.pageSize"
@update:page="(val) => queryParams.pageNum = val"
@update:limit="(val) => queryParams.pageSize = val"
@update:page="(val) => (queryParams.pageNum = val)"
@update:limit="(val) => (queryParams.pageSize = val)"
@pagination="getList"
/>
</div>
</el-card>
<!-- 批量添加审核人对话框 -->
<el-dialog title="批量添加审核人" v-model="batchAddDialog.visible" width="80%" append-to-body>
<el-dialog title="批量添加审核人" v-model="batchAddDialog.visible" width="1200px" append-to-body>
<div class="batch-add-container">
<!-- 左侧部门选择 -->
<div class="dept-select-section">
@@ -221,24 +232,42 @@
<el-card shadow="never" class="user-table-card">
<template #header>
<div class="card-header">
<span>部门用户列表</span>
<el-tag v-if="currentDeptName" type="info" size="small">{{ currentDeptName }}</el-tag>
<div class="header-left">
<span>部门用户列表</span>
<el-tag v-if="currentDeptName" type="info" size="small">{{ currentDeptName }}</el-tag>
<span v-if="deptUserList.length > 0" class="user-count">({{ deptUserList.length }}人)</span>
</div>
<div class="header-right" v-if="deptUserList.length > 0">
<el-input v-model="userSearchText" placeholder="搜索用户" size="small" style="width: 180px" clearable @input="handleUserSearch">
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
</div>
</div>
</template>
<el-table
ref="userTableRef"
v-loading="userLoading"
:data="deptUserList"
:data="filteredUserList"
@selection-change="handleUserSelectionChange"
max-height="400"
style="width: 100%"
table-layout="fixed"
>
<el-table-column type="selection" width="55" />
<el-table-column label="姓名" prop="nickName" width="100" />
<el-table-column label="用户名" prop="userName" width="120" />
<el-table-column label="部门" prop="deptName" min-width="150" show-overflow-tooltip />
<el-table-column label="手机号" prop="phonenumber" width="130" />
<el-table-column label="邮箱" prop="email" min-width="180" show-overflow-tooltip />
<el-table-column label="姓名" prop="nickName" width="100" show-overflow-tooltip />
<el-table-column label="用户名" prop="userName" width="120" show-overflow-tooltip />
<el-table-column label="部门" prop="deptName" width="160" show-overflow-tooltip />
<el-table-column label="手机号" prop="phonenumber" width="130" show-overflow-tooltip />
<el-table-column label="邮箱" prop="email" width="200" show-overflow-tooltip />
<el-table-column label="状态" prop="status" width="80" align="center">
<template #default="{ row }">
<el-tag :type="row.status === '1' ? 'success' : 'danger'" size="small">
{{ row.status === '1' ? '正常' : '停用' }}
</el-tag>
</template>
</el-table-column>
</el-table>
</el-card>
@@ -261,7 +290,7 @@
@close="removeSelectedUser(user)"
class="selected-user-tag"
>
{{ user.nickName }}({{ user.userName }})
{{ user.nickName }}({{ user.userName }}) - {{ user.deptName || '未知部门' }}
</el-tag>
</div>
</el-card>
@@ -271,12 +300,7 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="batchAddDialog.visible = false">取消</el-button>
<el-button
type="primary"
@click="confirmBatchAdd"
:disabled="batchAddDialog.selectedUsers.length === 0"
:loading="batchAddLoading"
>
<el-button type="primary" @click="confirmBatchAdd" :disabled="batchAddDialog.selectedUsers.length === 0" :loading="batchAddLoading">
确定添加 ({{ batchAddDialog.selectedUsers.length }})
</el-button>
</div>
@@ -329,8 +353,8 @@
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="editDialog.form.status">
<el-radio label="1">正常</el-radio>
<el-radio label="0">停用</el-radio>
<el-radio value="1">正常</el-radio>
<el-radio value="0">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
@@ -339,14 +363,7 @@
<el-input v-model="editDialog.form.email" placeholder="请输入邮箱地址" />
</el-form-item>
<el-form-item label="备注说明" prop="remark">
<el-input
v-model="editDialog.form.remark"
type="textarea"
:rows="3"
placeholder="请输入备注说明"
maxlength="500"
show-word-limit
/>
<el-input v-model="editDialog.form.remark" type="textarea" :rows="3" placeholder="请输入备注说明" maxlength="500" show-word-limit />
</el-form-item>
</el-form>
<template #footer>
@@ -360,7 +377,7 @@
</template>
<script setup lang="ts" name="CwProjectAuditor">
import { ref, reactive, onMounted, nextTick, getCurrentInstance } from 'vue';
import { ref, reactive, onMounted, nextTick, getCurrentInstance, computed } from 'vue';
import { ElMessage, ElMessageBox, ElTable, ElTree, FormInstance } from 'element-plus';
import {
listProjectAuditor,
@@ -369,18 +386,20 @@ import {
updateProjectAuditor,
delProjectAuditor,
batchAddProjectAuditor,
// batchAddProjectAuditorWithDetails, // 后端暂不支持此接口
listMyConfigAuditors,
listUserByDeptId,
enableAuditors,
disableAuditors,
exportProjectAuditor,
listUserByDeptId,
type CwProjectAuditorVO,
type CwProjectAuditorForm,
type CwProjectAuditorQuery,
type BatchAddRequest,
type BatchAddDetailRequest,
type DeptUserVO
} from '@/api/cw/projectAuditor';
import { listDept } from '@/api/system/dept/index';
import { deptTreeSelect } from '@/api/system/user/index';
const { proxy } = getCurrentInstance() as any;
@@ -417,7 +436,6 @@ const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
// 部门数据
const deptOptions = ref([]);
const deptTreeData = ref([]);
@@ -433,6 +451,20 @@ const batchAddLoading = ref(false);
const userLoading = ref(false);
const deptUserList = ref<DeptUserVO[]>([]);
const currentDeptName = ref('');
const userSearchText = ref('');
const filteredUserList = computed(() => {
if (!userSearchText.value) {
return deptUserList.value;
}
const searchText = userSearchText.value.toLowerCase();
return deptUserList.value.filter(
(user) =>
user.userName?.toLowerCase().includes(searchText) ||
user.nickName?.toLowerCase().includes(searchText) ||
user.phonenumber?.includes(searchText) ||
user.email?.toLowerCase().includes(searchText)
);
});
// 详情对话框
const detailDialog = reactive({
@@ -450,15 +482,9 @@ const editLoading = ref(false);
// 编辑表单验证规则
const editRules = {
phoneNumber: [
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
],
email: [
{ type: 'email' as const, message: '请输入正确的邮箱地址', trigger: 'blur' }
],
status: [
{ required: true, message: '请选择状态', trigger: 'change' }
]
phoneNumber: [{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }],
email: [{ type: 'email' as const, message: '请输入正确的邮箱地址', trigger: 'blur' }],
status: [{ required: true, message: '请选择状态', trigger: 'change' }]
};
// 查询审核人列表
@@ -500,7 +526,6 @@ const getList = async () => {
console.log('处理后的审核人列表:', auditorList.value);
console.log('总数:', total.value);
} catch (error) {
console.error('获取审核人列表失败:', error);
auditorList.value = [];
@@ -510,7 +535,6 @@ const getList = async () => {
}
};
// 搜索
const handleQuery = () => {
queryParams.pageNum = 1;
@@ -526,7 +550,7 @@ const resetQuery = () => {
// 多选框选中数据
const handleSelectionChange = (selection: CwProjectAuditorVO[]) => {
ids.value = selection.map(item => item.id);
ids.value = selection.map((item) => item.id);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
@@ -542,7 +566,7 @@ const handleUpdate = (row?: CwProjectAuditorVO) => {
if (row) {
editDialog.form = { ...row };
} else if (ids.value.length === 1) {
const selectedRow = auditorList.value.find(item => item.id === ids.value[0]);
const selectedRow = auditorList.value.find((item) => item.id === ids.value[0]);
if (selectedRow) {
editDialog.form = { ...selectedRow };
}
@@ -573,19 +597,13 @@ const submitEdit = async () => {
// 删除审核人
const handleDelete = (row?: CwProjectAuditorVO) => {
const deleteIds = row ? [row.id] : ids.value;
const names = row ? [row.userName] : auditorList.value
.filter((item) => deleteIds.includes(item.id))
.map((item) => item.userName);
const names = row ? [row.userName] : auditorList.value.filter((item) => deleteIds.includes(item.id)).map((item) => item.userName);
ElMessageBox.confirm(
`是否确认删除审核人"${names.join(', ')}"?`,
'警告',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).then(async () => {
ElMessageBox.confirm(`是否确认删除审核人"${names.join(', ')}"?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
await delProjectAuditor(deleteIds);
ElMessage.success('删除成功');
@@ -649,44 +667,28 @@ const handleExport = () => {
const handleBatchAdd = () => {
batchAddDialog.visible = true;
batchAddDialog.selectedUsers = [];
userSearchText.value = '';
currentDeptName.value = '';
deptUserList.value = [];
loadDeptTree();
};
// 加载部门树数据
const loadDeptTree = async () => {
try {
const response = await listDept();
const response = await deptTreeSelect();
console.log('部门选项响应:', response);
let deptData;
if (response.data) {
deptData = response.data;
} else {
deptData = response;
}
const deptData = response.data;
// 验证部门数据结构
if (Array.isArray(deptData)) {
// 检查数据格式并打印前几个节点用于调试
// deptTreeSelect 返回的已经是正确的 DeptTreeVO[] 格式
// 包含 id、label、children 字段,直接使用即可
console.log('部门数据示例:', deptData.slice(0, 3));
// 转换为树形结构,适配 el-tree-select 组件
const treeData = proxy?.handleTree(deptData, 'deptId', 'parentId', 'children', '0') || [];
// 转换数据格式以适配 el-tree-select
const formatTreeData = (nodes: any[]): any[] => {
return nodes.map(node => ({
id: node.deptId,
label: node.deptName,
value: node.deptId,
children: node.children ? formatTreeData(node.children) : []
}));
};
const formattedData = formatTreeData(treeData);
deptOptions.value = formattedData;
deptTreeData.value = formattedData;
deptOptions.value = deptData;
deptTreeData.value = deptData;
} else {
console.warn('部门数据格式错误,期望数组但收到:', typeof deptData);
deptOptions.value = [];
@@ -720,61 +722,70 @@ const handleDeptClick = async (data: any) => {
console.log('部门树节点点击:', data);
console.log('节点ID类型:', typeof data?.id, '值:', data?.id);
// 验证deptId是否为有效的数字
if (data?.id && !isNaN(Number(data.id)) && data.id !== 'treeselect') {
try {
userLoading.value = true;
currentDeptName.value = data.label || data.deptName;
// 验证deptId是否为有效的数字,排除特殊值
const nodeId = data?.id;
if (!nodeId || nodeId === 'treeselect' || nodeId === '') {
console.warn('无效的部门节点ID:', nodeId);
return;
}
// 确保传递数字类型的ID
const deptId = Number(data.id);
console.log('调用API,部门ID:', deptId);
try {
userLoading.value = true;
currentDeptName.value = data.label || data.deptName;
const response = await listUserByDeptId(deptId);
console.log('部门用户数据响应:', response);
// 保持字符串类型的ID,避免大整数精度丢失
const deptId = String(nodeId);
console.log('调用API,部门ID:', deptId, '类型:', typeof deptId);
let userData;
if (response.data) {
userData = response.data;
} else {
userData = response;
}
const response = await listUserByDeptId(deptId);
console.log('部门用户数据响应:', response);
deptUserList.value = Array.isArray(userData) ? userData : [];
console.log('处理后的用户数据:', deptUserList.value);
// 切换部门后,恢复之前选中的用户状态
nextTick(() => {
if (userTableRef.value) {
// 清空选择
userTableRef.value.clearSelection();
// 恢复已选择的用户
deptUserList.value.forEach(user => {
const isSelected = batchAddDialog.selectedUsers.some(selected => selected.userId === user.userId);
if (isSelected) {
userTableRef.value?.toggleRowSelection(user, true);
}
});
}
});
} catch (error) {
console.error('获取部门用户失败:', error);
ElMessage.error('获取部门用户失败,请重试');
deptUserList.value = [];
} finally {
userLoading.value = false;
// 后端返回格式: { code: 200, data: [...], msg: "操作成功" }
let userData = [];
if (response && response.data) {
userData = response.data;
} else if (Array.isArray(response)) {
userData = response;
}
} else {
console.warn('无效的部门节点:', data);
ElMessage.warning('请选择有效的部门节点');
// 处理用户数据,添加缺失的状态字段和部门信息
deptUserList.value = Array.isArray(userData) ? userData.map((user: any) => ({
...user,
status: user.status || '0', // 默认为正常状态
deptId: user.deptId || nodeId, // 确保有部门ID
deptName: user.deptName || currentDeptName.value // 确保有部门名称
})) : [];
console.log('处理后的用户数据:', deptUserList.value);
console.log('用户数据数量:', deptUserList.value.length);
// 切换部门后,恢复之前选中的用户状态
nextTick(() => {
if (userTableRef.value) {
// 清空选择
userTableRef.value.clearSelection();
// 恢复已选择的用户
deptUserList.value.forEach((user) => {
const isSelected = batchAddDialog.selectedUsers.some((selected) => selected.userId === user.userId);
if (isSelected) {
userTableRef.value?.toggleRowSelection(user, true);
}
});
}
});
} catch (error) {
console.error('获取部门用户失败:', error);
ElMessage.error('获取部门用户失败,请重试');
deptUserList.value = [];
} finally {
userLoading.value = false;
}
};
// 用户选择变化
const handleUserSelectionChange = (selection: DeptUserVO[]) => {
// 移除当前部门中未选中的用户
batchAddDialog.selectedUsers = batchAddDialog.selectedUsers.filter(user =>
!deptUserList.value.some(deptUser => deptUser.userId === user.userId)
batchAddDialog.selectedUsers = batchAddDialog.selectedUsers.filter(
(user) => !deptUserList.value.some((deptUser) => deptUser.userId === user.userId)
);
// 添加新选中的用户
@@ -782,7 +793,7 @@ const handleUserSelectionChange = (selection: DeptUserVO[]) => {
// 去重
const userMap = new Map();
batchAddDialog.selectedUsers.forEach(user => {
batchAddDialog.selectedUsers.forEach((user) => {
userMap.set(user.userId, user);
});
batchAddDialog.selectedUsers = Array.from(userMap.values());
@@ -790,14 +801,14 @@ const handleUserSelectionChange = (selection: DeptUserVO[]) => {
// 移除已选择的用户
const removeSelectedUser = (user: DeptUserVO) => {
const index = batchAddDialog.selectedUsers.findIndex(item => item.userId === user.userId);
const index = batchAddDialog.selectedUsers.findIndex((item) => item.userId === user.userId);
if (index > -1) {
batchAddDialog.selectedUsers.splice(index, 1);
}
// 如果该用户在当前显示的部门用户列表中,取消选中状态
if (userTableRef.value && deptUserList.value.some(deptUser => deptUser.userId === user.userId)) {
const tableUser = deptUserList.value.find(deptUser => deptUser.userId === user.userId);
if (userTableRef.value && deptUserList.value.some((deptUser) => deptUser.userId === user.userId)) {
const tableUser = deptUserList.value.find((deptUser) => deptUser.userId === user.userId);
if (tableUser) {
userTableRef.value.toggleRowSelection(tableUser, false);
}
@@ -812,6 +823,22 @@ const clearSelectedUsers = () => {
}
};
// 用户搜索处理
const handleUserSearch = () => {
// 搜索时重新应用选择状态
nextTick(() => {
if (userTableRef.value) {
userTableRef.value.clearSelection();
filteredUserList.value.forEach((user) => {
const isSelected = batchAddDialog.selectedUsers.some((selected) => selected.userId === user.userId);
if (isSelected) {
userTableRef.value?.toggleRowSelection(user, true);
}
});
}
});
};
// 确认批量添加
const confirmBatchAdd = async () => {
if (batchAddDialog.selectedUsers.length === 0) {
@@ -821,19 +848,38 @@ const confirmBatchAdd = async () => {
batchAddLoading.value = true;
try {
const userIds = batchAddDialog.selectedUsers.map(user => user.userId);
// 验证用户数据完整性
const invalidUsers = batchAddDialog.selectedUsers.filter(user =>
!user.userId || !user.userName || !user.deptId || !user.deptName
);
if (invalidUsers.length > 0) {
ElMessage.warning(`发现 ${invalidUsers.length} 个用户信息不完整,请重新选择`);
console.warn('用户信息不完整:', invalidUsers);
return;
}
// 使用标准批量添加API(后端会自动获取用户完整信息)
const userIds = batchAddDialog.selectedUsers.map((user) => user.userId);
const request: BatchAddRequest = {
userIds,
remark: '批量添加的审核人'
};
console.log('批量添加请求数据:', request);
console.log('选中的用户详情:', batchAddDialog.selectedUsers);
await batchAddProjectAuditor(request);
ElMessage.success(`成功添加 ${batchAddDialog.selectedUsers.length} 个审核人`);
batchAddDialog.visible = false;
batchAddDialog.selectedUsers = [];
userSearchText.value = '';
getList();
} catch (error) {
console.error('批量添加审核人失败:', error);
ElMessage.error('批量添加审核人失败,请检查用户信息是否完整');
} finally {
batchAddLoading.value = false;
}
@@ -882,7 +928,6 @@ onMounted(() => {
border-bottom: none;
}
/* 表格样式 */
.scroll-table {
:deep(.el-table__header-wrapper) {
@@ -901,6 +946,8 @@ onMounted(() => {
display: flex;
gap: 16px;
height: 500px;
max-width: 1160px;
margin: 0 auto;
}
.dept-select-section {
@@ -909,7 +956,8 @@ onMounted(() => {
}
.user-select-section {
flex: 1;
width: 840px;
flex-shrink: 0;
display: flex;
flex-direction: column;
gap: 16px;
@@ -923,12 +971,33 @@ onMounted(() => {
.user-table-card {
flex: 1;
.el-table {
width: 100% !important;
table-layout: fixed !important;
}
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
.header-left {
display: flex;
align-items: center;
gap: 8px;
.user-count {
color: #909399;
font-size: 12px;
}
}
.header-right {
display: flex;
align-items: center;
}
}
.selected-users-list {
+17 -15
View File
@@ -17,13 +17,7 @@
<div class="test-section">
<h3>3. 部门选择测试</h3>
<el-tree-select
v-model="selectedDept"
:data="deptOptions"
placeholder="选择部门"
clearable
style="width: 300px"
/>
<el-tree-select v-model="selectedDept" :data="deptOptions" placeholder="选择部门" clearable style="width: 300px" />
<p>选中部门: {{ selectedDept }}</p>
</div>
</div>
@@ -42,14 +36,22 @@ const apiResult = ref('');
// 模拟部门数据
const mockDeptOptions = [
{ value: '1', label: '总公司', children: [
{ value: '2', label: '财务部' },
{ value: '3', label: '人事部' }
]},
{ value: '4', label: '分公司', children: [
{ value: '5', label: '技术部' },
{ value: '6', label: '市场部' }
]}
{
value: '1',
label: '总公司',
children: [
{ value: '2', label: '财务部' },
{ value: '3', label: '人事部' }
]
},
{
value: '4',
label: '分公司',
children: [
{ value: '5', label: '技术部' },
{ value: '6', label: '市场部' }
]
}
];
// 模拟API响应数据
@@ -148,7 +148,7 @@
<!-- 操作按钮行 -->
<el-row :gutter="10" class="mb8">
<el-col :span="24" class="search-btn-container">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -167,10 +167,14 @@
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['jcbt:povertyApplication:add']">新增申请</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['jcbt:povertyApplication:edit']">修改</el-button>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['jcbt:povertyApplication:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['jcbt:povertyApplication:remove']">删除</el-button>
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['jcbt:povertyApplication:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['jcbt:povertyApplication:export']">导出</el-button>
+8 -1
View File
@@ -164,7 +164,14 @@
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Close" :disabled="multiple" @click="handleBatchReject" v-hasPermi="['jcbt:povertyApplication:reject']">
<el-button
type="danger"
plain
icon="Close"
:disabled="multiple"
@click="handleBatchReject"
v-hasPermi="['jcbt:povertyApplication:reject']"
>
批量驳回
</el-button>
</el-col>
@@ -44,12 +44,7 @@
<el-col :span="6">
<el-form-item label="奖学金类型" prop="scholarshipTypeId">
<el-select v-model="queryParams.scholarshipTypeId" placeholder="请选择奖学金类型" clearable style="width: 100%">
<el-option
v-for="type in scholarshipTypeList"
:key="type.id"
:label="type.typeName"
:value="type.id"
/>
<el-option v-for="type in scholarshipTypeList" :key="type.id" :label="type.typeName" :value="type.id" />
</el-select>
</el-form-item>
</el-col>
@@ -68,9 +63,9 @@
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10" class="mb8" >
<el-row :gutter="10" class="mb8">
<el-col :span="24" class="search-btn-container">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -89,14 +84,9 @@
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['jcbt:scholarshipApplication:add']">班主任批量申请</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate()"
v-hasPermi="['jcbt:scholarshipApplication:edit']"
>修改</el-button>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['jcbt:scholarshipApplication:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
@@ -106,7 +96,8 @@
:disabled="multiple"
@click="handleDelete()"
v-hasPermi="['jcbt:scholarshipApplication:remove']"
>删除</el-button>
>删除</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
+12 -40
View File
@@ -76,7 +76,7 @@
</el-row>
<el-row :gutter="10" class="mb8">
<el-col :span="24" class="search-btn-container">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -153,33 +153,17 @@
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['jcbt:scholarshipBatch:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate()"
v-hasPermi="['jcbt:scholarshipBatch:edit']"
>修改</el-button>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['jcbt:scholarshipBatch:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
v-hasPermi="['jcbt:scholarshipBatch:remove']"
>删除</el-button>
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['jcbt:scholarshipBatch:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['jcbt:scholarshipBatch:export']"
>导出</el-button>
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['jcbt:scholarshipBatch:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@@ -254,22 +238,10 @@
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="280" fixed="right">
<template #default="scope">
<el-tooltip content="查看" placement="top">
<el-button
link
type="info"
icon="View"
@click="handleView(scope.row)"
v-hasPermi="['jcbt:scholarshipBatch:query']"
></el-button>
<el-button link type="info" icon="View" @click="handleView(scope.row)" v-hasPermi="['jcbt:scholarshipBatch:query']"></el-button>
</el-tooltip>
<el-tooltip content="修改" placement="top">
<el-button
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['jcbt:scholarshipBatch:edit']"
></el-button>
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['jcbt:scholarshipBatch:edit']"></el-button>
</el-tooltip>
<el-tooltip content="状态变更" placement="top">
<el-button
@@ -772,7 +744,7 @@ async function handleUpdate(row?: ScholarshipBatchVO) {
// 关键修复:不调用reset(),直接赋值数据,确保ID不会被重置为undefined
form.value = {
...res.data,
id: String(_id) // 🔧 修复:确保ID为字符串格式,避免精度丢失
id: String(_id) // 🔧 修复:确保ID为字符串格式,避免精度丢失
};
// 确保scholarshipTypeIds正确赋值
@@ -828,7 +800,7 @@ function submitForm() {
// 不再使用parseInt(),直接使用字符串格式的ID
const updateData = {
...form.value,
id: String(form.value.id) // 确保ID始终为字符串格式
id: String(form.value.id) // 确保ID始终为字符串格式
};
console.log('发送到后端的修改数据:', JSON.stringify(updateData, null, 2));
await updateScholarshipBatch(updateData);
+75 -70
View File
@@ -68,7 +68,7 @@
</el-row>
<el-row :gutter="10" class="mb8">
<el-col :span="24" class="search-btn-container">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -85,45 +85,23 @@
<el-row ref="toolbarRef" :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['jcbt:scholarshipQuota:add']">新增</el-button>
</el-col>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate()"
v-hasPermi="['jcbt:scholarshipQuota:edit']"
>修改</el-button>
</el-col>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['jcbt:scholarshipQuota:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
v-hasPermi="['jcbt:scholarshipQuota:remove']"
>删除</el-button>
</el-col>
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['jcbt:scholarshipQuota:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['jcbt:scholarshipQuota:export']"
>导出</el-button>
</el-col>
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['jcbt:scholarshipQuota:export']">导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="Operation"
@click="handleBatchAllocate"
v-hasPermi="['jcbt:scholarshipQuota:add']"
>批量分配</el-button>
</el-col>
<el-button type="info" plain icon="Operation" @click="handleBatchAllocate" v-hasPermi="['jcbt:scholarshipQuota:add']">批量分配</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
@@ -178,18 +156,45 @@
</el-table-column>
<el-table-column label="操作" width="280" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['jcbt:scholarshipQuota:edit']" :disabled="scope.row.isLocked === 1"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['jcbt:scholarshipQuota:remove']" :disabled="scope.row.isLocked === 1"></el-button>
</el-tooltip>
<el-tooltip content="锁定/解锁" placement="top">
<el-button link :type="scope.row.isLocked === 1 ? 'warning' : 'info'" :icon="scope.row.isLocked === 1 ? 'Unlock' : 'Lock'" @click="handleLock(scope.row)" v-hasPermi="['jcbt:scholarshipQuota:edit']"></el-button>
</el-tooltip>
<el-tooltip content="调整名额" placement="top">
<el-button link type="success" icon="Switch" @click="handleAdjust(scope.row)" v-hasPermi="['jcbt:scholarshipQuota:edit']" :disabled="scope.row.isLocked === 1"></el-button>
</el-tooltip>
<el-tooltip content="修改" placement="top">
<el-button
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['jcbt:scholarshipQuota:edit']"
:disabled="scope.row.isLocked === 1"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button
link
type="danger"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['jcbt:scholarshipQuota:remove']"
:disabled="scope.row.isLocked === 1"
></el-button>
</el-tooltip>
<el-tooltip content="锁定/解锁" placement="top">
<el-button
link
:type="scope.row.isLocked === 1 ? 'warning' : 'info'"
:icon="scope.row.isLocked === 1 ? 'Unlock' : 'Lock'"
@click="handleLock(scope.row)"
v-hasPermi="['jcbt:scholarshipQuota:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="调整名额" placement="top">
<el-button
link
type="success"
icon="Switch"
@click="handleAdjust(scope.row)"
v-hasPermi="['jcbt:scholarshipQuota:edit']"
:disabled="scope.row.isLocked === 1"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
@@ -977,13 +982,13 @@ onMounted(() => {
}
/* 表格卡片布局 */
.table-card {
margin-top: 0;
}
.table-card {
margin-top: 0;
}
.table-card :deep(.el-card__body) {
padding: 0px 20px 20px 20px !important;
}
.table-card :deep(.el-card__body) {
padding: 0px 20px 20px 20px !important;
}
/* 表格容器 - 支持横向滚动 */
.table-container {
@@ -1013,29 +1018,29 @@ onMounted(() => {
}
/* Webkit 浏览器的横向滚动条样式 */
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar {
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar {
height: 12px !important; /* 增加滚动条高度 */
width: 12px !important;
}
}
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-track {
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-track {
background: #f0f0f0 !important;
border-radius: 6px !important;
border: 1px solid #e4e7ed !important; /* 添加边框使其更明显 */
}
}
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb {
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb {
background: #409eff !important; /* 使用主题色,更明显 */
border-radius: 6px !important;
border: 2px solid #f0f0f0 !important; /* 添加白色边框 */
min-height: 20px !important; /* 确保最小高度 */
}
}
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:hover {
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:hover {
background: #337ecc !important; /* 悬停时更深的蓝色 */
}
}
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:active {
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:active {
background: #2b6cb0 !important; /* 点击时的颜色 */
}
@@ -1065,13 +1070,13 @@ onMounted(() => {
}
/* 分页容器样式 */
.pagination-container {
display: flex;
justify-content: flex-end;
padding: 16px 20px 0 0 !important;
background-color: #fff;
margin-top: 0px !important;
}
.pagination-container {
display: flex;
justify-content: flex-end;
padding: 16px 20px 0 0 !important;
background-color: #fff;
margin-top: 0px !important;
}
/* 移除按钮栏底部边框 */
.button-card :deep(.el-card__header) {
@@ -140,7 +140,7 @@
<!-- 操作按钮行 -->
<el-row :gutter="10" class="mb8">
<el-col :span="24" class="search-btn-container">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
+65 -79
View File
@@ -69,9 +69,9 @@
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="10" class="mb8" >
<el-row :gutter="10" class="mb8">
<el-col :span="24" class="search-btn-container">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -88,36 +88,20 @@
<el-row ref="toolbarRef" :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['jcbt:scholarshipType:add']">新增</el-button>
</el-col>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate()"
v-hasPermi="['jcbt:scholarshipType:edit']"
>修改</el-button>
</el-col>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['jcbt:scholarshipType:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
v-hasPermi="['jcbt:scholarshipType:remove']"
>删除</el-button>
</el-col>
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['jcbt:scholarshipType:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['jcbt:scholarshipType:export']"
>导出</el-button>
</el-col>
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['jcbt:scholarshipType:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
@@ -180,15 +164,15 @@
</el-table-column>
<el-table-column label="操作" width="280" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="查看" placement="top">
<el-tooltip content="查看" placement="top">
<el-button link type="info" icon="View" @click="handleView(scope.row)" v-hasPermi="['jcbt:scholarshipType:query']"></el-button>
</el-tooltip>
<el-tooltip content="修改" placement="top">
</el-tooltip>
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['jcbt:scholarshipType:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['jcbt:scholarshipType:remove']"></el-button>
</el-tooltip>
</el-tooltip>
</template>
</el-table-column>
</el-table>
@@ -646,19 +630,21 @@ const getExclusiveRelationText = (scholarshipTypeId: string | number): string =>
}
// 异步获取并缓存
getExclusiveRelationDisplay(scholarshipTypeId).then(response => {
exclusiveRelationCache.value.set(scholarshipTypeId, response.data);
}).catch(error => {
console.warn('获取互斥关系显示文本失败:', error);
exclusiveRelationCache.value.set(scholarshipTypeId, '互斥(无)');
});
getExclusiveRelationDisplay(scholarshipTypeId)
.then((response) => {
exclusiveRelationCache.value.set(scholarshipTypeId, response.data);
})
.catch((error) => {
console.warn('获取互斥关系显示文本失败:', error);
exclusiveRelationCache.value.set(scholarshipTypeId, '互斥(无)');
});
return '加载中...';
};
/** 预加载互斥关系显示文本 */
const preloadExclusiveRelations = async () => {
const customTypes = scholarshipTypeList.value.filter(item => item.compatibilityType === 'CUSTOM');
const customTypes = scholarshipTypeList.value.filter((item) => item.compatibilityType === 'CUSTOM');
for (const type of customTypes) {
if (type.id && !exclusiveRelationCache.value.has(type.id)) {
@@ -967,13 +953,13 @@ onMounted(() => {
}
/* 表格卡片布局 */
.table-card {
margin-top: 0;
}
.table-card {
margin-top: 0;
}
.table-card :deep(.el-card__body) {
padding: 0px 20px 20px 20px !important;
}
.table-card :deep(.el-card__body) {
padding: 0px 20px 20px 20px !important;
}
/* 表格容器 - 支持横向滚动 */
.table-container {
@@ -1003,29 +989,29 @@ onMounted(() => {
}
/* Webkit 浏览器的横向滚动条样式 */
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar {
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar {
height: 12px !important; /* 增加滚动条高度 */
width: 12px !important;
}
}
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-track {
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-track {
background: #f0f0f0 !important;
border-radius: 6px !important;
border: 1px solid #e4e7ed !important; /* 添加边框使其更明显 */
}
}
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb {
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb {
background: #409eff !important; /* 使用主题色,更明显 */
border-radius: 6px !important;
border: 2px solid #f0f0f0 !important; /* 添加白色边框 */
min-height: 20px !important; /* 确保最小高度 */
}
}
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:hover {
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:hover {
background: #337ecc !important; /* 悬停时更深的蓝色 */
}
}
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:active {
.scroll-table :deep(.el-table__body-wrapper)::-webkit-scrollbar-thumb:active {
background: #2b6cb0 !important; /* 点击时的颜色 */
}
@@ -1055,13 +1041,13 @@ onMounted(() => {
}
/* 分页容器样式 */
.pagination-container {
display: flex;
justify-content: flex-end;
padding: 16px 20px 0 0 !important;
background-color: #fff;
margin-top: 0px !important;
}
.pagination-container {
display: flex;
justify-content: flex-end;
padding: 16px 20px 0 0 !important;
background-color: #fff;
margin-top: 0px !important;
}
/* 移除按钮栏底部边框 */
.button-card :deep(.el-card__header) {
@@ -1074,23 +1060,23 @@ onMounted(() => {
color: #e74c3c;
}
/* 对话框样式 */
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
/* 对话框样式 */
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
.dialog-form {
.el-form-item {
margin-bottom: 18px;
}
.dialog-form {
.el-form-item {
margin-bottom: 18px;
}
.form-tip {
font-size: 12px;
color: #909399;
margin-top: 4px;
line-height: 1.4;
.form-tip {
font-size: 12px;
color: #909399;
margin-top: 4px;
line-height: 1.4;
}
}
+16 -6
View File
@@ -227,7 +227,9 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button icon="Download" @click="handleExportApplications" v-hasPermi="['rs:application:export']" style="display: none">导出</el-button>
<el-button icon="Download" @click="handleExportApplications" v-hasPermi="['rs:application:export']" style="display: none"
>导出</el-button
>
</el-col>
<el-col :span="1.5">
<el-button icon="Printer" @click="handlePrintApplications" style="display: none">打印</el-button>
@@ -424,8 +426,16 @@
</div>
</div>
<div class="application-table mt-4">
<el-table v-loading="applicationLoading" :data="applicationList" border size="small" class="compact-table" :row-style="{ height: '32px' }" :cell-style="{ padding: '2px 0' }">
<div class="application-table mt-4">
<el-table
v-loading="applicationLoading"
:data="applicationList"
border
size="small"
class="compact-table"
:row-style="{ height: '32px' }"
:cell-style="{ padding: '2px 0' }"
>
<el-table-column label="应聘者" align="center" prop="applicantName" width="110" />
<el-table-column label="申请状态" align="center" prop="status" width="110">
<template #default="scope">
@@ -1996,7 +2006,7 @@ onMounted(() => {
/* 紧凑表格样式 */
.compact-table {
font-size: 13px;
:deep(.el-table__header) {
th {
padding: 4px 0;
@@ -2004,14 +2014,14 @@ onMounted(() => {
height: 32px;
}
}
:deep(.el-table__body) {
td {
padding: 4px 0;
font-size: 13px;
}
}
:deep(.cell) {
padding: 0 4px;
line-height: 1.3;
+6 -9
View File
@@ -69,9 +69,9 @@
</template>
<!-- 待审批申请表格 -->
<el-table v-loading="pendingLoading" border :data="pendingList" @row-dblclick="handleViewApplication" v-hasPermi="['rs:overseas:list']">
<el-table-column label="申请人" align="center" prop="applicantName" width="100" :show-overflow-tooltip="true" />
<el-table-column label="所属部门" align="center" prop="department" width="140" :show-overflow-tooltip="true" />
<el-table v-loading="pendingLoading" border :data="pendingList" @row-dblclick="handleViewApplication" v-hasPermi="['rs:overseas:list']">
<el-table-column label="申请人" align="center" prop="applicantName" width="100" :show-overflow-tooltip="true" />
<el-table-column label="所属部门" align="center" prop="department" width="140" :show-overflow-tooltip="true" />
<el-table-column label="出国目的" align="center" prop="purposeText" width="100" :show-overflow-tooltip="true" />
<el-table-column label="目的地" align="center" prop="destination" width="120" :show-overflow-tooltip="true" />
<el-table-column label="出国时间" align="center" min-width="220" :show-overflow-tooltip="true">
@@ -198,11 +198,11 @@
</template>
<!-- 审批记录表格 -->
<el-table v-loading="recordLoading" border :data="recordList" @row-dblclick="handleViewRecord" v-hasPermi="['rs:overseas:list']">
<el-table-column label="申请人" align="center" prop="applicantName" width="100" :show-overflow-tooltip="true" />
<el-table v-loading="recordLoading" border :data="recordList" @row-dblclick="handleViewRecord" v-hasPermi="['rs:overseas:list']">
<el-table-column label="申请人" align="center" prop="applicantName" width="100" :show-overflow-tooltip="true" />
<el-table-column label="出国目的" align="center" prop="purposeText" width="100" :show-overflow-tooltip="true" />
<el-table-column label="目的地" align="center" prop="destination" width="120" :show-overflow-tooltip="true" />
<el-table-column label="所属部门" align="center" prop="department" width="140" :show-overflow-tooltip="true" />
<el-table-column label="所属部门" align="center" prop="department" width="140" :show-overflow-tooltip="true" />
<el-table-column label="出国时间" align="center" min-width="220" :show-overflow-tooltip="true">
<template #default="scope"> {{ formatDate(scope.row.startDate) }} 至 {{ formatDate(scope.row.endDate) }} </template>
</el-table-column>
@@ -634,7 +634,6 @@ const resetRecordQuery = () => {
}
};
// 查看申请详情
const handleViewApplication = async (row: OverseasApplicationVO) => {
try {
@@ -821,8 +820,6 @@ const getApprovalStatusTagType = (status: string): 'success' | 'warning' | 'dang
};
return typeMap[status] || 'info';
};
</script>
<style scoped>
+8 -10
View File
@@ -31,7 +31,6 @@
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
@@ -55,7 +54,9 @@
<el-button type="primary" plain icon="Plus" @click="handleAddApplication" v-hasPermi="['rs:overseas:add']">新增申请</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="Download" @click="handleExportApplication" v-hasPermi="['rs:overseas:export']" style="display: none">导出</el-button>
<el-button icon="Download" @click="handleExportApplication" v-hasPermi="['rs:overseas:export']" style="display: none"
>导出</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showApplicationSearch" @queryTable="getApplicationList"></right-toolbar>
</el-row>
@@ -316,12 +317,13 @@
<template #footer>
<div class="dialog-footer">
<el-button
type="primary"
:loading="state.submitLoading"
<el-button
type="primary"
:loading="state.submitLoading"
@click="submitApplicationForm"
v-hasPermi="state.applicationForm.applicationId ? ['rs:overseas:edit'] : ['rs:overseas:add', 'rs:overseas:submit']"
>确 定</el-button>
>确 定</el-button
>
<el-button @click="cancelApplicationForm">取 消</el-button>
</div>
</template>
@@ -543,8 +545,6 @@ const state = reactive({
status: '0'
},
// 字典数据
statusOptions: [
{ label: '待审核', value: '0' },
@@ -723,8 +723,6 @@ const resetRecordQuery = () => {
getRecordList();
};
// 新增申请
const handleAddApplication = () => {
resetApplicationForm();
+7 -5
View File
@@ -64,7 +64,9 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['rs:overseas:export']" style="display: none">导出</el-button>
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['rs:overseas:export']" style="display: none"
>导出</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@@ -534,7 +536,7 @@ function resetQuery() {
} catch (error) {
console.error('重置查询条件时发生错误:', error);
// 即使出错也要重新查询
handleQuery();
handleQuery();
}
}
@@ -565,7 +567,7 @@ function resetRecordQuery() {
} catch (error) {
console.error('重置查询条件时发生错误:', error);
// 即使出错也要重新查询
handleRecordQuery();
handleRecordQuery();
}
}
@@ -663,14 +665,14 @@ function submitForm() {
/** 删除按钮操作 */
function handleDelete(row) {
const _approvalIds = row.approvalId || ids.value;
let confirmMessage = '';
if (row) {
confirmMessage = `是否确认删除申请人"${row.applicantName}"前往"${row.destination}"的审批记录?`;
} else {
confirmMessage = `是否确认删除选中的${ids.value.length}条审批记录?`;
}
proxy.$modal
.confirm(confirmMessage)
.then(function () {
+23 -23
View File
@@ -420,13 +420,13 @@ onMounted(() => {
.el-breadcrumb {
margin: 0 auto;
padding: 0 30px;
:deep(.el-breadcrumb__item) {
.el-breadcrumb__inner {
color: #666;
font-weight: 400;
}
&:last-child .el-breadcrumb__inner {
color: #2d5a3d;
font-weight: 500;
@@ -488,10 +488,10 @@ onMounted(() => {
font-size: 1rem;
position: relative;
border: none;
// 默认状态 - 普通导航项
background: #004e48;
&:hover {
background: #006157;
}
@@ -501,7 +501,7 @@ onMounted(() => {
background: #003a37;
position: relative;
z-index: 2;
// 创建右箭头效果
&::after {
content: '';
@@ -516,7 +516,7 @@ onMounted(() => {
border-bottom: 15px solid transparent;
z-index: 3;
}
// 箭头阴影效果
&::before {
content: '';
@@ -539,7 +539,7 @@ onMounted(() => {
color: #ffffff;
transition: color 0.2s ease;
}
span {
font-size: 1rem;
letter-spacing: 0.5px;
@@ -558,18 +558,18 @@ onMounted(() => {
border-radius: 8px;
border: 1px solid #e8e8e8;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
:deep(.el-card__header) {
background: #fafafa;
border-bottom: 1px solid #e8e8e8;
padding: 16px 20px;
}
:deep(.el-card__body) {
padding: 20px;
}
}
.list-header {
display: flex;
justify-content: space-between;
@@ -596,19 +596,19 @@ onMounted(() => {
font-weight: 500;
}
}
.view-options {
:deep(.el-radio-group) {
.el-radio-button__inner {
background: #fff;
border-color: #2d5a3d;
color: #2d5a3d;
&:hover {
color: #2d5a3d;
}
}
.el-radio-button__original-radio:checked + .el-radio-button__inner {
background: #2d5a3d;
border-color: #2d5a3d;
@@ -622,21 +622,21 @@ onMounted(() => {
:deep(.el-table) {
.el-table__header {
background: #fafafa;
th {
background: #fafafa !important;
color: #333;
font-weight: 500;
}
}
.el-table__row {
&:hover {
background: #f8f9fa;
}
}
}
.job-title-link {
font-weight: 500;
color: #2d5a3d;
@@ -672,12 +672,12 @@ onMounted(() => {
.el-button {
background: #2d5a3d;
border-color: #2d5a3d;
&:hover {
background: #4a7c59;
border-color: #4a7c59;
}
&:disabled {
background: #ccc;
border-color: #ccc;
@@ -692,11 +692,11 @@ onMounted(() => {
// 对话框样式优化
:deep(.el-dialog) {
border-radius: 8px;
.el-dialog__header {
background: #fafafa;
border-bottom: 1px solid #e8e8e8;
.el-dialog__title {
color: #2d5a3d;
font-weight: 600;
@@ -723,7 +723,7 @@ onMounted(() => {
color: #666;
font-size: 0.9rem;
}
.el-tag {
&.el-tag--success {
background: #e8f5e8;
@@ -771,11 +771,11 @@ onMounted(() => {
.el-button {
margin-left: 10px;
&.el-button--primary {
background: #2d5a3d;
border-color: #2d5a3d;
&:hover {
background: #4a7c59;
border-color: #4a7c59;
File diff suppressed because it is too large. Load diff
+15 -7
View File
@@ -57,7 +57,13 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button icon="Download" @click="handleExportTrainingApplication" v-hasPermi="['rs:trainingapproval:export']" style="display: none">导出</el-button>
<el-button
icon="Download"
@click="handleExportTrainingApplication"
v-hasPermi="['rs:trainingapproval:export']"
style="display: none"
>导出</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showApplicationSearch" @queryTable="getApplicationList"></right-toolbar>
</el-row>
@@ -120,7 +126,7 @@
</el-tab-pane>
<!-- 审批记录选项卡 -->
<el-tab-pane label="审批记录" name="approvalRecord" v-hasPermi="['rs:trainingapproval:query']">
<el-tab-pane label="审批记录" name="approvalRecord" v-hasPermi="['rs:trainingapproval:query']">
<!-- 搜索区域 -->
<transition :enter-active-class="animate.searchAnimate.enter" :leave-active-class="animate.searchAnimate.leave">
<div v-show="showApprovalRecordSearch" class="mb-[10px]">
@@ -167,7 +173,9 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button icon="Download" @click="handleExportApprovalRecord" v-hasPermi="['rs:trainingapproval:export']" style="display: none">导出</el-button>
<el-button icon="Download" @click="handleExportApprovalRecord" v-hasPermi="['rs:trainingapproval:export']" style="display: none"
>导出</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showApprovalRecordSearch" @queryTable="getApprovalRecordList"></right-toolbar>
</el-row>
@@ -704,12 +712,12 @@ const getApplicationList = async () => {
// 排除已通过状态的数据,只显示待审核、已拒绝、已取消等状态
excludeStatus: '1' // 1表示已通过状态
};
const response = await listTrainingApplication(queryParams);
// 前端二次过滤,确保不显示已通过的数据
const filteredApplications = response.rows.filter(app => app.status !== '1');
const filteredApplications = response.rows.filter((app) => app.status !== '1');
applicationList.value = filteredApplications;
applicationTotal.value = filteredApplications.length;
} catch (error) {
@@ -38,9 +38,15 @@
<!-- 主办方信息区域 -->
<el-divider content-position="left">主办方信息</el-divider>
<el-descriptions :column="3" border class="organizer-info-section custom-width-organizer">
<el-descriptions-item label="主办方名称" :label-style="{ width: '13%' }" :content-style="{ width: '35%' }">{{ activity.organizerName || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="联系人" :label-style="{ width: '10%' }" :content-style="{ width: '15%' }">{{ activity.organizerContact || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="联系电话" :label-style="{ width: '10%' }" :content-style="{ width: '17%' }">{{ activity.organizerPhone || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="主办方名称" :label-style="{ width: '13%' }" :content-style="{ width: '35%' }">{{
activity.organizerName || '暂无'
}}</el-descriptions-item>
<el-descriptions-item label="联系人" :label-style="{ width: '10%' }" :content-style="{ width: '15%' }">{{
activity.organizerContact || '暂无'
}}</el-descriptions-item>
<el-descriptions-item label="联系电话" :label-style="{ width: '10%' }" :content-style="{ width: '17%' }">{{
activity.organizerPhone || '暂无'
}}</el-descriptions-item>
</el-descriptions>
<!-- 承办方信息区域 -->
@@ -48,9 +54,15 @@
<div v-if="coOrganizerList.length > 0">
<div v-for="(organizer, index) in coOrganizerList" :key="index" class="co-organizer-item custom-width-co-organizer">
<el-descriptions :column="3" border>
<el-descriptions-item label="承办方名称" :label-style="{ width: '12%' }" :content-style="{ width: '36%' }">{{ organizer.name || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="联系人" :label-style="{ width: '10%' }" :content-style="{ width: '16%' }">{{ organizer.contact || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="联系电话" :label-style="{ width: '10%' }" :content-style="{ width: '16%' }">{{ organizer.phone || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="承办方名称" :label-style="{ width: '12%' }" :content-style="{ width: '36%' }">{{
organizer.name || '暂无'
}}</el-descriptions-item>
<el-descriptions-item label="联系人" :label-style="{ width: '10%' }" :content-style="{ width: '16%' }">{{
organizer.contact || '暂无'
}}</el-descriptions-item>
<el-descriptions-item label="联系电话" :label-style="{ width: '10%' }" :content-style="{ width: '16%' }">{{
organizer.phone || '暂无'
}}</el-descriptions-item>
</el-descriptions>
</div>
</div>
@@ -61,10 +73,18 @@
<div v-if="instructorList.length > 0">
<div v-for="(instructor, index) in instructorList" :key="index" class="instructor-item custom-width-instructor">
<el-descriptions :column="4" border size="small">
<el-descriptions-item label="姓名" :label-style="{ width: '8%' }" :content-style="{ width: '15%' }">{{ instructor.name || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="部门" :label-style="{ width: '8%' }" :content-style="{ width: '17%' }">{{ instructor.dept || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="电话" :label-style="{ width: '8%' }" :content-style="{ width: '15%' }">{{ instructor.phone || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="邮箱" :label-style="{ width: '8%' }" :content-style="{ width: '17%' }">{{ instructor.email || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="姓名" :label-style="{ width: '8%' }" :content-style="{ width: '15%' }">{{
instructor.name || '暂无'
}}</el-descriptions-item>
<el-descriptions-item label="部门" :label-style="{ width: '8%' }" :content-style="{ width: '17%' }">{{
instructor.dept || '暂无'
}}</el-descriptions-item>
<el-descriptions-item label="电话" :label-style="{ width: '8%' }" :content-style="{ width: '15%' }">{{
instructor.phone || '暂无'
}}</el-descriptions-item>
<el-descriptions-item label="邮箱" :label-style="{ width: '8%' }" :content-style="{ width: '17%' }">{{
instructor.email || '暂无'
}}</el-descriptions-item>
</el-descriptions>
</div>
</div>
@@ -203,11 +223,16 @@ const props = defineProps<Props>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { stgl_activity_type, stgl_activity_status, stgl_registration_type, stgl_registration_status, stgl_activity_range, sys_yes_no } = toRefs<any>(
proxy?.useDict('stgl_activity_type', 'stgl_activity_status', 'stgl_registration_type', 'stgl_registration_status', 'stgl_activity_range', 'sys_yes_no')
proxy?.useDict(
'stgl_activity_type',
'stgl_activity_status',
'stgl_registration_type',
'stgl_registration_status',
'stgl_activity_range',
'sys_yes_no'
)
);
// 当前激活的Tab页签
const activeTab = ref('basic');
@@ -585,25 +610,25 @@ onMounted(() => {
.el-descriptions__header {
margin-bottom: 20px;
}
.el-descriptions__title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.el-descriptions__body {
.el-descriptions__table {
.el-descriptions__cell {
padding: 12px 16px;
}
.el-descriptions__label {
font-weight: 500;
color: #606266;
background-color: #fafafa;
}
.el-descriptions__content {
color: #303133;
}
@@ -624,7 +649,7 @@ onMounted(() => {
.activity-detail-view {
padding: 12px;
}
:deep(.el-descriptions) {
.el-descriptions__body {
.el-descriptions__table {
@@ -634,7 +659,7 @@ onMounted(() => {
}
}
}
.co-organizer-item,
.instructor-item {
padding: 12px;
@@ -3,24 +3,15 @@
<el-tabs v-model="activeTab" type="border-card" @tab-change="handleTabChange">
<!-- 已报名标签页 -->
<el-tab-pane label="已报名" name="registered">
<RegisteredMembersTab
:activity-id="activityId"
:activity-name="activityName"
ref="registeredTabRef"
/>
<RegisteredMembersTab :activity-id="activityId" :activity-name="activityName" ref="registeredTabRef" />
</el-tab-pane>
<!-- 待审核标签页 -->
<el-tab-pane label="待审核" name="pending">
<PendingMembersTab
:activity-id="activityId"
:activity-name="activityName"
ref="pendingTabRef"
@refresh-other-tabs="handleRefreshOtherTabs"
/>
<PendingMembersTab :activity-id="activityId" :activity-name="activityName" ref="pendingTabRef" @refresh-other-tabs="handleRefreshOtherTabs" />
</el-tab-pane>
</el-tabs>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">关 闭</el-button>
@@ -57,18 +48,21 @@ const handleVisibleChange = (value: boolean) => {
};
// 监听弹窗显示状态,当弹窗打开时刷新数据
watch(() => props.visible, (newVal) => {
if (newVal) {
// 弹窗打开时刷新当前标签页数据
nextTick(() => {
if (activeTab.value === 'registered' && registeredTabRef.value) {
registeredTabRef.value.getList();
} else if (activeTab.value === 'pending' && pendingTabRef.value) {
pendingTabRef.value.getList();
}
});
watch(
() => props.visible,
(newVal) => {
if (newVal) {
// 弹窗打开时刷新当前标签页数据
nextTick(() => {
if (activeTab.value === 'registered' && registeredTabRef.value) {
registeredTabRef.value.getList();
} else if (activeTab.value === 'pending' && pendingTabRef.value) {
pendingTabRef.value.getList();
}
});
}
}
});
);
// 标签页切换时刷新数据
const handleTabChange = (tabName: string) => {
@@ -127,4 +121,4 @@ defineExpose({
:deep(.el-tabs__content) {
padding: 20px 0;
}
</style>
</style>
@@ -6,7 +6,7 @@
<div v-else>
<!-- 移除统计信息部分 -->
<el-table :data="ratings" height="600" border stripe>
<!-- 基本信息 - 减少宽度 -->
<el-table-column label="评分人信息" width="160" fixed="left">
@@ -18,7 +18,7 @@
</div>
</template>
</el-table-column>
<!-- 所属信息 - 减少宽度 -->
<el-table-column label="所属信息" width="140">
<template #default="scope">
@@ -117,19 +117,23 @@ const averageRating = computed(() => {
// 计算最高评分
const maxRating = computed(() => {
if (ratings.value.length === 0) return 0;
return Math.max(...ratings.value.map((rating) => {
const score = typeof rating.ratingScore === 'string' ? parseFloat(rating.ratingScore) : rating.ratingScore;
return isNaN(score) ? 0 : score;
}));
return Math.max(
...ratings.value.map((rating) => {
const score = typeof rating.ratingScore === 'string' ? parseFloat(rating.ratingScore) : rating.ratingScore;
return isNaN(score) ? 0 : score;
})
);
});
// 计算最低评分
const minRating = computed(() => {
if (ratings.value.length === 0) return 0;
return Math.min(...ratings.value.map((rating) => {
const score = typeof rating.ratingScore === 'string' ? parseFloat(rating.ratingScore) : rating.ratingScore;
return isNaN(score) ? 0 : score;
}));
return Math.min(
...ratings.value.map((rating) => {
const score = typeof rating.ratingScore === 'string' ? parseFloat(rating.ratingScore) : rating.ratingScore;
return isNaN(score) ? 0 : score;
})
);
});
// 简化的日期时间格式化
@@ -171,12 +175,16 @@ const loadRatings = async () => {
};
// 监听 activityId 变化,自动重新加载评分数据
watch(() => props.activityId, (newId, oldId) => {
console.log('活动ID发生变化:', oldId, '->', newId);
if (newId && newId !== oldId) {
loadRatings();
}
}, { immediate: true }); // immediate: true 确保组件挂载时也会执行一次
watch(
() => props.activityId,
(newId, oldId) => {
console.log('活动ID发生变化:', oldId, '->', newId);
if (newId && newId !== oldId) {
loadRatings();
}
},
{ immediate: true }
); // immediate: true 确保组件挂载时也会执行一次
// 移除 onMounted,因为 watch 的 immediate: true 已经覆盖了这个功能
</script>
@@ -3,26 +3,26 @@
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="always" style="padding: 8px 16px;">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0;">
<el-card shadow="always" style="padding: 8px 16px">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0">
<el-row :gutter="16" align="middle">
<el-col :span="5">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0;">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0">
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0;">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="联系方式" prop="phone" style="margin-bottom: 0;">
<el-form-item label="联系方式" prop="phone" style="margin-bottom: 0">
<el-input v-model="queryParams.phone" placeholder="请输入联系方式" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -94,13 +94,7 @@
</el-table-column>
<el-table-column label="签到时间" align="center" width="120">
<template #default="scope">
<el-button
v-if="scope.row.latestSignInTime"
link
type="primary"
@click="showSignRecords(scope.row)"
class="time-button"
>
<el-button v-if="scope.row.latestSignInTime" link type="primary" @click="showSignRecords(scope.row)" class="time-button">
{{ formatSignTime(scope.row.latestSignInTime) }}
</el-button>
<span v-else class="text-gray-400">未签到</span>
@@ -108,13 +102,7 @@
</el-table-column>
<el-table-column label="签退时间" align="center" width="120">
<template #default="scope">
<el-button
v-if="scope.row.latestSignOutTime"
link
type="primary"
@click="showSignRecords(scope.row)"
class="time-button"
>
<el-button v-if="scope.row.latestSignOutTime" link type="primary" @click="showSignRecords(scope.row)" class="time-button">
{{ formatSignTime(scope.row.latestSignOutTime) }}
</el-button>
<span v-else class="text-gray-400">未签退</span>
@@ -309,7 +297,7 @@
是否更新已经存在的报名数据
</div>
<span>仅允许导入xls、xlsx格式文件。</span>
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
</div>
</template>
</el-upload>
@@ -493,15 +481,15 @@ const getList = async () => {
try {
// 构建查询参数,查询已审核通过的报名记录
const queryData = { ...queryParams.value };
// 如果状态字段为空或未选择,则查询已通过(approvalStatus=1)的报名记录
if (!queryData.approvalStatus) {
queryData.approvalStatus = 1;
}
console.log('ActivityRegistrationView: getList 开始查询,参数:', queryData);
console.log('ActivityRegistrationView: props.activityId:', props.activityId);
const res = await listActivityRegister(queryData);
console.log('ActivityRegistrationView: 查询结果完整对象:', res);
console.log('ActivityRegistrationView: res 的类型:', typeof res);
@@ -510,7 +498,7 @@ const getList = async () => {
console.log('ActivityRegistrationView: res.rows 的类型:', typeof res.rows);
console.log('ActivityRegistrationView: res.total:', res.total);
console.log('ActivityRegistrationView: res 的所有属性:', Object.keys(res || {}));
// 检查 res.rows 是否存在且为数组
if (!res || !res.rows) {
console.error('ActivityRegistrationView: API返回数据异常 - res.rows 不存在');
@@ -520,7 +508,7 @@ const getList = async () => {
total.value = 0;
return;
}
if (!Array.isArray(res.rows)) {
console.error('ActivityRegistrationView: API返回数据异常 - res.rows 不是数组');
console.error('ActivityRegistrationView: res.rows 实际值:', res.rows);
@@ -530,9 +518,9 @@ const getList = async () => {
total.value = 0;
return;
}
console.log('ActivityRegistrationView: res.rows 数组长度:', res.rows.length);
// 为每个成员添加签到信息
const membersWithSignInfo = await Promise.all(
res.rows.map(async (member) => {
@@ -546,17 +534,17 @@ const getList = async () => {
studentNo: member.studentNo
}
});
if (signRes.code === 200 && signRes.data && signRes.data.length > 0) {
// 获取最新的签到和签退时间
const latestSignIn = signRes.data
.filter((record: any) => record.signInTime)
.sort((a: any, b: any) => new Date(b.signInTime).getTime() - new Date(a.signInTime).getTime())[0];
const latestSignOut = signRes.data
.filter((record: any) => record.signOutTime)
.sort((a: any, b: any) => new Date(b.signOutTime).getTime() - new Date(a.signOutTime).getTime())[0];
return {
...member,
latestSignInTime: latestSignIn?.signInTime,
@@ -566,7 +554,7 @@ const getList = async () => {
} catch (error) {
console.warn('获取签到信息失败:', error);
}
return {
...member,
latestSignInTime: undefined,
@@ -574,7 +562,7 @@ const getList = async () => {
};
})
);
memberList.value = membersWithSignInfo;
total.value = res.total;
} catch (error: any) {
@@ -586,7 +574,7 @@ const getList = async () => {
request: error?.request,
config: error?.config
});
// 检查是否是网络错误
if (error?.code === 'NETWORK_ERROR' || error?.code === 'ECONNABORTED') {
proxy?.$modal.msgError('网络连接失败,请检查网络状态');
@@ -603,7 +591,7 @@ const getList = async () => {
// 其他错误(如代码逻辑错误)
proxy?.$modal.msgError(`查询成员列表失败: ${error?.message || '未知错误'}`);
}
// 确保在错误情况下清空数据
memberList.value = [];
total.value = 0;
@@ -617,7 +605,7 @@ const showSignRecords = async (member: ExtendedActivityRegisterVO) => {
signRecordsDialog.visible = true;
signRecordsDialog.title = `${member.studentName}(${member.studentNo})的签到记录`;
signRecordsLoading.value = true;
try {
const res = await request({
url: '/stgl/activityAttendance/getAllByActivityAndUser',
@@ -627,7 +615,7 @@ const showSignRecords = async (member: ExtendedActivityRegisterVO) => {
studentNo: member.studentNo
}
});
if (res.code === 200) {
signRecords.value = res.data || [];
} else {
@@ -708,7 +696,7 @@ const handleUpdate = async (row?: ExtendedActivityRegisterVO) => {
const _id = row?.id || ids.value[0];
const res = await getActivityRegister(_id);
Object.assign(form.value, res.data);
// 设置学生选择器数据
if (res.data.studentName) {
form.value.selectedStudent = {
@@ -722,7 +710,7 @@ const handleUpdate = async (row?: ExtendedActivityRegisterVO) => {
className: res.data.className || ''
} as StudentVO;
}
dialog.visible = true;
dialog.title = '修改活动报名';
};
@@ -732,14 +720,14 @@ const submitForm = () => {
memberFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
// 创建提交数据,排除selectedStudent字段
const { selectedStudent, ...submitData } = form.value;
// 确保包含必要字段
submitData.activityId = props.activityId;
submitData.approvalStatus = 1; // 新增的报名默认为已通过状态
try {
if (form.value.id) {
await updateActivityRegister(submitData as ActivityRegisterForm);
@@ -762,14 +750,14 @@ const submitForm = () => {
const handleDelete = async (row?: ExtendedActivityRegisterVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除选中的活动报名记录?');
try {
if (Array.isArray(_ids)) {
await delActivityRegister(_ids.join(','));
} else {
await delActivityRegister(_ids);
}
proxy?.$modal.msgSuccess('删除成功');
await getList();
} catch (error) {
@@ -911,4 +899,4 @@ onMounted(() => {
.text-gray-400 {
color: #909399;
}
</style>
</style>
@@ -3,21 +3,21 @@
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="always" style="padding: 8px 16px;">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0;">
<el-card shadow="always" style="padding: 8px 16px">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0">
<el-row :gutter="16" align="middle">
<el-col :span="8">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0;">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0">
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0;">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -33,10 +33,14 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="success" plain icon="Check" :disabled="single" @click="handleApprove()" v-hasPermi="['stgl:activityRegister:approve']">批量通过</el-button>
<el-button type="success" plain icon="Check" :disabled="single" @click="handleApprove()" v-hasPermi="['stgl:activityRegister:approve']"
>批量通过</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Close" :disabled="single" @click="handleReject()" v-hasPermi="['stgl:activityRegister:approve']">批量拒绝</el-button>
<el-button type="danger" plain icon="Close" :disabled="single" @click="handleReject()" v-hasPermi="['stgl:activityRegister:approve']"
>批量拒绝</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
@@ -86,9 +90,15 @@
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['stgl:activityRegister:query']"></el-button>
</el-tooltip>
<el-tooltip content="通过" placement="top">
<el-button link type="success" icon="Check" @click="handleApprove(scope.row)" v-hasPermi="['stgl:activityRegister:approve']"></el-button>
</el-tooltip>
<el-tooltip content="拒绝" placement="top">
<el-button
link
type="success"
icon="Check"
@click="handleApprove(scope.row)"
v-hasPermi="['stgl:activityRegister:approve']"
></el-button>
</el-tooltip>
<el-tooltip content="拒绝" placement="top">
<el-button link type="danger" icon="Close" @click="handleReject(scope.row)" v-hasPermi="['stgl:activityRegister:approve']"></el-button>
</el-tooltip>
</template>
@@ -123,7 +133,7 @@
<el-divider content-position="left">
<span class="text-lg font-medium">团队信息</span>
</el-divider>
<el-descriptions :column="2" border class="mb-4">
<el-descriptions-item label="团队名称">{{ teamInfo.teamName }}</el-descriptions-item>
<el-descriptions-item label="团队人数">{{ teamInfo.teamSize || 0 }}人</el-descriptions-item>
@@ -160,7 +170,7 @@
</el-table>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelView">关 闭</el-button>
@@ -178,9 +188,9 @@
</el-radio-group>
</el-form-item>
<el-form-item label="审核意见" prop="approvalRemark">
<el-input
v-model="approvalForm.approvalRemark"
type="textarea"
<el-input
v-model="approvalForm.approvalRemark"
type="textarea"
:rows="4"
:placeholder="approvalForm.approvalStatus === 1 ? '请输入通过意见(可选)' : '请输入拒绝原因(必填)'"
/>
@@ -299,9 +309,9 @@ const approvalForm = reactive({
const approvalRules = reactive({
approvalStatus: [{ required: true, message: '请选择审核结果', trigger: 'change' }],
approvalRemark: [
{
required: true,
message: '请输入拒绝原因',
{
required: true,
message: '请输入拒绝原因',
trigger: 'blur',
validator: (rule: any, value: any, callback: any) => {
if (approvalForm.approvalStatus === 2 && !value) {
@@ -332,15 +342,15 @@ const { queryParams } = toRefs(data);
/** 查询成员列表 */
const getList = async () => {
loading.value = true;
try {
const queryData = { ...queryParams.value };
// 确保查询待审核状态(approvalStatus='0')
queryData.approvalStatus = '0';
const res = await listActivityRegister(queryData);
memberList.value = res.rows;
total.value = res.total;
} catch (error) {
@@ -385,7 +395,7 @@ const handleApprove = (row?: ExtendedStglActivityRegistrationVo) => {
proxy?.$modal.msgError('请选择要审核的记录');
return;
}
approvalForm.ids = _ids;
approvalForm.approvalStatus = 1;
approvalForm.approvalRemark = '';
@@ -400,7 +410,7 @@ const handleReject = (row?: ExtendedStglActivityRegistrationVo) => {
proxy?.$modal.msgError('请选择要审核的记录');
return;
}
approvalForm.ids = _ids;
approvalForm.approvalStatus = 2;
approvalForm.approvalRemark = '';
@@ -413,29 +423,21 @@ const submitApproval = () => {
approvalFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
try {
if (approvalForm.ids.length === 1) {
// 单个审核
await approveActivityRegister(
approvalForm.ids[0],
approvalForm.approvalStatus,
approvalForm.approvalRemark
);
await approveActivityRegister(approvalForm.ids[0], approvalForm.approvalStatus, approvalForm.approvalRemark);
} else {
// 批量审核
await batchApproveActivityRegister(
approvalForm.ids,
approvalForm.approvalStatus,
approvalForm.approvalRemark
);
await batchApproveActivityRegister(approvalForm.ids, approvalForm.approvalStatus, approvalForm.approvalRemark);
}
const actionText = approvalForm.approvalStatus === 1 ? '通过' : '拒绝';
proxy?.$modal.msgSuccess(`批量${actionText}成功`);
approvalDialog.visible = false;
await getList();
// 通知父组件刷新其他标签页
notifyRefreshOtherTabs();
} catch (error) {
@@ -457,14 +459,14 @@ const cancelApproval = () => {
const handleDelete = async (row?: ExtendedStglActivityRegistrationVo) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除选中的活动报名记录?');
try {
if (Array.isArray(_ids)) {
await delActivityRegister(_ids.join(','));
} else {
await delActivityRegister(_ids);
}
proxy?.$modal.msgSuccess('删除成功');
await getList();
} catch (error) {
@@ -478,7 +480,7 @@ const handleView = async (row: ExtendedStglActivityRegistrationVo) => {
Object.assign(viewForm, res.data);
viewDialog.visible = true;
viewDialog.title = '查看活动报名详情';
// 如果是团队报名,获取团队信息
if (Number(viewForm.registrationType) === 2 && viewForm.teamId) {
await loadTeamInfo(viewForm.teamId);
@@ -494,39 +496,37 @@ const loadTeamInfo = async (teamId: string | number) => {
if (!teamId) {
return;
}
loadingTeamInfo.value = true;
try {
// 尝试调用团队信息API获取完整信息
try {
const response = await getTeamsByActivityId(viewForm.activityId);
console.log('PendingMembersTab: 团队信息API响应:', response);
if (response.data && Array.isArray(response.data)) {
// 从团队列表中查找当前团队
const teamData = response.data.find((team: any) => {
return team.id == teamId || team.id.toString() === teamId || team.id === parseInt(teamId as string);
});
console.log('PendingMembersTab: 找到的团队数据:', teamData);
if (teamData) {
// 优先从teamMembers中查找队长信息
let leaderPhone = '暂无联系方式';
let leaderDepartment = '暂无院系信息';
let leaderMajor = '';
let leaderClassName = '';
try {
if (teamData.teamMembers) {
const membersData = JSON.parse(teamData.teamMembers);
if (Array.isArray(membersData)) {
// 查找队长信息(isLeader: 1 或者 userId 匹配 leaderUserId)
const leaderMember = membersData.find((member: any) =>
member.isLeader === 1 || member.userId === teamData.leaderUserId
);
const leaderMember = membersData.find((member: any) => member.isLeader === 1 || member.userId === teamData.leaderUserId);
if (leaderMember) {
leaderPhone = leaderMember.phone || '暂无联系方式';
leaderDepartment = leaderMember.department || '暂无院系信息';
@@ -539,7 +539,7 @@ const loadTeamInfo = async (teamId: string | number) => {
} catch (parseError) {
console.error('PendingMembersTab: 解析团队成员JSON失败:', parseError);
}
// 如果从teamMembers中没有找到队长信息,尝试通过leaderUserId获取
if (leaderPhone === '暂无联系方式' || leaderDepartment === '暂无院系信息') {
try {
@@ -547,7 +547,7 @@ const loadTeamInfo = async (teamId: string | number) => {
console.log('PendingMembersTab: 通过leaderUserId获取队长详细信息:', teamData.leaderUserId);
const studentResponse = await getStudent(teamData.leaderUserId);
console.log('PendingMembersTab: getStudent API响应:', studentResponse);
if (studentResponse.data) {
const studentData = studentResponse.data;
console.log('PendingMembersTab: 学生数据详情:', studentData);
@@ -557,7 +557,7 @@ const loadTeamInfo = async (teamId: string | number) => {
majorName: studentData.majorName,
className: studentData.className
});
// 只有在teamMembers中没有找到时才使用API获取的信息
if (leaderPhone === '暂无联系方式') {
leaderPhone = studentData.phone || '暂无联系方式';
@@ -580,7 +580,7 @@ const loadTeamInfo = async (teamId: string | number) => {
console.log('PendingMembersTab: 获取队长详细信息失败:', studentError);
}
}
// 如果仍然没有获取到队长信息,尝试从报名详情中获取
if (leaderPhone === '暂无联系方式' && viewForm.phone) {
leaderPhone = viewForm.phone;
@@ -590,17 +590,15 @@ const loadTeamInfo = async (teamId: string | number) => {
leaderDepartment = viewForm.department;
console.log('PendingMembersTab: 使用报名详情中的院系信息:', leaderDepartment);
}
// 最后的备用方案:如果还是没有获取到,尝试从teamMembers中查找匹配leaderUserId的成员
if ((leaderPhone === '暂无联系方式' || leaderDepartment === '暂无院系信息') && teamData.teamMembers) {
try {
const membersData = JSON.parse(teamData.teamMembers);
if (Array.isArray(membersData)) {
// 查找与leaderUserId匹配的成员
const leaderMember = membersData.find((member: any) =>
member.userId === teamData.leaderUserId
);
const leaderMember = membersData.find((member: any) => member.userId === teamData.leaderUserId);
if (leaderMember) {
if (leaderPhone === '暂无联系方式') {
leaderPhone = leaderMember.phone || '暂无联系方式';
@@ -621,7 +619,7 @@ const loadTeamInfo = async (teamId: string | number) => {
console.error('PendingMembersTab: 备用方案解析团队成员JSON失败:', parseError);
}
}
// 更新团队信息
teamInfo.value = {
id: teamData.id,
@@ -636,9 +634,9 @@ const loadTeamInfo = async (teamId: string | number) => {
description: teamData.description || '',
teamMembers: teamData.teamMembers || '[]'
};
console.log('PendingMembersTab: 最终设置的团队信息:', teamInfo.value);
// 解析团队成员信息
if (teamData.teamMembers) {
try {
@@ -674,13 +672,13 @@ const loadTeamInfo = async (teamId: string | number) => {
}
} catch (apiError) {
console.log('PendingMembersTab: 团队信息API调用失败,使用报名详情中的信息:', apiError);
// 如果API调用失败,使用报名详情中的信息
if (viewForm.teamName) {
// 尝试获取队长的详细信息
let leaderPhone = '暂无联系方式';
let leaderDepartment = '暂无院系信息';
try {
if (viewForm.userId) {
const studentResponse = await getStudent(viewForm.userId);
@@ -701,7 +699,7 @@ const loadTeamInfo = async (teamId: string | number) => {
leaderDepartment = viewForm.department;
}
}
// 构造团队信息对象,优先使用获取到的详细信息
teamInfo.value = {
id: viewForm.teamId,
@@ -716,10 +714,10 @@ const loadTeamInfo = async (teamId: string | number) => {
description: viewForm.remark || '暂无团队简介',
teamMembers: '[]' // JSON格式的字符串
};
// 团队成员信息暂时为空,因为报名详情中没有这个字段
teamMembers.value = [] as TeamMemberInfo[];
console.log('PendingMembersTab: 使用报名详情构造的团队信息:', teamInfo.value);
} else {
teamInfo.value = null;
@@ -744,8 +742,6 @@ const cancelView = () => {
teamMembers.value = [] as TeamMemberInfo[];
};
// 暴露给父组件的方法
defineExpose({
getList
@@ -753,7 +749,7 @@ defineExpose({
// 定义事件
const emit = defineEmits<{
refreshOtherTabs: []
refreshOtherTabs: [];
}>();
// 通知父组件刷新其他标签页
@@ -803,4 +799,4 @@ onMounted(() => {
.mb-4 {
margin-bottom: 16px;
}
</style>
</style>
@@ -3,21 +3,21 @@
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="always" style="padding: 8px 16px;">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0;">
<el-card shadow="always" style="padding: 8px 16px">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0">
<el-row :gutter="16" align="middle">
<el-col :span="5">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0;">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0">
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0;">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -36,10 +36,14 @@
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['stgl:activityRegister:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['stgl:activityRegister:edit']">修改</el-button>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['stgl:activityRegister:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['stgl:activityRegister:remove']">删除</el-button>
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['stgl:activityRegister:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Download" @click="handleExport" v-hasPermi="['stgl:activityRegister:export']">导出数据</el-button>
@@ -98,7 +102,7 @@
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['stgl:activityRegister:edit']"></el-button>
</el-tooltip>
<!-- 审核通过状态显示成绩录入按钮 -->
<template v-if="Number(scope.row.approvalStatus) === 1">
<el-tooltip content="录入成绩" placement="top">
@@ -111,7 +115,7 @@
></el-button>
</el-tooltip>
</template>
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['stgl:activityRegister:remove']"></el-button>
</el-tooltip>
@@ -216,7 +220,7 @@
<el-divider content-position="left">
<span class="text-lg font-medium">团队信息</span>
</el-divider>
<el-descriptions :column="2" border class="mb-4">
<el-descriptions-item label="团队名称">{{ teamInfo.teamName }}</el-descriptions-item>
<el-descriptions-item label="团队人数">{{ teamInfo.teamSize || 0 }}人</el-descriptions-item>
@@ -253,7 +257,7 @@
</el-table>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelView">关 闭</el-button>
@@ -306,7 +310,7 @@
是否更新已经存在的报名数据
</div>
<span>仅允许导入xls、xlsx格式文件。</span>
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
</div>
</template>
</el-upload>
@@ -508,18 +512,22 @@ const data = reactive<PageData<ExtendedActivityRegisterForm, StglActivityRegistr
const { queryParams, form, rules } = toRefs(data);
// 监听activityId变化,同步更新查询参数
watch(() => props.activityId, (newActivityId) => {
if (newActivityId) {
console.log('RegisteredMembersTab: activityId发生变化,从', queryParams.value.activityId, '更新为', newActivityId);
queryParams.value.activityId = newActivityId;
// 重置分页到第一页
queryParams.value.pageNum = 1;
// 如果组件已经挂载,则立即重新查询
if (memberList.value.length > 0 || loading.value === false) {
getList();
watch(
() => props.activityId,
(newActivityId) => {
if (newActivityId) {
console.log('RegisteredMembersTab: activityId发生变化,从', queryParams.value.activityId, '更新为', newActivityId);
queryParams.value.activityId = newActivityId;
// 重置分页到第一页
queryParams.value.pageNum = 1;
// 如果组件已经挂载,则立即重新查询
if (memberList.value.length > 0 || loading.value === false) {
getList();
}
}
}
}, { immediate: true });
},
{ immediate: true }
);
// 上传相关
const uploadRef = ref<UploadInstance>();
@@ -544,23 +552,23 @@ const getList = async () => {
loading.value = true;
try {
// 构建查询参数,确保使用最新的activityId
const queryData = {
const queryData = {
...queryParams.value,
activityId: props.activityId // 强制使用最新的props值
activityId: props.activityId // 强制使用最新的props值
};
// 如果状态字段为空或未选择,则查询已通过(approvalStatus=1)的报名记录
if (!queryData.approvalStatus) {
queryData.approvalStatus = '1';
}
console.log('RegisteredMembersTab: getList 开始查询,参数:', queryData);
console.log('RegisteredMembersTab: props.activityId:', props.activityId);
console.log('RegisteredMembersTab: queryParams.activityId:', queryParams.value.activityId);
const res = await listActivityRegister(queryData);
console.log('RegisteredMembersTab: 查询结果:', res);
memberList.value = res.rows;
total.value = res.total;
} catch (error) {
@@ -579,9 +587,9 @@ const cancel = () => {
/** 表单重置 */
const reset = () => {
form.value = {
...initFormData,
activityId: props.activityId // 确保使用最新的activityId
form.value = {
...initFormData,
activityId: props.activityId // 确保使用最新的activityId
};
memberFormRef.value?.resetFields();
};
@@ -626,7 +634,7 @@ const handleUpdate = async (row?: ExtendedStglActivityRegistrationVo) => {
const _id = row?.id || ids.value[0];
const res = await getActivityRegister(_id);
Object.assign(form.value, res.data);
// 设置学生选择器数据
if (res.data.studentName) {
// 使用扩展类型来访问额外字段
@@ -642,7 +650,7 @@ const handleUpdate = async (row?: ExtendedStglActivityRegistrationVo) => {
className: extendedData.className || ''
} as StudentVO;
}
dialog.visible = true;
dialog.title = '修改活动报名';
};
@@ -652,14 +660,14 @@ const submitForm = () => {
memberFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
// 创建提交数据,排除selectedStudent字段
const { selectedStudent, ...submitData } = form.value;
// 确保包含必要字段
submitData.activityId = props.activityId; // 使用最新的activityId
submitData.activityId = props.activityId; // 使用最新的activityId
submitData.approvalStatus = 1; // 新增的报名默认为已通过状态
try {
if (form.value.id) {
await updateActivityRegister(submitData as StglActivityRegistrationForm);
@@ -682,14 +690,14 @@ const submitForm = () => {
const handleDelete = async (row?: ExtendedStglActivityRegistrationVo) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除选中的活动报名记录?');
try {
if (Array.isArray(_ids)) {
await delActivityRegister(_ids.join(','));
} else {
await delActivityRegister(_ids);
}
proxy?.$modal.msgSuccess('删除成功');
await getList();
} catch (error) {
@@ -703,7 +711,7 @@ const handleView = async (row: ExtendedStglActivityRegistrationVo) => {
Object.assign(viewForm, res.data);
viewDialog.visible = true;
viewDialog.title = '查看活动报名详情';
// 如果是团队报名,获取团队信息
if (Number(viewForm.registrationType) === 2 && viewForm.teamId) {
await loadTeamInfo(viewForm.teamId);
@@ -719,39 +727,37 @@ const loadTeamInfo = async (teamId: string | number) => {
if (!teamId) {
return;
}
loadingTeamInfo.value = true;
try {
// 尝试调用团队信息API获取完整信息
try {
const response = await getTeamsByActivityId(viewForm.activityId);
console.log('RegisteredMembersTab: 团队信息API响应:', response);
if (response.data && Array.isArray(response.data)) {
// 从团队列表中查找当前团队
const teamData = response.data.find((team: any) => {
return team.id == teamId || team.id.toString() === teamId || team.id === parseInt(teamId as string);
});
console.log('RegisteredMembersTab: 找到的团队数据:', teamData);
if (teamData) {
// 优先从teamMembers中查找队长信息
let leaderPhone = '暂无联系方式';
let leaderDepartment = '暂无院系信息';
let leaderMajor = '';
let leaderClassName = '';
try {
if (teamData.teamMembers) {
const membersData = JSON.parse(teamData.teamMembers);
if (Array.isArray(membersData)) {
// 查找队长信息(isLeader: 1 或者 userId 匹配 leaderUserId)
const leaderMember = membersData.find((member: any) =>
member.isLeader === 1 || member.userId === teamData.leaderUserId
);
const leaderMember = membersData.find((member: any) => member.isLeader === 1 || member.userId === teamData.leaderUserId);
if (leaderMember) {
leaderPhone = leaderMember.phone || '暂无联系方式';
leaderDepartment = leaderMember.department || '暂无院系信息';
@@ -764,7 +770,7 @@ const loadTeamInfo = async (teamId: string | number) => {
} catch (parseError) {
console.error('RegisteredMembersTab: 解析团队成员JSON失败:', parseError);
}
// 如果从teamMembers中没有找到队长信息,尝试通过leaderUserId获取
if (leaderPhone === '暂无联系方式' || leaderDepartment === '暂无院系信息') {
try {
@@ -772,7 +778,7 @@ const loadTeamInfo = async (teamId: string | number) => {
console.log('RegisteredMembersTab: 通过leaderUserId获取队长详细信息:', teamData.leaderUserId);
const studentResponse = await getStudent(teamData.leaderUserId);
console.log('RegisteredMembersTab: getStudent API响应:', studentResponse);
if (studentResponse.data) {
const studentData = studentResponse.data;
console.log('RegisteredMembersTab: 学生数据详情:', studentData);
@@ -782,7 +788,7 @@ const loadTeamInfo = async (teamId: string | number) => {
majorName: studentData.majorName,
className: studentData.className
});
// 只有在teamMembers中没有找到时才使用API获取的信息
if (leaderPhone === '暂无联系方式') {
leaderPhone = studentData.phone || '暂无联系方式';
@@ -805,7 +811,7 @@ const loadTeamInfo = async (teamId: string | number) => {
console.log('RegisteredMembersTab: 获取队长详细信息失败:', studentError);
}
}
// 如果仍然没有获取到队长信息,尝试从报名详情中获取
if (leaderPhone === '暂无联系方式' && viewForm.phone) {
leaderPhone = viewForm.phone;
@@ -815,17 +821,15 @@ const loadTeamInfo = async (teamId: string | number) => {
leaderDepartment = viewForm.department;
console.log('RegisteredMembersTab: 使用报名详情中的院系信息:', leaderDepartment);
}
// 最后的备用方案:如果还是没有获取到,尝试从teamMembers中查找匹配leaderUserId的成员
if ((leaderPhone === '暂无联系方式' || leaderDepartment === '暂无院系信息') && teamData.teamMembers) {
try {
const membersData = JSON.parse(teamData.teamMembers);
if (Array.isArray(membersData)) {
// 查找与leaderUserId匹配的成员
const leaderMember = membersData.find((member: any) =>
member.userId === teamData.leaderUserId
);
const leaderMember = membersData.find((member: any) => member.userId === teamData.leaderUserId);
if (leaderMember) {
if (leaderPhone === '暂无联系方式') {
leaderPhone = leaderMember.phone || '暂无联系方式';
@@ -846,7 +850,7 @@ const loadTeamInfo = async (teamId: string | number) => {
console.error('RegisteredMembersTab: 备用方案解析团队成员JSON失败:', parseError);
}
}
// 更新团队信息
teamInfo.value = {
id: teamData.id,
@@ -861,9 +865,9 @@ const loadTeamInfo = async (teamId: string | number) => {
description: teamData.description || '',
teamMembers: teamData.teamMembers || '[]'
};
console.log('RegisteredMembersTab: 最终设置的团队信息:', teamInfo.value);
// 解析团队成员信息
if (teamData.teamMembers) {
try {
@@ -899,13 +903,13 @@ const loadTeamInfo = async (teamId: string | number) => {
}
} catch (apiError) {
console.log('RegisteredMembersTab: 团队信息API调用失败,使用报名详情中的信息:', apiError);
// 如果API调用失败,使用报名详情中的信息
if (viewForm.teamName) {
// 尝试获取队长的详细信息
let leaderPhone = '暂无联系方式';
let leaderDepartment = '暂无院系信息';
try {
if (viewForm.userId) {
const studentResponse = await getStudent(viewForm.userId);
@@ -926,7 +930,7 @@ const loadTeamInfo = async (teamId: string | number) => {
leaderDepartment = viewForm.department;
}
}
// 构造团队信息对象,优先使用获取到的详细信息
teamInfo.value = {
id: viewForm.teamId,
@@ -941,10 +945,10 @@ const loadTeamInfo = async (teamId: string | number) => {
description: viewForm.remark || '暂无团队简介',
teamMembers: '[]' // JSON格式的字符串
};
// 团队成员信息暂时为空,因为报名详情中没有这个字段
teamMembers.value = [] as TeamMemberInfo[];
console.log('RegisteredMembersTab: 使用报名详情构造的团队信息:', teamInfo.value);
} else {
teamInfo.value = null;
@@ -1124,4 +1128,4 @@ onMounted(() => {
.mb-4 {
margin-bottom: 16px;
}
</style>
</style>
+5 -5
View File
@@ -147,7 +147,7 @@
<!-- 查看活动详细信息对话框 -->
<el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="1200px" append-to-body>
<ActivityDetailView :activity="viewForm" />
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleRegister(viewForm)" v-if="canRegister(viewForm)">立即报名</el-button>
@@ -484,7 +484,7 @@ const checkUserRegistrationStatus = async () => {
console.warn('活动数据不完整,跳过报名状态检查:', activity);
return;
}
const registered = await checkRegistration(activity.id as number);
userRegistrationStatus.value[activity.id as number] = registered.data;
} catch (error) {
@@ -591,7 +591,7 @@ const handleRegistrationTypeChange = async (value: number) => {
} else if (value === 2) {
// 团队报名,自动填充当前用户信息作为团队负责人
await loadUserInfoToForm();
// 设置团队负责人为学生对象(用于StudentSelector组件)
// 使用类型断言避免类型错误,因为我们只需要这些字段用于显示
registerForm.presidentStudent = {
@@ -747,14 +747,14 @@ const submitRegister = () => {
// 个人报名时不发送任何团队相关字段
await registerActivity(submitData);
// 根据审核设置显示不同的成功消息
if (selectedActivity.value.needApproval === 0) {
proxy?.$modal.msgSuccess('报名成功!您的报名已自动通过审核');
} else {
proxy?.$modal.msgSuccess('报名成功,请等待审核');
}
registerDialog.visible = false;
// 更新报名状态
+140 -102
View File
@@ -84,18 +84,41 @@
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['stgl:activity:add']">新增</el-button>
</el-col>
<el-col :span="1.5" v-if="!single && selectedActivity && ['0', '7', '6'].includes(getOriginalActivityStatus(selectedActivity)) && hasActivityOperationPermission(selectedActivity)">
<el-button icon="Edit" @click="handleUpdate()" v-hasPermi="['stgl:activity:edit']">修改</el-button>
</el-col>
<el-col :span="1.5" v-if="!single && selectedActivity && getOriginalActivityStatus(selectedActivity) === '0' && hasActivityOperationPermission(selectedActivity)">
<el-button type="success" icon="Upload" @click="handleSubmit()" v-hasPermi="['stgl:activity:edit']">提交</el-button>
</el-col>
<el-col :span="1.5" v-if="!single && selectedActivity && getOriginalActivityStatus(selectedActivity) === '2' && hasActivityOperationPermission(selectedActivity)">
<el-button type="warning" icon="Check" @click="handleApprove()" v-hasPermi="['stgl:activity:approve']">审批</el-button>
</el-col>
<el-col :span="1.5" v-if="!single && selectedActivity && ['3', '4'].includes(selectedActivity.activityStatus) && hasActivityOperationPermission(selectedActivity)">
<el-button type="danger" icon="Close" @click="handleCancel()" v-hasPermi="['stgl:activity:edit']">取消活动</el-button>
</el-col>
<el-col
:span="1.5"
v-if="
!single &&
selectedActivity &&
['0', '7', '6'].includes(getOriginalActivityStatus(selectedActivity)) &&
hasActivityOperationPermission(selectedActivity)
"
>
<el-button icon="Edit" @click="handleUpdate()" v-hasPermi="['stgl:activity:edit']">修改</el-button>
</el-col>
<el-col
:span="1.5"
v-if="
!single && selectedActivity && getOriginalActivityStatus(selectedActivity) === '0' && hasActivityOperationPermission(selectedActivity)
"
>
<el-button type="success" icon="Upload" @click="handleSubmit()" v-hasPermi="['stgl:activity:edit']">提交</el-button>
</el-col>
<el-col
:span="1.5"
v-if="
!single && selectedActivity && getOriginalActivityStatus(selectedActivity) === '2' && hasActivityOperationPermission(selectedActivity)
"
>
<el-button type="warning" icon="Check" @click="handleApprove()" v-hasPermi="['stgl:activity:approve']">审批</el-button>
</el-col>
<el-col
:span="1.5"
v-if="
!single && selectedActivity && ['3', '4'].includes(selectedActivity.activityStatus) && hasActivityOperationPermission(selectedActivity)
"
>
<el-button type="danger" icon="Close" @click="handleCancel()" v-hasPermi="['stgl:activity:edit']">取消活动</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="CopyDocument" :disabled="single" @click="handleCopy()" v-hasPermi="['stgl:activity:add']">复制</el-button>
</el-col>
@@ -105,7 +128,9 @@
:disabled="multiple || !['0', '7'].includes(getOriginalActivityStatus(selectedActivity) || '')"
@click="handleDelete()"
v-hasPermi="['stgl:activity:remove']"
:title="selectedActivity && !['0', '7'].includes(getOriginalActivityStatus(selectedActivity)) ? '只有草稿和已驳回状态的活动可以删除' : ''"
:title="
selectedActivity && !['0', '7'].includes(getOriginalActivityStatus(selectedActivity)) ? '只有草稿和已驳回状态的活动可以删除' : ''
"
>删除</el-button
>
</el-col>
@@ -209,7 +234,11 @@
v-hasPermi="['stgl:activity:query']"
></el-button>
</el-tooltip>
<el-tooltip content="修改" placement="top" v-if="['0', '7', '6'].includes(getOriginalActivityStatus(scope.row)) && hasActivityOperationPermission(scope.row)">
<el-tooltip
content="修改"
placement="top"
v-if="['0', '7', '6'].includes(getOriginalActivityStatus(scope.row)) && hasActivityOperationPermission(scope.row)"
>
<el-button
link
type="primary"
@@ -220,7 +249,11 @@
v-hasPermi="['stgl:activity:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="提交" placement="top" v-if="getOriginalActivityStatus(scope.row) === '0' && hasActivityOperationPermission(scope.row)">
<el-tooltip
content="提交"
placement="top"
v-if="getOriginalActivityStatus(scope.row) === '0' && hasActivityOperationPermission(scope.row)"
>
<el-button
link
type="success"
@@ -231,7 +264,11 @@
v-hasPermi="['stgl:activity:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="审批" placement="top" v-if="getOriginalActivityStatus(scope.row) === '2' && hasActivityOperationPermission(scope.row)">
<el-tooltip
content="审批"
placement="top"
v-if="getOriginalActivityStatus(scope.row) === '2' && hasActivityOperationPermission(scope.row)"
>
<el-button
link
type="warning"
@@ -242,7 +279,11 @@
v-hasPermi="['stgl:activity:approve']"
></el-button>
</el-tooltip>
<el-tooltip content="取消活动" placement="top" v-if="['3', '4'].includes(scope.row.activityStatus) && hasActivityOperationPermission(scope.row)">
<el-tooltip
content="取消活动"
placement="top"
v-if="['3', '4'].includes(scope.row.activityStatus) && hasActivityOperationPermission(scope.row)"
>
<el-button
link
type="danger"
@@ -286,7 +327,11 @@
v-hasPermi="['stgl:activity:query']"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top" v-if="['0', '7'].includes(getOriginalActivityStatus(scope.row)) && hasActivityOperationPermission(scope.row)">
<el-tooltip
content="删除"
placement="top"
v-if="['0', '7'].includes(getOriginalActivityStatus(scope.row)) && hasActivityOperationPermission(scope.row)"
>
<el-button
link
type="danger"
@@ -409,9 +454,9 @@
<div class="section-divider">
<h4 class="section-title">指导老师信息</h4>
</div>
<AdvisorManager
<AdvisorManager
:key="`advisor-manager-${form.id || 'new'}-${formKey}`"
v-model="form.advisors"
v-model="form.advisors"
@change="handleAdvisorsChange"
:initial-advisors="form.advisors"
/>
@@ -495,10 +540,6 @@
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</el-form>
<template #footer>
@@ -565,7 +606,7 @@
</el-dialog>
<!-- 成员审核弹窗 -->
<ActivityMembersDialog
<ActivityMembersDialog
v-model:visible="membersDialog.visible"
:activity-id="selectedActivity?.id"
:activity-name="selectedActivity?.activityName"
@@ -585,14 +626,7 @@
</el-radio-group>
</el-form-item>
<el-form-item label="驳回原因" prop="rejectReason" v-if="approveForm.approvalResult === 'reject'">
<el-input
v-model="approveForm.rejectReason"
type="textarea"
placeholder="请输入驳回原因"
:rows="3"
:maxlength="200"
show-word-limit
/>
<el-input v-model="approveForm.rejectReason" type="textarea" placeholder="请输入驳回原因" :rows="3" :maxlength="200" show-word-limit />
</el-form-item>
</el-form>
<template #footer>
@@ -636,7 +670,14 @@ const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const { stgl_activity_type, stgl_activity_status, stgl_registration_type, stgl_registration_status, stgl_activity_range, sys_yes_no } = toRefs<any>(
proxy?.useDict('stgl_activity_type', 'stgl_activity_status', 'stgl_registration_type', 'stgl_registration_status', 'stgl_activity_range', 'sys_yes_no')
proxy?.useDict(
'stgl_activity_type',
'stgl_activity_status',
'stgl_registration_type',
'stgl_registration_status',
'stgl_activity_range',
'sys_yes_no'
)
);
const activityList = ref<StglActivityVO[]>([]);
@@ -790,8 +831,6 @@ const { queryParams, form, rules } = toRefs(data);
const viewForm = ref<StglActivityVO>({} as StglActivityVO);
/** 初始化URL参数 */
const initUrlParams = () => {
const { relatedClubId, relatedClubName, relatedTeamId, relatedTeamName, from } = route.query;
@@ -897,27 +936,27 @@ const reset = () => {
form.value.advisors = [];
form.value.instructorList = [];
form.value.instructors = '[]';
// 重置关联社团禁用状态
isRelatedClubDisabled.value = false;
// 重置审批表单
approveForm.id = undefined;
approveForm.activityName = '';
approveForm.approvalResult = 'approve';
approveForm.rejectReason = '';
// 更新formKey以强制重新渲染组件
formKey.value++;
activeTab.value = 'basic';
console.log('表单重置后的状态:', {
advisors: form.value.advisors,
instructorList: form.value.instructorList,
instructors: form.value.instructors
});
// 延迟重置表单字段,避免递归更新
setTimeout(() => {
activityFormRef.value?.resetFields();
@@ -950,37 +989,37 @@ const handleAdd = () => {
// 先重置表单
form.value = { ...initFormData };
activeTab.value = 'basic';
// 确保指导老师字段被正确初始化
form.value.advisors = [];
form.value.instructorList = [];
form.value.instructors = '[]';
// 更新formKey以强制重新渲染组件
formKey.value++;
console.log('新增活动 - 初始化表单数据:', {
advisors: form.value.advisors,
instructorList: form.value.instructorList,
instructors: form.value.instructors,
formKey: formKey.value
});
// 如果有查询条件中的关联组织(社团或志愿者队伍),自动填充关联信息
if (queryParams.value.relatedClub) {
form.value.relatedClub = queryParams.value.relatedClub;
form.value.relatedClubId = queryParams.value.relatedClub.relatedClubId;
form.value.relatedType = queryParams.value.relatedClub.relatedType;
// 设置关联社团字段为禁用状态(不允许修改)
isRelatedClubDisabled.value = true;
console.log('新增活动 - 自动填充关联组织信息:', {
relatedType: queryParams.value.relatedClub.relatedType,
relatedClubId: queryParams.value.relatedClub.relatedClubId,
relatedClubName: queryParams.value.relatedClub.relatedClubName
});
// 确保表单数据正确设置
console.log('新增活动 - 表单数据设置后:', {
formRelatedClub: form.value.relatedClub,
@@ -994,7 +1033,7 @@ const handleAdd = () => {
dialog.visible = true;
dialog.title = '添加活动信息';
// 确保对话框完全打开后再检查表单状态
nextTick(() => {
console.log('新增活动 - 对话框打开后的表单状态:', {
@@ -1036,7 +1075,7 @@ const handleUpdate = async (row?: StglActivityVO) => {
relatedType: res.data.relatedType,
uniqueKey: `${res.data.relatedType}-${res.data.relatedClubId}`
};
// 修改时,如果是从URL参数进入的,则禁用关联社团字段
if (queryParams.value.relatedClub && queryParams.value.relatedClub.relatedClubId === res.data.relatedClubId) {
isRelatedClubDisabled.value = true;
@@ -1074,7 +1113,7 @@ const handleUpdate = async (row?: StglActivityVO) => {
try {
const instructorsData = JSON.parse(res.data.instructors);
console.log('解析的指导老师数据:', instructorsData);
// 确保数据格式正确
if (Array.isArray(instructorsData)) {
(res.data as any).advisors = instructorsData.map((advisor: any) => ({
@@ -1098,12 +1137,12 @@ const handleUpdate = async (row?: StglActivityVO) => {
}
Object.assign(form.value, res.data);
// 确保数据类型正确
if (form.value.registrationType !== undefined) {
form.value.registrationType = String(form.value.registrationType);
}
// 确保指导老师数据正确设置
console.log('修改活动 - 设置表单数据后的状态:', {
advisors: form.value.advisors,
@@ -1111,10 +1150,10 @@ const handleUpdate = async (row?: StglActivityVO) => {
instructors: form.value.instructors,
registrationType: form.value.registrationType
});
// 更新formKey以强制重新渲染组件
formKey.value++;
dialog.visible = true;
dialog.title = '修改活动信息';
};
@@ -1122,7 +1161,7 @@ const handleUpdate = async (row?: StglActivityVO) => {
/** 显示验证错误提示 */
const showValidationErrors = (invalidFields: any) => {
if (!invalidFields) return;
const errorMessages: string[] = [];
const tabMapping = {
// 基本信息标签页字段
@@ -1135,20 +1174,23 @@ const showValidationErrors = (invalidFields: any) => {
'registrationType': { tab: 'registration', label: '报名方式' },
'activityRange': { tab: 'registration', label: '报名范围' }
};
let needSwitchTab = '';
let currentTabErrors: string[] = [];
let otherTabErrors: string[] = [];
// 分类错误信息
Object.keys(invalidFields).forEach(fieldName => {
Object.keys(invalidFields).forEach((fieldName) => {
const fieldInfo = tabMapping[fieldName];
if (fieldInfo) {
const errorMsg = invalidFields[fieldName][0]?.message || '验证失败';
// 去掉重复的"不能为空"等文字,只保留核心提示
const cleanMessage = errorMsg.replace(/不能为空$/, '').replace(/^请选择/, '').replace(/^请输入/, '');
const cleanMessage = errorMsg
.replace(/不能为空$/, '')
.replace(/^请选择/, '')
.replace(/^请输入/, '');
const errorMessage = cleanMessage ? `${fieldInfo.label}:${cleanMessage}` : `${fieldInfo.label}为必填项`;
if (fieldInfo.tab === activeTab.value) {
currentTabErrors.push(errorMessage);
} else {
@@ -1159,17 +1201,17 @@ const showValidationErrors = (invalidFields: any) => {
}
}
});
// 构建提示消息
if (currentTabErrors.length > 0) {
errorMessages.push(`【当前页面】${currentTabErrors.join(';')}`);
}
if (otherTabErrors.length > 0) {
const tabName = needSwitchTab === 'registration' ? '报名设置' : '基本信息';
errorMessages.push(`【${tabName}页面】${otherTabErrors.join(';')}`);
}
if (errorMessages.length > 0) {
// 使用Notification在浏览器顶部显示提示
ElNotification({
@@ -1180,11 +1222,11 @@ const showValidationErrors = (invalidFields: any) => {
position: 'top-right',
showClose: true
});
// 如果其他标签页有错误,自动切换到第一个有错误的标签页
if (needSwitchTab && needSwitchTab !== activeTab.value) {
activeTab.value = needSwitchTab;
// 切换标签页后延迟一下再次验证,确保用户能看到错误字段
nextTick(() => {
activityFormRef.value?.validate(() => {});
@@ -1201,7 +1243,7 @@ const submitForm = () => {
// 准备提交的数据
const submitData = { ...form.value } as any;
console.log('提交前 - 原始表单数据:', {
advisors: submitData.advisors,
instructorList: submitData.instructorList,
@@ -1234,7 +1276,7 @@ const submitForm = () => {
if (submitData.coOrganizerList && submitData.coOrganizerList.length > 0) {
submitData.coOrganizers = JSON.stringify(submitData.coOrganizerList);
} else {
submitData.coOrganizers = '[]'; // 修复:使用空JSON数组而不是undefined
submitData.coOrganizers = '[]'; // 修复:使用空JSON数组而不是undefined
}
// 处理指导老师数据
@@ -1245,24 +1287,24 @@ const submitForm = () => {
submitData.advisors = [];
}
try {
if (form.value.id) {
await updateStglActivity(submitData);
} else {
await addStglActivity(submitData);
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
// 提交成功后重置表单
reset();
await getList();
} catch (error) {
console.error('提交失败:', error);
} finally {
buttonLoading.value = false;
try {
if (form.value.id) {
await updateStglActivity(submitData);
} else {
await addStglActivity(submitData);
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
// 提交成功后重置表单
reset();
await getList();
} catch (error) {
console.error('提交失败:', error);
} finally {
buttonLoading.value = false;
}
} else {
// 验证失败,显示必填项提示
showValidationErrors(invalidFields);
@@ -1459,7 +1501,7 @@ const handleRelatedClubChange = (club: ClubOption | null) => {
form.value.relatedClub = club;
form.value.relatedClubId = club?.relatedClubId;
form.value.relatedType = club?.relatedType; // 修复:同时设置关联类型
console.log('活动表单 - 更新后的表单数据:', {
relatedClub: form.value.relatedClub,
relatedClubId: form.value.relatedClubId,
@@ -1473,8 +1515,6 @@ const handleQueryRelatedClubChange = (club: ClubOption | null) => {
queryParams.value.relatedClubId = club?.relatedClubId;
};
/** 承办方变化处理 */
const handleCoOrganizersChange = (coOrganizers: CoOrganizerInfo[]) => {
form.value.coOrganizerList = coOrganizers;
@@ -1525,14 +1565,14 @@ const handleSubmit = async (row?: StglActivityVO) => {
try {
await proxy?.$modal.confirm(`是否确认提交活动"${activity.activityName}"?提交后将进入审批流程。`);
// 调用后端接口更新活动状态为2(待审批)
// 传递完整的活动数据,避免后端验证失败
await updateStglActivity({
...activity,
activityStatus: '2'
});
proxy?.$modal.msgSuccess('提交成功');
await getList();
} catch (error) {
@@ -1564,14 +1604,14 @@ const handleCancel = async (row?: StglActivityVO) => {
try {
await proxy?.$modal.confirm(`是否确认取消活动"${activity.activityName}"?取消后将无法恢复。`);
// 调用后端接口更新活动状态为6(已取消)
// 传递完整的活动数据,避免后端验证失败
await updateStglActivity({
...activity,
activityStatus: '6'
});
proxy?.$modal.msgSuccess('取消活动成功');
await getList();
} catch (error) {
@@ -1588,7 +1628,7 @@ const confirmApprove = async () => {
approveLoading.value = true;
try {
const activity = activityList.value.find(item => item.id === approveForm.id);
const activity = activityList.value.find((item) => item.id === approveForm.id);
if (!activity) {
proxy?.$modal.msgError('活动信息不存在');
return;
@@ -1677,7 +1717,7 @@ const hasInstructorPermission = computed(() => {
// 检查当前用户是否为指定活动的指导老师
const isCurrentUserAdvisor = (activity: StglActivityVO): boolean => {
if (!activity.instructors) return false;
try {
const advisors = JSON.parse(activity.instructors);
if (Array.isArray(advisors)) {
@@ -1690,7 +1730,7 @@ const isCurrentUserAdvisor = (activity: StglActivityVO): boolean => {
} catch (error) {
console.warn('解析活动指导老师数据失败:', error);
}
return false;
};
@@ -1700,22 +1740,22 @@ const hasActivityOperationPermission = (activity: StglActivityVO): boolean => {
if (proxy?.$auth.hasRole('admin') || proxy?.$auth.hasRole('common')) {
return true;
}
// 指导老师只能操作自己指导的活动
if (proxy?.$auth.hasRole('stgl_instructor')) {
return isCurrentUserAdvisor(activity);
}
// 社长只能操作自己创建的活动
if (proxy?.$auth.hasRole('stgl_president')) {
return activity.createBy === userStore.userId;
}
// 志愿者队长只能操作自己创建的活动
if (proxy?.$auth.hasRole('stgl_volunteer_captain')) {
return activity.createBy === userStore.userId;
}
return false;
};
@@ -1844,6 +1884,4 @@ onMounted(() => {
border: 1px solid #dcdfe6;
}
}
</style>
@@ -43,7 +43,6 @@
新增
</el-button>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['stgl:clubmember:edit']"
@@ -421,7 +420,7 @@ const { queryParams, form, rules } = toRefs(data);
const isAddButtonDisabled = computed(() => {
// 使用本地成员数量,确保实时更新
const currentCount = memberList.value.length;
if (!props.memberLimit || props.memberLimit <= 0) {
return false; // 没有设置上限,允许新增
}
@@ -431,7 +430,7 @@ const isAddButtonDisabled = computed(() => {
// 计算属性:新增按钮的提示信息
const getAddButtonTooltip = computed(() => {
const currentCount = memberList.value.length;
if (!props.memberLimit || props.memberLimit <= 0) {
return '该社团未设置成员上限';
}
@@ -441,7 +440,7 @@ const getAddButtonTooltip = computed(() => {
// 计算属性:成员数量标签的类型
const getMemberCountTagType = computed(() => {
const currentCount = memberList.value.length;
if (!props.memberLimit || props.memberLimit <= 0) {
return 'info'; // 无上限时显示信息标签
}
@@ -812,7 +811,7 @@ defineExpose({
// 定义事件,用于通知父组件数据变化
const emit = defineEmits<{
memberCountChanged: [newCount: number]
memberCountChanged: [newCount: number];
}>();
// 通知父组件成员数量变化
@@ -838,6 +837,4 @@ const notifyMemberCountChange = () => {
white-space: pre-wrap;
word-break: break-word;
}
</style>
@@ -3,27 +3,22 @@
<el-tabs v-model="activeTab" type="border-card" :key="clubId" @tab-change="handleTabChange">
<!-- 已加入标签页 -->
<el-tab-pane label="已加入" name="joined">
<JoinedMembersTab
<JoinedMembersTab
ref="joinedTabRef"
:club-id="clubId"
:club-name="clubName"
:club-id="clubId"
:club-name="clubName"
:member-limit="memberLimit"
:current-member-count="currentMemberCount"
:key="`joined-${clubId}`"
:key="`joined-${clubId}`"
/>
</el-tab-pane>
<!-- 待审核标签页 -->
<el-tab-pane label="待审核" name="pending">
<PendingMembersTab
ref="pendingTabRef"
:club-id="clubId"
:club-name="clubName"
:key="`pending-${clubId}`"
/>
<PendingMembersTab ref="pendingTabRef" :club-id="clubId" :club-name="clubName" :key="`pending-${clubId}`" />
</el-tab-pane>
</el-tabs>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">关 闭</el-button>
@@ -58,27 +53,33 @@ const joinedTabRef = ref();
const pendingTabRef = ref();
// 监听visible变化
watch(() => props.visible, (newVal) => {
if (newVal) {
activeTab.value = 'joined';
// 当弹窗打开时,触发当前标签页的数据加载
nextTick(() => {
if (activeTab.value === 'joined' && joinedTabRef.value) {
joinedTabRef.value.getList();
} else if (activeTab.value === 'pending' && pendingTabRef.value) {
pendingTabRef.value.getList();
}
});
watch(
() => props.visible,
(newVal) => {
if (newVal) {
activeTab.value = 'joined';
// 当弹窗打开时,触发当前标签页的数据加载
nextTick(() => {
if (activeTab.value === 'joined' && joinedTabRef.value) {
joinedTabRef.value.getList();
} else if (activeTab.value === 'pending' && pendingTabRef.value) {
pendingTabRef.value.getList();
}
});
}
}
});
);
// 监听clubId变化,重置标签页
watch(() => props.clubId, (newClubId) => {
console.log('MemberManagementDialog: clubId 变化:', newClubId);
if (newClubId) {
activeTab.value = 'joined';
watch(
() => props.clubId,
(newClubId) => {
console.log('MemberManagementDialog: clubId 变化:', newClubId);
if (newClubId) {
activeTab.value = 'joined';
}
}
});
);
const handleClose = () => {
emit('update:visible', false);
@@ -91,7 +92,7 @@ const handleVisibleChange = (value: boolean) => {
/** 标签页切换处理 */
const handleTabChange = (tabName: string) => {
console.log('标签页切换到:', tabName);
// 根据当前激活的标签页,调用对应组件的刷新方法
if (tabName === 'joined' && joinedTabRef.value) {
// 调用已加入成员Tab的刷新方法
@@ -111,4 +112,4 @@ const handleTabChange = (tabName: string) => {
.dialog-footer {
text-align: right;
}
</style>
</style>
@@ -3,26 +3,26 @@
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="always" style="padding: 8px 16px;">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0;">
<el-card shadow="always" style="padding: 8px 16px">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0">
<el-row :gutter="16" align="middle">
<el-col :span="5">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0;">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0">
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0;">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="联系方式" prop="params.phone" style="margin-bottom: 0;">
<el-form-item label="联系方式" prop="params.phone" style="margin-bottom: 0">
<el-input v-model="queryParams.params.phone" placeholder="请输入联系方式" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="8" class="search-btn-container">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -38,13 +38,7 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="info"
plain
icon="Check"
:disabled="multiple"
@click="handleBatchApprove()"
v-hasPermi="['stgl:clubmember:approve']"
<el-button type="info" plain icon="Check" :disabled="multiple" @click="handleBatchApprove()" v-hasPermi="['stgl:clubmember:approve']"
>批量审核</el-button
>
</el-col>
@@ -280,9 +274,9 @@ const getList = async () => {
try {
// 构建查询参数,支持多状态查询
const queryData = { ...queryParams.value };
console.log('PendingMembersTab: getList 开始查询,参数:', queryData);
// 待审核页面:查询待审核(status=0)和已驳回(status=3)的成员
// 字典配置:0-待审核,1-审核中,2-已加入,3-已驳回,4-已退出
if (!queryData.status) {
@@ -290,19 +284,19 @@ const getList = async () => {
queryData.params = { ...queryData.params, statusList: ['0', '3'] } as any;
delete queryData.status; // 删除单个status字段
}
// 确保phone参数正确传递到params中
if (queryData.params.phone) {
// 保持phone参数在params中,后端会从params中读取
console.log('PendingMembersTab: phone参数:', queryData.params.phone);
}
console.log('PendingMembersTab: 最终查询参数:', queryData);
console.log('PendingMembersTab: phone参数值:', queryData.params?.phone);
const res = await listStglMember(queryData);
console.log('PendingMembersTab: 查询结果:', res);
memberList.value = res.rows;
total.value = res.total;
} catch (error) {
@@ -363,25 +357,25 @@ const validateMemberData = (member: StglMemberVO, operation: string): boolean =>
console.error(`${operation}失败 - 用户ID缺失:`, member);
return false;
}
if (!member.relationId && !props.clubId) {
proxy?.$modal.msgError(`${operation}失败:关联ID(社团ID)不能为空`);
console.error(`${operation}失败 - 关联ID缺失:`, member);
return false;
}
if (!member.memberType) {
proxy?.$modal.msgError(`${operation}失败:成员类型不能为空`);
console.error(`${operation}失败 - 成员类型缺失:`, member);
return false;
}
if (!member.studentNo || !member.studentName) {
proxy?.$modal.msgError(`${operation}失败:学号和姓名不能为空`);
console.error(`${operation}失败 - 基础信息缺失:`, member);
return false;
}
return true;
};
@@ -391,7 +385,7 @@ const handleApprove = (row: StglMemberVO) => {
if (!validateMemberData(row, '审核')) {
return;
}
approvalForm.id = row.id;
approvalForm.approvalStatus = undefined;
approvalForm.approvalRemark = '';
@@ -408,11 +402,11 @@ const handleBatchApprove = () => {
// 验证所有选中成员的数据完整性
const invalidMembers = memberList.value
.filter(member => ids.value.includes(member.id))
.filter(member => !validateMemberData(member, '批量审核'));
.filter((member) => ids.value.includes(member.id))
.filter((member) => !validateMemberData(member, '批量审核'));
if (invalidMembers.length > 0) {
const invalidNames = invalidMembers.map(m => m.studentName || '未知成员').join('、');
const invalidNames = invalidMembers.map((m) => m.studentName || '未知成员').join('、');
proxy.$modal.msgError(`以下成员数据不完整,无法审核:${invalidNames}`);
return;
}
@@ -432,41 +426,43 @@ const submitApproval = () => {
if (valid) {
if (approvalForm.id) {
// 单个审核 - 再次验证数据完整性
const member = memberList.value.find(m => m.id === approvalForm.id);
const member = memberList.value.find((m) => m.id === approvalForm.id);
if (member && !validateMemberData(member, '审核')) {
return;
}
approveMember(approvalForm.id, approvalForm.approvalStatus === 2).then(() => {
proxy.$modal.msgSuccess('审核成功');
approvalOpen.value = false;
getList();
}).catch((error) => {
console.error('审核失败:', error);
proxy.$modal.msgError('审核失败,请检查数据完整性');
});
approveMember(approvalForm.id, approvalForm.approvalStatus === 2)
.then(() => {
proxy.$modal.msgSuccess('审核成功');
approvalOpen.value = false;
getList();
})
.catch((error) => {
console.error('审核失败:', error);
proxy.$modal.msgError('审核失败,请检查数据完整性');
});
} else {
// 批量审核 - 再次验证数据完整性
const selectedMembers = memberList.value.filter(m => ids.value.includes(m.id));
const invalidMembers = selectedMembers.filter(member => !validateMemberData(member, '批量审核'));
const selectedMembers = memberList.value.filter((m) => ids.value.includes(m.id));
const invalidMembers = selectedMembers.filter((member) => !validateMemberData(member, '批量审核'));
if (invalidMembers.length > 0) {
const invalidNames = invalidMembers.map(m => m.studentName || '未知成员').join('、');
const invalidNames = invalidMembers.map((m) => m.studentName || '未知成员').join('、');
proxy.$modal.msgError(`以下成员数据不完整,无法审核:${invalidNames}`);
return;
}
const promises = ids.value.map(id =>
approveMember(id, approvalForm.approvalStatus === 2)
);
Promise.all(promises).then(() => {
proxy.$modal.msgSuccess('批量审核成功');
approvalOpen.value = false;
getList();
}).catch((error) => {
console.error('批量审核失败:', error);
proxy.$modal.msgError('批量审核失败,请检查数据完整性');
});
const promises = ids.value.map((id) => approveMember(id, approvalForm.approvalStatus === 2));
Promise.all(promises)
.then(() => {
proxy.$modal.msgSuccess('批量审核成功');
approvalOpen.value = false;
getList();
})
.catch((error) => {
console.error('批量审核失败:', error);
proxy.$modal.msgError('批量审核失败,请检查数据完整性');
});
}
}
});
@@ -519,4 +515,4 @@ defineExpose({
white-space: pre-wrap;
word-break: break-word;
}
</style>
</style>
+12 -25
View File
@@ -26,8 +26,6 @@
</el-col>
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
@@ -238,7 +236,14 @@
</el-col>
<el-col :span="12">
<el-form-item label="成员规模上限" prop="memberLimit" required>
<el-input-number v-model="form.memberLimit" :min="1" :max="9999" placeholder="请输入成员规模上限" style="width: 100%" :disabled="!hasInstructorPermission" />
<el-input-number
v-model="form.memberLimit"
:min="1"
:max="9999"
placeholder="请输入成员规模上限"
style="width: 100%"
:disabled="!hasInstructorPermission"
/>
</el-form-item>
</el-col>
</el-row>
@@ -307,8 +312,6 @@
</el-row>
</div>
<!-- 指导老师信息 -->
<div class="personnel-section">
<h4 class="section-title">指导老师信息</h4>
@@ -439,8 +442,6 @@
</div>
</div>
<!-- 指导老师信息 -->
<div class="position-group">
<div class="position-title">指导老师:</div>
@@ -623,12 +624,12 @@
<script setup name="StglClub" lang="ts">
/**
* 社团信息管理
*
*
* 权限控制说明:
* - stgl_president角色:可以操作自己担任社长的社团(无论是否为创建者)
* - stgl_instructor角色:指导老师,有部分字段的只读权限
* - 管理员角色:可以操作所有社团
*
*
* 业务逻辑:
* 管理员创建社团后选择社长,被选中的社长(stgl_president角色)
* 需要能够看到和操作自己担任社长的社团,不再受限于创建者身份
@@ -663,8 +664,6 @@ const hasInstructorPermission = computed(() => {
return proxy?.$auth.hasRoleOr(['stgl_instructor', 'stgl_stgl_admin', 'superadmin']);
});
// 社长权限控制 - 检查用户是否为各社团的社长(无论是否为创建者)
// 用于控制转让按钮显示和社长相关操作权限
const presidentRoles = ref<Record<string | number, boolean>>({});
@@ -974,8 +973,6 @@ const handleUpdate = async (row?: StglClubVO) => {
vicePresidentPhone: res.data.vicePresidentPhone
});
// 处理指导老师信息:优先从JSON字段读取,如果没有则使用单一字段
// 🔥 优先从JSON字段读取指导老师数据
if (res.data.advisorsJson) {
@@ -1040,8 +1037,6 @@ const submitForm = () => {
});
}
// 🔥 新增:同时保存JSON字段和兼容性字段
submitData.advisorsJson = JSON.stringify(submitData.advisors || []);
@@ -1109,8 +1104,6 @@ const handleView = async (row: StglClubVO) => {
(viewData as any).sighOpen = String(viewData.sighOpen);
}
// 🔥 处理指导老师信息:优先从JSON字段读取,如果没有则使用单一字段
console.log('handleView: 指导老师原始数据:', {
advisorsJson: viewData.advisorsJson,
@@ -1263,8 +1256,6 @@ const handlePresidentChange = (student: StudentVO | null) => {
});
};
// 防止递归更新的锁
const syncLock = ref(false);
@@ -1486,9 +1477,9 @@ const handleMemberCountChanged = (newCount: number) => {
if (currentClub.value) {
// 更新当前社团的成员数量
currentClub.value.memberCount = newCount;
// 同时更新列表中的对应社团数据
const clubIndex = clubList.value.findIndex(club => club.id === currentClub.value?.id);
const clubIndex = clubList.value.findIndex((club) => club.id === currentClub.value?.id);
if (clubIndex !== -1) {
clubList.value[clubIndex].memberCount = newCount;
console.log('handleMemberCountChanged: 已更新列表中的成员数量:', clubList.value[clubIndex]);
@@ -1552,8 +1543,6 @@ onMounted(() => {
text-align: center;
}
.member-count {
font-weight: bold;
color: #409eff;
@@ -1706,8 +1695,6 @@ onMounted(() => {
}
}
/* 人员信息部分样式 */
.personnel-section {
margin-bottom: 24px;
+63 -115
View File
@@ -12,19 +12,9 @@
</el-col>
<el-col :span="6">
<el-form-item label="活动类型" prop="activityType">
<el-select
v-model="queryParams.activityType"
placeholder="请选择活动类型"
clearable
style="width: 100%"
>
<el-select v-model="queryParams.activityType" placeholder="请选择活动类型" clearable style="width: 100%">
<el-option label="全部" value="" />
<el-option
v-for="dict in stgl_activity_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
<el-option v-for="dict in stgl_activity_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
@@ -44,7 +34,7 @@
</el-form-item>
</el-col> -->
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
@@ -106,7 +96,7 @@
</el-table-column>
<el-table-column label="报名状态" align="center" prop="myRegistrationStatus" width="100">
<template #default="scope">
<el-tag
<el-tag
:type="scope.row.myRegistrationStatus === '1' ? 'success' : scope.row.myRegistrationStatus === '2' ? 'danger' : 'warning'"
size="small"
>
@@ -217,7 +207,9 @@
<span class="rating-text">{{ detailForm.myRatingScore }}</span>
</div>
</el-descriptions-item>
<el-descriptions-item label="评分时间" v-if="detailForm.myRatingTime">{{ parseTime(detailForm.myRatingTime, '{y}-{m}-{d} {h}:{i}') }}</el-descriptions-item>
<el-descriptions-item label="评分时间" v-if="detailForm.myRatingTime">{{
parseTime(detailForm.myRatingTime, '{y}-{m}-{d} {h}:{i}')
}}</el-descriptions-item>
<el-descriptions-item label="活动平均评分" v-if="detailForm.averageRating">
<div class="rating-cell">
<el-rate :model-value="detailForm.averageRating" disabled size="small" />
@@ -305,10 +297,7 @@
</el-table-column>
<el-table-column label="身份" align="center" width="80">
<template #default="{ row }">
<el-tag
:type="row.isLeader === 1 ? 'success' : 'info'"
size="small"
>
<el-tag :type="row.isLeader === 1 ? 'success' : 'info'" size="small">
{{ row.isLeader === 1 ? '队长' : '队员' }}
</el-tag>
</template>
@@ -321,14 +310,12 @@
</div>
<div v-else class="no-team-info">
<el-empty description="未找到队伍信息" :image-size="60">
<el-button type="primary" size="small" @click="getTeamInfo(detailForm.id)">
重新获取
</el-button>
<el-button type="primary" size="small" @click="getTeamInfo(detailForm.id)"> 重新获取 </el-button>
</el-empty>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="detailOpen = false">关 闭</el-button>
@@ -382,10 +369,10 @@
</el-dialog>
<!-- 签到对话框 -->
<el-dialog
title="活动签到"
v-model="attendanceOpen"
width="560px"
<el-dialog
title="活动签到"
v-model="attendanceOpen"
width="560px"
append-to-body
:close-on-click-modal="false"
:close-on-press-escape="true"
@@ -393,25 +380,11 @@
>
<div class="attendance-content">
<div class="attendance-actions">
<el-button
type="primary"
size="large"
:loading="signInLoading"
:disabled="!canSignIn"
@click="handleSignIn"
class="action-btn"
>
<el-button type="primary" size="large" :loading="signInLoading" :disabled="!canSignIn" @click="handleSignIn" class="action-btn">
{{ signInButtonText }}
</el-button>
<el-button
type="warning"
size="large"
:loading="signOutLoading"
:disabled="!canSignOut"
@click="handleSignOut"
class="action-btn"
>
<el-button type="warning" size="large" :loading="signOutLoading" :disabled="!canSignOut" @click="handleSignOut" class="action-btn">
签 退
</el-button>
</div>
@@ -420,24 +393,10 @@
<div class="attendance-records" v-if="attendanceRecords.length > 0">
<div class="records-header">
<span class="records-title">签到记录</span>
<el-button
type="text"
size="small"
@click="refreshRecords"
:loading="recordsLoading"
>
刷新
</el-button>
<el-button type="text" size="small" @click="refreshRecords" :loading="recordsLoading"> 刷新 </el-button>
</div>
<el-table
:data="attendanceRecords"
stripe
size="small"
class="records-table"
v-loading="recordsLoading"
:max-height="getTableMaxHeight()"
>
<el-table :data="attendanceRecords" stripe size="small" class="records-table" v-loading="recordsLoading" :max-height="getTableMaxHeight()">
<el-table-column prop="signInTime" label="签到时间" width="200">
<template #default="{ row }">
{{ formatDateTime(row.signInTime) }}
@@ -451,10 +410,7 @@
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag
:type="row.signOutTime ? 'success' : 'warning'"
size="small"
>
<el-tag :type="row.signOutTime ? 'success' : 'warning'" size="small">
{{ row.signOutTime ? '已完成' : '进行中' }}
</el-tag>
</template>
@@ -549,23 +505,23 @@ const getList = async () => {
const params = Object.fromEntries(
Object.entries(queryParams.value).filter(([_, value]) => value !== '' && value !== undefined && value !== null)
);
// 直接使用getMyActivities接口,后端已经处理了所有情况(包括用户作为队员参与的活动)
const res = await getMyActivities(params);
const activities = res.data || [];
// 按报名时间排序
activities.sort((a, b) => {
const timeA = a.myRegistrationTime ? new Date(a.myRegistrationTime).getTime() : 0;
const timeB = b.myRegistrationTime ? new Date(b.myRegistrationTime).getTime() : 0;
return timeB - timeA; // 降序排列
});
activityList.value = activities;
console.log('我的活动列表数据:', {
totalCount: activities.length,
activities: activities.map(activity => ({
activities: activities.map((activity) => ({
id: activity.id,
activityName: activity.activityName,
registrationType: activity.registrationType,
@@ -580,8 +536,6 @@ const getList = async () => {
}
};
/** 搜索按钮操作 */
const handleQuery = () => {
getList();
@@ -597,7 +551,7 @@ const resetQuery = () => {
const handleView = async (row: MyActivityVO) => {
detailForm.value = { ...row };
detailOpen.value = true;
// 如果是团队报名,获取队伍信息
if (row.registrationType === '2') {
await getTeamInfo(row.id);
@@ -609,7 +563,7 @@ const handleView = async (row: MyActivityVO) => {
/** 获取队伍信息 */
const getTeamInfo = async (activityId: string | number) => {
if (!activityId) return;
teamInfoLoading.value = true;
try {
const res = await getTeamsByActivityId(activityId);
@@ -621,7 +575,7 @@ const getTeamInfo = async (activityId: string | number) => {
teamInfo.value = null;
return;
}
const currentUserTeam = res.data.find((team: StglActivityTeamInfoVo) => {
// 检查是否为队长
if (team.leaderStudentNo === userInfo.userName) {
@@ -639,9 +593,9 @@ const getTeamInfo = async (activityId: string | number) => {
}
return false;
});
teamInfo.value = currentUserTeam || null;
if (currentUserTeam) {
console.log('获取到队伍信息:', {
teamName: currentUserTeam.teamName,
@@ -681,14 +635,12 @@ const parseTeamMembers = (teamMembersStr: string): any[] => {
}
};
/** 活动评分按钮操作 */
const handleRate = async (row: MyActivityVO) => {
try {
// 获取当前用户详细信息
const userInfo = await getCurrentUserDetailInfo();
ratingForm.value = {
activityId: row.id,
activityName: row.activityName,
@@ -699,7 +651,7 @@ const handleRate = async (row: MyActivityVO) => {
experienceScore: 0,
ratingComment: ''
};
console.log('评分表单数据:', ratingForm.value);
ratingOpen.value = true;
} catch (error) {
@@ -711,7 +663,7 @@ const handleRate = async (row: MyActivityVO) => {
/** 提交评分 */
const submitRating = async () => {
if (!ratingFormRef.value) return;
await ratingFormRef.value.validate(async (valid: boolean) => {
if (valid) {
ratingLoading.value = true;
@@ -821,8 +773,8 @@ const attendanceHistory = ref<any[]>([]);
const currentAttendance = ref<any>(null);
// 签到记录相关数据
const attendanceRecords = ref<any[]>([])
const recordsLoading = ref(false)
const attendanceRecords = ref<any[]>([]);
const recordsLoading = ref(false);
// 计算属性 - 简化状态判断
const canSignIn = computed(() => {
@@ -830,37 +782,35 @@ const canSignIn = computed(() => {
if (!currentAttendance.value) {
return true; // 没有记录时可以签到
}
// 如果最新记录已签到已签退,则可以重新签到
if (currentAttendance.value.signInStatus === 1 && currentAttendance.value.signOutStatus === 1) {
return true;
}
// 如果最新记录已签到未签退,则不能签到
if (currentAttendance.value.signInStatus === 1 && currentAttendance.value.signOutStatus === 0) {
return false;
}
// 其他情况允许签到
return true;
});
const canSignOut = computed(() => {
// 如果有记录且已签到未签退,则可以签退
return currentAttendance.value &&
currentAttendance.value.signInStatus === 1 &&
currentAttendance.value.signOutStatus === 0;
return currentAttendance.value && currentAttendance.value.signInStatus === 1 && currentAttendance.value.signOutStatus === 0;
});
const signInButtonText = computed(() => {
if (!currentAttendance.value) {
return '签到';
}
if (currentAttendance.value.signInStatus === 1 && currentAttendance.value.signOutStatus === 0) {
return '已签到';
}
return '签到';
});
@@ -868,7 +818,7 @@ const signInButtonText = computed(() => {
const handleAttendance = async (row: MyActivityVO) => {
try {
const userInfo = await getCurrentUserDetailInfo();
// 填充活动信息
attendanceForm.value = {
activityId: row.id,
@@ -929,13 +879,13 @@ const handleSignIn = async () => {
};
await createSignInRecord(signInData);
// 签到成功消息
proxy?.$modal.msgSuccess('签到成功');
// 刷新签到状态和历史记录
await refreshAttendanceData();
// 强制更新计算属性
nextTick(() => {
console.log('签到后状态更新:', {
@@ -980,7 +930,7 @@ const handleSignOut = async () => {
// 刷新签到状态和历史记录
await refreshAttendanceData();
// 强制更新计算属性
nextTick(() => {
console.log('签退后状态更新:', {
@@ -1003,19 +953,17 @@ const refreshAttendanceData = async () => {
try {
// 获取最新的签到记录
const recordRes = await getAllAttendanceRecords(attendanceForm.value.activityId, attendanceForm.value.studentNo);
if (recordRes.data && recordRes.data.length > 0) {
// 按创建时间排序,取最新的一条作为当前记录
const sortedRecords = recordRes.data.sort((a: any, b: any) =>
new Date(b.createTime).getTime() - new Date(a.createTime).getTime()
);
const sortedRecords = recordRes.data.sort((a: any, b: any) => new Date(b.createTime).getTime() - new Date(a.createTime).getTime());
currentAttendance.value = sortedRecords[0];
attendanceHistory.value = sortedRecords;
// 同时更新签到记录表格数据
attendanceRecords.value = sortedRecords;
console.log('获取到签到记录:', {
total: sortedRecords.length,
current: currentAttendance.value,
@@ -1030,7 +978,7 @@ const refreshAttendanceData = async () => {
// 强制触发计算属性重新计算
await nextTick();
console.log('刷新签到数据完成:', {
history: attendanceHistory.value,
current: currentAttendance.value,
@@ -1050,7 +998,7 @@ const refreshAttendanceData = async () => {
/** 刷新签到记录 */
const refreshRecords = async () => {
if (!attendanceForm.value.activityId || !attendanceForm.value.studentNo) return;
recordsLoading.value = true;
try {
await refreshAttendanceData();
@@ -1093,14 +1041,14 @@ const getTableMaxHeight = () => {
const calculateDuration = (signInTime: string, signOutTime: string): string => {
if (!signInTime) return '-';
if (!signOutTime) return '进行中';
const start = new Date(signInTime);
const end = new Date(signOutTime);
const diffMs = end.getTime() - start.getTime();
const hours = Math.floor(diffMs / (1000 * 60 * 60));
const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
if (hours > 0) {
return `${hours}小时${minutes}分钟`;
} else {
@@ -1111,9 +1059,9 @@ const calculateDuration = (signInTime: string, signOutTime: string): string => {
/** 提取指导老师姓名 */
const getInstructorNames = (instructors: string | any[]): string => {
if (!instructors) return '暂无';
let instructorArray: any[] = [];
// 如果是字符串,尝试解析JSON
if (typeof instructors === 'string') {
try {
@@ -1127,12 +1075,12 @@ const getInstructorNames = (instructors: string | any[]): string => {
} else {
return '暂无';
}
if (!instructorArray || instructorArray.length === 0) return '暂无';
return instructorArray
.map(instructor => instructor.name || instructor.nickName || '未知')
.filter(name => name && name !== '未知')
.map((instructor) => instructor.name || instructor.nickName || '未知')
.filter((name) => name && name !== '未知')
.join('、');
};
@@ -1423,4 +1371,4 @@ onMounted(() => {
display: flex;
justify-content: center;
}
</style>
</style>
+1 -10
View File
@@ -17,16 +17,9 @@
</el-col>
<el-col :span="6">
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
style="width: 100%"
@keyup.enter="handleQuery"
/>
<el-input v-model="queryParams.name" placeholder="请输入名称" clearable style="width: 100%" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
</el-row>
<el-row>
@@ -228,8 +221,6 @@ const getRegistrationTypeTag = (type: string) => {
return tagMap[type as keyof typeof tagMap] || ('info' as const);
};
/** 获取当前状态标签样式 */
const getCurrentStatusTag = (status: string) => {
const tagMap = {
+4 -10
View File
@@ -202,9 +202,7 @@ import {
import { StglClassQuery, StglClassVO, StglClassDetailVO, ClubDetailVO, VolunteerTeamDetailVO, ActivityDetailVO } from '@/api/stgl/stclass/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_user_sex, stgl_club_type, stgl_activity_type } = toRefs<any>(
proxy?.useDict('sys_user_sex', 'stgl_club_type', 'stgl_activity_type')
);
const { sys_user_sex, stgl_club_type, stgl_activity_type } = toRefs<any>(proxy?.useDict('sys_user_sex', 'stgl_club_type', 'stgl_activity_type'));
const loading = ref(false);
const showSearch = ref(true);
@@ -308,7 +306,7 @@ const handleClassChange = () => {
/** 查看按钮操作 */
const handleView = (row?: StglClassVO) => {
const _row = row || studentList.value.find(item => item.id === ids.value[0]);
const _row = row || studentList.value.find((item) => item.id === ids.value[0]);
if (!_row) {
proxy?.$modal.msgError('请选择要查看的学生');
return;
@@ -364,11 +362,7 @@ const handleViewActivities = async (row: StglClassVO) => {
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'stgl/stclass/export',
queryParams.value,
`班级学生参与情况_${new Date().getTime()}.xlsx`
);
proxy?.download('stgl/stclass/export', queryParams.value, `班级学生参与情况_${new Date().getTime()}.xlsx`);
};
onMounted(() => {
@@ -452,4 +446,4 @@ onMounted(() => {
.activity-item:last-child {
margin-bottom: 0;
}
</style>
</style>
@@ -3,26 +3,26 @@
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="always" style="padding: 8px 16px;">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0;">
<el-card shadow="always" style="padding: 8px 16px">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0">
<el-row :gutter="16" align="middle">
<el-col :span="5">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0;">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0">
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0;">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="联系方式" prop="phone" style="margin-bottom: 0;">
<el-form-item label="联系方式" prop="phone" style="margin-bottom: 0">
<el-input v-model="queryParams.phone" placeholder="请输入联系方式" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -39,16 +39,27 @@
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-tooltip :content="getAddButtonTooltip" placement="top" :disabled="!isAddButtonDisabled">
<el-button type="primary" plain icon="Plus" @click="handleAdd" :disabled="isAddButtonDisabled" v-hasPermi="['stgl:volunteermember:add']">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
:disabled="isAddButtonDisabled"
v-hasPermi="['stgl:volunteermember:add']"
>
新增
</el-button>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['stgl:volunteermember:edit']">修改</el-button>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['stgl:volunteermember:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['stgl:volunteermember:remove']">删除</el-button>
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['stgl:volunteermember:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-dropdown class="mt-[1px]">
@@ -259,7 +270,7 @@
是否更新已经存在的志愿者成员数据
</div>
<span>仅允许导入xls、xlsx格式文件。</span>
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
</div>
</template>
</el-upload>
@@ -308,7 +319,9 @@ interface ExtendedVolunteerMemberForm extends VolunteerMemberForm {
const props = defineProps<Props>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_user_sex, sys_normal_disable, stgl_member_status } = toRefs<any>(proxy?.useDict('sys_user_sex', 'sys_normal_disable', 'stgl_member_status'));
const { sys_user_sex, sys_normal_disable, stgl_member_status } = toRefs<any>(
proxy?.useDict('sys_user_sex', 'sys_normal_disable', 'stgl_member_status')
);
const memberList = ref<VolunteerMemberVO[]>([]);
const buttonLoading = ref(false);
@@ -401,7 +414,7 @@ const { queryParams, form, rules } = toRefs(data);
const isAddButtonDisabled = computed(() => {
// 使用本地成员数量,确保实时更新
const currentCount = memberList.value.length;
if (!props.memberLimit || props.memberLimit <= 0) {
return false; // 没有设置上限,允许新增
}
@@ -411,7 +424,7 @@ const isAddButtonDisabled = computed(() => {
// 计算属性:新增按钮的提示信息
const getAddButtonTooltip = computed(() => {
const currentCount = memberList.value.length;
if (!props.memberLimit || props.memberLimit <= 0) {
return '该团队未设置成员上限';
}
@@ -442,30 +455,30 @@ const getList = async () => {
try {
// 构建查询参数,支持多状态查询
const queryData = { ...queryParams.value };
console.log('JoinedMembersTab: getList 开始查询,参数:', queryData);
// 构建params对象,包含所有需要在params中的查询条件
queryData.params = { ...queryData.params } as any;
// 将phone等查询条件放入params中(后端期望params.phone)
if (queryData.phone) {
(queryData.params as any).phone = queryData.phone;
delete queryData.phone; // 删除顶级phone字段
}
// 如果状态字段为空或未选择,则查询已加入(status=2)和已退出(status=4)的成员
if (!queryData.status) {
// 使用params传递多状态查询,传递字符串格式
(queryData.params as any).statusList = '2,4';
delete queryData.status; // 删除单个status字段
}
console.log('JoinedMembersTab: 最终查询参数:', queryData);
const res = await listVolunteerMember(queryData);
console.log('JoinedMembersTab: 查询结果:', res);
memberList.value = res.rows;
total.value = res.total;
} catch (error) {
@@ -520,7 +533,7 @@ const handleSelectionChange = (selection: VolunteerMemberVO[]) => {
const handleAdd = () => {
// 检查人员上限 - 使用本地成员数量,确保实时更新
const currentCount = memberList.value.length;
if (props.memberLimit && props.memberLimit > 0) {
if (currentCount >= props.memberLimit) {
proxy?.$modal.msgError(`该团队成员已满,当前成员数量:${currentCount},成员上限:${props.memberLimit},无法再新增成员`);
@@ -569,20 +582,20 @@ const submitForm = () => {
return;
}
}
buttonLoading.value = true;
// 创建提交数据,排除selectedStudent字段,确保包含userId和memberType
const { selectedStudent, ...submitData } = form.value;
// 确保memberType为2(志愿者成员)
submitData.memberType = 2;
// 确保status为2(已加入状态)
if (!submitData.status) {
submitData.status = '2';
}
if (form.value.id) {
await updateVolunteerMember(submitData as VolunteerMemberForm).finally(() => (buttonLoading.value = false));
} else {
@@ -601,14 +614,14 @@ const submitForm = () => {
const handleDelete = async (row?: VolunteerMemberVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除选中的志愿者成员?');
// 处理单个删除或批量删除
if (Array.isArray(_ids)) {
await delVolunteerMember(_ids.join(','));
} else {
await delVolunteerMember(_ids);
}
proxy?.$modal.msgSuccess('删除成功');
await getList();
// 通知父组件成员数量变化
@@ -693,7 +706,7 @@ const handleFileSuccess = (response: any) => {
getList();
// 通知父组件成员数量变化
notifyMemberCountChange();
// 检查导入后是否超过成员上限
setTimeout(() => {
const currentCount = memberList.value.length;
@@ -711,7 +724,7 @@ defineExpose({
// 定义事件,用于通知父组件数据变化
const emit = defineEmits<{
memberCountChanged: [newCount: number]
memberCountChanged: [newCount: number];
}>();
// 通知父组件成员数量变化
@@ -741,4 +754,4 @@ onMounted(() => {
white-space: pre-wrap;
word-break: break-word;
}
</style>
</style>
@@ -3,28 +3,23 @@
<el-tabs v-model="activeTab" type="border-card" :key="teamId" @tab-change="handleTabChange">
<!-- 已加入标签页 -->
<el-tab-pane label="已加入" name="joined">
<JoinedMembersTab
<JoinedMembersTab
ref="joinedTabRef"
:team-id="teamId"
:team-name="teamName"
:team-id="teamId"
:team-name="teamName"
:member-limit="memberLimit"
:current-member-count="currentMemberCount"
:key="`joined-${teamId}`"
:key="`joined-${teamId}`"
@member-count-changed="handleMemberCountChanged"
/>
</el-tab-pane>
<!-- 待审核标签页 -->
<el-tab-pane label="待审核" name="pending">
<PendingMembersTab
ref="pendingTabRef"
:team-id="teamId"
:team-name="teamName"
:key="`pending-${teamId}`"
/>
<PendingMembersTab ref="pendingTabRef" :team-id="teamId" :team-name="teamName" :key="`pending-${teamId}`" />
</el-tab-pane>
</el-tabs>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">关 闭</el-button>
@@ -59,27 +54,33 @@ const joinedTabRef = ref();
const pendingTabRef = ref();
// 监听visible变化
watch(() => props.visible, (newVal) => {
if (newVal) {
activeTab.value = 'joined';
// 当弹窗打开时,触发当前标签页的数据加载
nextTick(() => {
if (activeTab.value === 'joined' && joinedTabRef.value) {
joinedTabRef.value.getList();
} else if (activeTab.value === 'pending' && pendingTabRef.value) {
pendingTabRef.value.getList();
}
});
watch(
() => props.visible,
(newVal) => {
if (newVal) {
activeTab.value = 'joined';
// 当弹窗打开时,触发当前标签页的数据加载
nextTick(() => {
if (activeTab.value === 'joined' && joinedTabRef.value) {
joinedTabRef.value.getList();
} else if (activeTab.value === 'pending' && pendingTabRef.value) {
pendingTabRef.value.getList();
}
});
}
}
});
);
// 监听teamId变化,重置标签页
watch(() => props.teamId, (newTeamId) => {
console.log('MemberManagementDialog: teamId 变化:', newTeamId);
if (newTeamId) {
activeTab.value = 'joined';
watch(
() => props.teamId,
(newTeamId) => {
console.log('MemberManagementDialog: teamId 变化:', newTeamId);
if (newTeamId) {
activeTab.value = 'joined';
}
}
});
);
const handleClose = () => {
emit('update:visible', false);
@@ -98,7 +99,7 @@ const handleMemberCountChanged = (newCount: number) => {
/** 标签页切换处理 */
const handleTabChange = (tabName: string) => {
console.log('标签页切换到:', tabName);
// 根据当前激活的标签页,调用对应组件的刷新方法
if (tabName === 'joined' && joinedTabRef.value) {
// 调用已加入成员Tab的刷新方法
@@ -118,4 +119,4 @@ const handleTabChange = (tabName: string) => {
.dialog-footer {
text-align: right;
}
</style>
</style>
@@ -3,26 +3,26 @@
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="always" style="padding: 8px 16px;">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0;">
<el-card shadow="always" style="padding: 8px 16px">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0">
<el-row :gutter="16" align="middle">
<el-col :span="5">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0;">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0">
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0;">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="联系方式" prop="phone" style="margin-bottom: 0;">
<el-form-item label="联系方式" prop="phone" style="margin-bottom: 0">
<el-input v-model="queryParams.phone" placeholder="请输入联系方式" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -38,10 +38,14 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="success" plain icon="Select" :disabled="multiple" @click="handleBatchApprove" v-hasPermi="['stgl:volunteermember:edit']">批量通过</el-button>
<el-button type="success" plain icon="Select" :disabled="multiple" @click="handleBatchApprove" v-hasPermi="['stgl:volunteermember:edit']"
>批量通过</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Close" :disabled="multiple" @click="handleBatchReject" v-hasPermi="['stgl:volunteermember:edit']">批量拒绝</el-button>
<el-button type="danger" plain icon="Close" :disabled="multiple" @click="handleBatchReject" v-hasPermi="['stgl:volunteermember:edit']"
>批量拒绝</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@@ -130,12 +134,7 @@
<script setup lang="ts">
import { getCurrentInstance, onMounted, reactive, ref, toRefs } from 'vue';
import {
listVolunteerMember,
getVolunteerMember,
updateVolunteerMember,
approveVolunteerMember
} from '@/api/stgl/volunteermember';
import { listVolunteerMember, getVolunteerMember, updateVolunteerMember, approveVolunteerMember } from '@/api/stgl/volunteermember';
import { VolunteerMemberVO, VolunteerMemberQuery } from '@/api/stgl/volunteermember/types';
import { parseTime } from '@/utils/ruoyi';
@@ -147,7 +146,9 @@ interface Props {
const props = defineProps<Props>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_user_sex, sys_normal_disable, stgl_member_status } = toRefs<any>(proxy?.useDict('sys_user_sex', 'sys_normal_disable', 'stgl_member_status'));
const { sys_user_sex, sys_normal_disable, stgl_member_status } = toRefs<any>(
proxy?.useDict('sys_user_sex', 'sys_normal_disable', 'stgl_member_status')
);
const memberList = ref<VolunteerMemberVO[]>([]);
const loading = ref(true);
@@ -204,19 +205,19 @@ const validateMemberData = (member: VolunteerMemberVO, operation: string): boole
console.error(`${operation}失败 - 用户ID缺失:`, member);
return false;
}
if (!member.relationId && !props.teamId) {
proxy?.$modal.msgError(`${operation}失败:关联ID(志愿者队伍ID)不能为空`);
console.error(`${operation}失败 - 关联ID缺失:`, member);
return false;
}
if (!member.studentNo || !member.studentName) {
proxy?.$modal.msgError(`${operation}失败:学号和姓名不能为空`);
console.error(`${operation}失败 - 基础信息缺失:`, member);
return false;
}
return true;
};
@@ -226,31 +227,31 @@ const getList = async () => {
try {
// 构建查询参数,支持多状态查询
const queryData = { ...queryParams.value };
console.log('PendingMembersTab: getList 开始查询,参数:', queryData);
// 构建params对象,包含所有需要在params中的查询条件
queryData.params = { ...queryData.params } as any;
// 将phone等查询条件放入params中(后端期望params.phone)
if (queryData.phone) {
(queryData.params as any).phone = queryData.phone;
console.log('PendingMembersTab: phone参数放入params:', queryData.phone);
delete queryData.phone; // 删除顶级phone字段
}
// 如果状态字段为空或未选择,则查询待审核(status=0)和已驳回(status=3)的成员
if (!queryData.status) {
// 使用params传递多状态查询,保留现有params内容,传递字符串格式
(queryData.params as any).statusList = '0,3';
delete queryData.status; // 删除单个status字段
}
console.log('PendingMembersTab: 最终查询参数:', queryData);
const res = await listVolunteerMember(queryData);
console.log('PendingMembersTab: 查询结果:', res);
// 防御性编程:检查响应数据结构
if (!res || !res.rows) {
console.warn('PendingMembersTab: API返回数据格式异常:', res);
@@ -259,7 +260,7 @@ const getList = async () => {
} else {
memberList.value = res.rows;
total.value = res.total;
// 调试:检查数据完整性
if (memberList.value.length > 0) {
console.log('PendingMembersTab: 第一条成员数据:', memberList.value[0]);
@@ -322,9 +323,9 @@ const handleApprove = async (row: VolunteerMemberVO) => {
if (!validateMemberData(row, '审核通过')) {
return;
}
await proxy?.$modal.confirm(`确认通过"${row.studentName}"的入队申请?`);
await approveVolunteerMember(row.id, true);
proxy?.$modal.msgSuccess('审核通过成功');
await getList();
@@ -336,9 +337,9 @@ const handleReject = async (row: VolunteerMemberVO) => {
if (!validateMemberData(row, '审核拒绝')) {
return;
}
await proxy?.$modal.confirm(`确认拒绝"${row.studentName}"的入队申请?`);
await approveVolunteerMember(row.id, false);
proxy?.$modal.msgSuccess('审核拒绝成功');
await getList();
@@ -350,25 +351,25 @@ const handleBatchApprove = async () => {
proxy?.$modal.msgWarning('请选择要审核的成员');
return;
}
await proxy?.$modal.confirm(`确认批量通过所选成员的入队申请?`);
const promises = ids.value.map(id => {
const promises = ids.value.map((id) => {
// 根据ID找到对应的成员记录,进行数据验证
const member = memberList.value.find(m => m.id === id);
const member = memberList.value.find((m) => m.id === id);
if (!member) {
proxy?.$modal.msgError(`未找到ID为${id}的成员记录`);
return Promise.reject(new Error(`未找到ID为${id}的成员记录`));
}
// 验证数据完整性
if (!validateMemberData(member, '批量审核通过')) {
return Promise.reject(new Error(`成员${member.studentName}数据不完整`));
}
return approveVolunteerMember(id, true);
});
await Promise.all(promises);
proxy?.$modal.msgSuccess('批量审核通过成功');
await getList();
@@ -380,25 +381,25 @@ const handleBatchReject = async () => {
proxy?.$modal.msgWarning('请选择要审核的成员');
return;
}
await proxy?.$modal.confirm(`确认批量拒绝所选成员的入队申请?`);
const promises = ids.value.map(id => {
const promises = ids.value.map((id) => {
// 根据ID找到对应的成员记录,进行数据验证
const member = memberList.value.find(m => m.id === id);
const member = memberList.value.find((m) => m.id === id);
if (!member) {
proxy?.$modal.msgError(`未找到ID为${id}的成员记录`);
return Promise.reject(new Error(`未找到ID为${id}的成员记录`));
}
// 验证数据完整性
if (!validateMemberData(member, '批量审核拒绝')) {
return Promise.reject(new Error(`成员${member.studentName}数据不完整`));
}
return approveVolunteerMember(id, false);
});
await Promise.all(promises);
proxy?.$modal.msgSuccess('批量审核拒绝成功');
await getList();
@@ -486,4 +487,4 @@ onMounted(() => {
min-width: auto;
}
}
</style>
</style>
+1 -1
View File
@@ -1199,7 +1199,7 @@ const handleActivityManagement = (row) => {
relatedTeamName: row.teamName,
from: 'team'
});
router.push({
path: '/stgl/activity/info',
query: {
@@ -4,4 +4,4 @@ export type {
XskhAssessmentIndicatorTypeBo,
XskhAssessmentIndicatorTypeQuery,
XskhAssessmentIndicatorTypeForm
} from '@/api/xskh/assessmentIndicatorType/types';
} from '@/api/xskh/assessmentIndicatorType/types';
+11 -5
View File
@@ -78,7 +78,7 @@
<!-- 操作按钮行 -->
<el-row :gutter="10" class="mb8">
<el-col :span="24" class="search-btn-container">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -97,16 +97,22 @@
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['zsjy:enrollment:info:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['zsjy:enrollment:info:edit']">修改</el-button>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['zsjy:enrollment:info:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['zsjy:enrollment:info:remove']">删除</el-button>
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['zsjy:enrollment:info:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['zsjy:enrollment:info:export']">导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Check" :disabled="multiple" @click="handleBatchAudit()" v-hasPermi="['zsjy:enrollment:info:edit']">批量审核</el-button>
<el-button type="success" plain icon="Check" :disabled="multiple" @click="handleBatchAudit()" v-hasPermi="['zsjy:enrollment:info:edit']"
>批量审核</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@@ -1606,7 +1612,7 @@ const handleBatchUpdate = async () => {
return;
}
// 如果只选择了一条记录,调用单个修改方法
const row = enrollmentInfoList.value.find(item => item.id === ids.value[0]);
const row = enrollmentInfoList.value.find((item) => item.id === ids.value[0]);
if (row) {
handleUpdate(row);
}
+1 -7
View File
@@ -474,13 +474,7 @@
<script setup name="GraduateInfo" lang="ts">
import { ref, reactive, getCurrentInstance, onMounted } from 'vue';
import {
listGraduateInfo,
getGraduateInfo,
delGraduateInfo,
exportGraduateInfo,
getOverallStatistics
} from '@/api/zsjy/graduateInfo';
import { listGraduateInfo, getGraduateInfo, delGraduateInfo, exportGraduateInfo, getOverallStatistics } from '@/api/zsjy/graduateInfo';
import { GraduateInfoVO, GraduateInfoQuery, GraduateStatisticsVO } from '@/api/zsjy/types';
const { proxy } = getCurrentInstance() as any;
+3 -13
View File
@@ -55,7 +55,7 @@
<!-- 第三行:搜索按钮 -->
<el-row :gutter="10" class="mb8">
<el-col :span="24" class="search-btn-container">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -186,20 +186,10 @@
<el-table-column label="操作" width="160" align="center" class-name="small-padding fixed-width" fixed="right">
<template #default="scope">
<el-tooltip content="查看详情" placement="top">
<el-button
link
type="info"
icon="View"
@click="handleView(scope.row)"
></el-button>
<el-button link type="info" icon="View" @click="handleView(scope.row)"></el-button>
</el-tooltip>
<el-tooltip content="查看日历" placement="top">
<el-button
link
type="success"
icon="Calendar"
@click="handleCalendar(scope.row)"
></el-button>
<el-button link type="success" icon="Calendar" @click="handleCalendar(scope.row)"></el-button>
</el-tooltip>
</template>
</el-table-column>
@@ -56,7 +56,7 @@
<!-- 搜索按钮行 -->
<el-row :gutter="10" class="mb8">
<el-col :span="24" class="search-btn-container">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -170,31 +170,13 @@
<el-table-column label="操作" width="280" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="查看详情" placement="top">
<el-button
link
type="info"
icon="View"
@click="handleView(scope.row)"
v-hasPermi="['zsjy:internship:query']"
></el-button>
<el-button link type="info" icon="View" @click="handleView(scope.row)" v-hasPermi="['zsjy:internship:query']"></el-button>
</el-tooltip>
<el-tooltip content="新增实习信息" placement="top" v-if="!scope.row.internshipId">
<el-button
link
type="success"
icon="Plus"
@click="handleAdd(scope.row)"
v-hasPermi="['zsjy:internship:add']"
></el-button>
<el-button link type="success" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['zsjy:internship:add']"></el-button>
</el-tooltip>
<el-tooltip content="编辑实习信息" placement="top" v-if="scope.row.internshipId">
<el-button
link
type="primary"
icon="Edit"
@click="handleEdit(scope.row)"
v-hasPermi="['zsjy:internship:edit']"
></el-button>
<el-button link type="primary" icon="Edit" @click="handleEdit(scope.row)" v-hasPermi="['zsjy:internship:edit']"></el-button>
</el-tooltip>
</template>
</el-table-column>
@@ -37,7 +37,7 @@
</el-row>
<el-row :gutter="10" class="mb8">
<el-col :span="24" class="search-btn-container">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -56,33 +56,17 @@
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['zsjy:internshipmonthly:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate()"
v-hasPermi="['zsjy:internshipmonthly:edit']"
>修改</el-button>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['zsjy:internshipmonthly:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
v-hasPermi="['zsjy:internshipmonthly:remove']"
>删除</el-button>
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['zsjy:internshipmonthly:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['zsjy:internshipmonthly:export']"
>导出</el-button>
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['zsjy:internshipmonthly:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@@ -144,16 +128,27 @@
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['zsjy:internshipmonthly:edit']"></el-button>
</el-tooltip>
<el-tooltip content="提交" placement="top" v-if="scope.row.reviewStatus === 3">
<el-button link type="success" icon="Check" @click="handleSubmit(scope.row)" v-hasPermi="['zsjy:internshipmonthly:submit']"></el-button>
<el-button
link
type="success"
icon="Check"
@click="handleSubmit(scope.row)"
v-hasPermi="['zsjy:internshipmonthly:submit']"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top" v-if="scope.row.reviewStatus === 3">
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['zsjy:internshipmonthly:remove']"></el-button>
<el-button
link
type="danger"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['zsjy:internshipmonthly:remove']"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</div>
</el-card>
<!-- 分页 -->
+9 -31
View File
@@ -52,7 +52,7 @@
<!-- 第三行:搜索按钮 -->
<el-row :gutter="10" class="mb8">
<el-col :span="24" class="search-btn-container">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -71,33 +71,17 @@
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['zsjy:internshipVisit:add']">新增走访</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleBatchUpdate"
v-hasPermi="['zsjy:internshipVisit:edit']"
>修改</el-button>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleBatchUpdate" v-hasPermi="['zsjy:internshipVisit:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleBatchDelete"
v-hasPermi="['zsjy:internshipVisit:remove']"
>删除</el-button>
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleBatchDelete" v-hasPermi="['zsjy:internshipVisit:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['zsjy:internshipVisit:export']"
>导出</el-button>
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['zsjy:internshipVisit:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@@ -335,8 +319,6 @@ const viewDialogVisible = ref(false);
const formDialogVisible = ref(false);
const currentRecord = ref(null);
// 字典数据
const visitStatusOptions = ref([]);
const studentOptions = ref([]);
@@ -449,8 +431,6 @@ function getList() {
});
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.pageNum = 1;
@@ -540,7 +520,7 @@ function handleBatchUpdate() {
if (ids.value.length === 1) {
// 单个修改,直接调用修改方法
const visitId = ids.value[0];
const row = visitList.value.find(item => item.visitId === visitId);
const row = visitList.value.find((item) => item.visitId === visitId);
if (row) {
handleUpdate(row);
}
@@ -782,8 +762,6 @@ onMounted(() => {
padding: 8px;
}
/* 表格卡片布局 */
.table-card {
margin-top: 0;
+15 -3
View File
@@ -84,10 +84,14 @@
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['zsjy:position:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['zsjy:position:edit']">修改</el-button>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['zsjy:position:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['zsjy:position:remove']">删除</el-button>
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['zsjy:position:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['zsjy:position:export']">导出</el-button>
@@ -260,7 +264,15 @@
</template>
<script setup name="JobPosition" lang="ts">
import { listJobPosition, getJobPosition, delJobPosition, addJobPosition, updateJobPosition, exportJobPosition, listEnterpriseOptions } from '@/api/zsjy/jobPosition';
import {
listJobPosition,
getJobPosition,
delJobPosition,
addJobPosition,
updateJobPosition,
exportJobPosition,
listEnterpriseOptions
} from '@/api/zsjy/jobPosition';
import { JobPositionVO, JobPositionForm, JobPositionQuery } from '@/api/zsjy/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+20 -10
View File
@@ -63,7 +63,9 @@
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate" v-hasPermi="['zsjy:poster:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['zsjy:poster:remove']">删除</el-button>
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete" v-hasPermi="['zsjy:poster:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['zsjy:poster:export']">导出</el-button>
@@ -105,8 +107,22 @@
:preview="true"
style="cursor: pointer"
/>
<div v-else style="width: 80px; height: 80px; background: #fafafa; display: flex; align-items: center; justify-content: center; font-size: 12px; color: #ccc; border: 1px dashed #d9d9d9; border-radius: 4px;">
<div style="text-align: center;">
<div
v-else
style="
width: 80px;
height: 80px;
background: #fafafa;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: #ccc;
border: 1px dashed #d9d9d9;
border-radius: 4px;
"
>
<div style="text-align: center">
<el-icon><Picture /></el-icon>
<div>未上传</div>
</div>
@@ -185,13 +201,7 @@
<div v-if="previewOpen && previewData.posterPath" class="poster-preview-container">
<!-- 海报图片预览 -->
<div class="poster-image-section">
<image-preview
:src="previewData.posterPath"
:width="400"
:height="600"
:preview="true"
style="margin: 0 auto;"
/>
<image-preview :src="previewData.posterPath" :width="400" :height="600" :preview="true" style="margin: 0 auto" />
</div>
<!-- 海报详情信息 -->