This commit is contained in:
李旭明 committed 2025-08-27 10:16:15 +08:00
1 parent 93711d76a3
commit 73ffd576bc
63 files changed
+5064 -647

No files matched your search

+51 -1
View File
@@ -582,6 +582,16 @@ export interface ActivityCopyParams {
* 新活动名称
*/
newActivityName: string;
/**
* 新活动状态(复制后默认为草稿状态)
*/
activityStatus?: string;
/**
* 新活动报名状态(复制后默认为未开始状态)
*/
registrationStatus?: string;
}
/**
@@ -639,10 +649,50 @@ export interface ActivityRatingVO {
userName?: string;
/**
* 评分(1-5分)
* 评分人学号
*/
studentNo?: string;
/**
* 评分人联系方式
*/
phoneNumber?: string;
/**
* 评分人院系
*/
deptName?: string;
/**
* 评分人专业
*/
majorName?: string;
/**
* 评分人班级
*/
className?: string;
/**
* 总评分(1-5分)
*/
ratingScore: number;
/**
* 内容评分(1-5分)
*/
contentScore?: number;
/**
* 组织评分(1-5分)
*/
organizationScore?: number;
/**
* 体验评分(1-5分)
*/
experienceScore?: number;
/**
* 评分备注
*/
@@ -369,6 +369,11 @@ export interface StglActivityRegistrationForm {
* 班级
*/
className?: string;
/**
* 审核状态(0-待审核 1-通过 2-驳回)
*/
approvalStatus?: number;
}
// 团队成员信息
@@ -0,0 +1,62 @@
import request from '@/utils/request';
import { StglActivityTeamInfoVo } from './types';
// 查询活动团队信息列表
export function listActivityTeamInfo(query?: any) {
return request({
url: '/stgl/activityteaminfo/list',
method: 'get',
params: query
});
}
// 查询活动团队信息详细
export function getActivityTeamInfo(id: string | number) {
return request({
url: '/stgl/activityteaminfo/' + id,
method: 'get'
});
}
// 新增活动团队信息
export function addActivityTeamInfo(data: any) {
return request({
url: '/stgl/activityteaminfo',
method: 'post',
data: data
});
}
// 修改活动团队信息
export function updateActivityTeamInfo(data: any) {
return request({
url: '/stgl/activityteaminfo',
method: 'put',
data: data
});
}
// 删除活动团队信息
export function delActivityTeamInfo(ids: string | number | Array<string | number>) {
return request({
url: '/stgl/activityteaminfo/' + ids,
method: 'delete'
});
}
// 导出活动团队信息
export function exportActivityTeamInfo(query?: any) {
return request({
url: '/stgl/activityteaminfo/export',
method: 'post',
data: query
});
}
// 根据活动ID查询团队列表
export function getTeamsByActivityId(activityId: string | number) {
return request({
url: '/stgl/activityteaminfo/activity/' + activityId,
method: 'get'
});
}
+241
View File
@@ -0,0 +1,241 @@
// 团队成员信息
export interface TeamMemberInfo {
/**
* 用户ID
*/
userId: string | number;
/**
* 学号
*/
studentNo: string;
/**
* 姓名
*/
studentName: string;
/**
* 性别
*/
gender?: string;
/**
* 联系电话
*/
phone?: string;
/**
* 邮箱
*/
email?: string;
/**
* 院系
*/
department?: string;
/**
* 专业
*/
major?: string;
/**
* 班级
*/
className?: string;
/**
* 是否为队长(0-否 1-是)
*/
isLeader?: number;
}
// 活动团队信息表单对象
export interface StglActivityTeamInfoForm {
/**
* 主键ID
*/
id?: string | number;
/**
* 团队名称
*/
teamName: string;
/**
* 关联活动ID
*/
activityId: string | number;
/**
* 队长用户ID
*/
leaderUserId: string | number;
/**
* 队长姓名
*/
leaderName: string;
/**
* 队长学号
*/
leaderStudentNo: string;
/**
* 团队成员信息(JSON格式)
*/
teamMembers?: string;
/**
* 团队人数
*/
teamSize?: number;
/**
* 团队简介
*/
description?: string;
}
// 活动团队信息查询对象
export interface StglActivityTeamInfoQuery {
/**
* 页码
*/
pageNum?: number;
/**
* 每页数量
*/
pageSize?: number;
/**
* 团队名称
*/
teamName?: string;
/**
* 关联活动ID
*/
activityId?: string | number;
/**
* 队长用户ID
*/
leaderUserId?: string | number;
/**
* 队长姓名
*/
leaderName?: string;
/**
* 队长学号
*/
leaderStudentNo?: string;
}
// 活动团队信息视图对象
export interface StglActivityTeamInfoVo {
/**
* 主键ID
*/
id: string | number;
/**
* 团队名称
*/
teamName: string;
/**
* 关联活动ID
*/
activityId: string | number;
/**
* 队长用户ID
*/
leaderUserId: string | number;
/**
* 队长姓名
*/
leaderName: string;
/**
* 队长学号
*/
leaderStudentNo: string;
/**
* 团队成员信息(JSON格式)
*/
teamMembers?: string;
/**
* 团队人数
*/
teamSize?: number;
/**
* 团队简介
*/
description?: string;
/**
* 队长性别
*/
leaderGender?: string;
/**
* 队长联系电话
*/
leaderPhone?: string;
/**
* 队长邮箱
*/
leaderEmail?: string;
/**
* 队长院系
*/
leaderDepartment?: string;
/**
* 队长专业
*/
leaderMajor?: string;
/**
* 队长班级
*/
leaderClassName?: string;
/**
* 活动名称
*/
activityName?: string;
/**
* 创建者
*/
createBy?: string | number;
/**
* 创建时间
*/
createTime?: string;
/**
* 更新者
*/
updateBy?: string | number;
/**
* 更新时间
*/
updateTime?: string;
}
+5 -1
View File
@@ -93,12 +93,14 @@ export interface MyActivityVO {
location?: string;
/** 主办方名称 */
organizerName?: string;
/** 承办方名称 */
undertakerName?: string;
/** 关联类型(1-社团 2-志愿者队伍) */
relatedType?: number;
/** 关联组织名称(社团名称或志愿者团队名称) */
organizationName?: string;
/** 指导老师 */
instructors?: string;
instructors?: string | any[];
/** 活动状态 */
activityStatus: string;
/** 报名状态 */
@@ -111,6 +113,8 @@ export interface MyActivityVO {
myRegistrationTime: string;
/** 我的报名状态 */
myRegistrationStatus: string;
/** 报名类型(1-个人 2-团队) */
registrationType: string;
/** 我的评分状态(0-未评分 1-已评分) */
myRatingStatus: number;
/** 我的综合评分 */
+64
View File
@@ -0,0 +1,64 @@
import request from '@/utils/request';
import { StglClassQuery, StglClassVO, StglClassDetailVO } from './types';
/**
* 查询班主任的班级列表
*/
export function getTeacherClasses() {
return request({
url: '/stgl/stclass/teacher/classes',
method: 'get'
});
}
/**
* 查询本班学生社团、志愿者队伍、活动参加情况列表
*/
export function listStglClass(query: StglClassQuery) {
return request({
url: '/stgl/stclass/list',
method: 'get',
params: query
});
}
/**
* 查询学生社团详情
*/
export function getStudentClubs(studentId: string | number) {
return request({
url: `/stgl/stclass/student/clubs/${studentId}`,
method: 'get'
});
}
/**
* 查询学生志愿者队伍详情
*/
export function getStudentVolunteerTeams(studentId: string | number) {
return request({
url: `/stgl/stclass/student/volunteer-teams/${studentId}`,
method: 'get'
});
}
/**
* 查询学生活动参加详情
*/
export function getStudentActivities(studentId: string | number) {
return request({
url: `/stgl/stclass/student/activities/${studentId}`,
method: 'get'
});
}
/**
* 导出本班学生社团、志愿者队伍、活动参加情况
*/
export function exportStglClass(query: StglClassQuery) {
return request({
url: '/stgl/stclass/export',
method: 'get',
params: query
});
}
+292
View File
@@ -0,0 +1,292 @@
/**
* 分页查询基础接口
*/
export interface PageQuery {
pageNum?: number;
pageSize?: number;
}
/**
* 班级学生社团、志愿者队伍、活动参加情况查询对象
*/
export interface StglClassQuery extends PageQuery {
/**
* 班级ID
*/
classId?: string | number;
/**
* 学生姓名
*/
studentName?: string;
/**
* 学号
*/
studentNo?: string;
}
/**
* 班级学生社团、志愿者队伍、活动参加情况视图对象
*/
export interface StglClassVO {
/**
* 学生ID
*/
id: string | number;
/**
* 学号
*/
studentNo: string;
/**
* 学生姓名
*/
studentName: string;
/**
* 性别
*/
gender: string;
/**
* 联系电话
*/
phone: string;
/**
* 班级名称
*/
className: string;
/**
* 加入社团数量
*/
clubCount: number;
/**
* 加入志愿者队伍数量
*/
volunteerTeamCount: number;
/**
* 参与活动数量
*/
activityCount: number;
}
/**
* 社团详情对象
*/
export interface ClubDetailVO {
/**
* 社团ID
*/
id: string | number;
/**
* 社团名称
*/
clubName: string;
/**
* 社团类型
*/
clubType: string;
/**
* 成立日期
*/
establishDate: string;
/**
* 成员规模
*/
memberLimit: number;
/**
* 社长姓名
*/
presidentName: string;
/**
* 社长联系方式
*/
presidentPhone: string;
/**
* 社长院系
*/
presidentDept: string;
/**
* 社长专业
*/
presidentMajor: string;
/**
* 社长班级
*/
presidentClass: string;
/**
* 指导老师姓名
*/
advisorName: string;
/**
* 指导老师部门
*/
advisorDept: string;
/**
* 指导老师电话
*/
advisorPhone: string;
}
/**
* 志愿者队伍详情对象
*/
export interface VolunteerTeamDetailVO {
/**
* 队伍ID
*/
id: string | number;
/**
* 队伍名称
*/
teamName: string;
/**
* 成立日期
*/
establishDate: string;
/**
* 队长姓名
*/
leaderName: string;
/**
* 队长联系方式
*/
leaderPhone: string;
/**
* 队长院系
*/
leaderDept: string;
/**
* 队长专业
*/
leaderMajor: string;
/**
* 队长班级
*/
leaderClass: string;
/**
* 指导老师姓名
*/
advisorName: string;
/**
* 指导老师部门
*/
advisorDept: string;
/**
* 指导老师电话
*/
advisorPhone: string;
}
/**
* 活动详情对象
*/
export interface ActivityDetailVO {
/**
* 活动ID
*/
id: string | number;
/**
* 活动名称
*/
activityName: string;
/**
* 活动类型
*/
activityType: string;
/**
* 活动主题
*/
activityTheme: string;
/**
* 活动地点
*/
location: string;
/**
* 活动时间
*/
activityTime: string;
/**
* 关联社团/队伍
*/
relatedOrganization: string;
/**
* 指导老师姓名
*/
advisorName: string;
/**
* 指导老师部门
*/
advisorDept: string;
/**
* 指导老师电话
*/
advisorPhone: string;
}
/**
* 班级详情对象
*/
export interface StglClassDetailVO {
/**
* 班级ID
*/
id: string | number;
/**
* 班级名称
*/
className: string;
/**
* 学院名称
*/
collegeName: string;
/**
* 专业名称
*/
majorName: string;
/**
* 年级
*/
grade: string;
}
+57 -109
View File
@@ -15,7 +15,11 @@
<el-row :gutter="2" align="middle">
<el-col :span="5">
<el-form-item label="姓名" :prop="`advisors.${index}.name`">
<TeacherSelector v-model="advisor.teacher" placeholder="选择指导老师" />
<TeacherSelector
:model-value="advisor.teacher"
placeholder="选择指导老师"
@update:model-value="(teacher) => handleTeacherChange(advisor, teacher)"
/>
</el-form-item>
</el-col>
<el-col :span="6">
@@ -48,7 +52,7 @@
</template>
<script setup lang="ts">
import { ref, watch, nextTick, computed } from 'vue';
import { ref, watch, computed } from 'vue';
import { Plus, Delete } from '@element-plus/icons-vue';
import TeacherSelector from '@/components/TeacherSelector/index.vue';
import { TeacherVO } from '@/api/system/teacher/types';
@@ -59,6 +63,7 @@ export interface AdvisorVO {
phone: string;
dept: string;
email: string;
userId?: string;
}
interface Props {
@@ -73,131 +78,74 @@ interface Emits {
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const advisors = ref<AdvisorVO[]>([]);
const updateLock = ref(false);
const advisors = ref<AdvisorVO[]>([...props.modelValue]);
// 计算属性:生成当前数据的hash值,用于检测变化
const currentHash = computed(() => {
return JSON.stringify(
advisors.value.map((advisor) => ({
userId: advisor.teacher?.userId,
name: advisor.name,
phone: advisor.phone,
dept: advisor.dept,
email: advisor.email
}))
);
});
// 监听外部值变化
// 监听props变化
watch(
() => props.modelValue,
(newValue) => {
console.log('AdvisorManager - 外部值变化:', newValue);
if (!updateLock.value) {
updateLock.value = true;
nextTick(() => {
// 确保深拷贝,避免引用问题
advisors.value = newValue ? newValue.map((item) => ({ ...item })) : [];
console.log('AdvisorManager - 更新内部值:', advisors.value);
updateLock.value = false;
});
}
advisors.value = newValue ? newValue.map((item) => ({ ...item })) : [];
},
{ immediate: true, deep: false }
{ deep: true }
);
// 监听内部值变化 - 使用hash比较避免深度比较
watch(currentHash, (newHash, oldHash) => {
// 避免初始化时的重复触发
if (oldHash === undefined) return;
console.log('AdvisorManager - 内部值变化检测到:', newHash);
if (!updateLock.value) {
updateLock.value = true;
// 延迟emit,避免在TabPane环境下的立即更新冲突
nextTick(() => {
const newValue = advisors.value.map((advisor) => ({ ...advisor }));
console.log('AdvisorManager - 发送更新事件:', newValue);
emit('update:modelValue', newValue);
// 重新添加change事件,但添加防护机制
emit('change', newValue);
updateLock.value = false;
});
}
});
// 监听内部数据变化,同步到父组件
watch(
advisors,
(newValue) => {
emit('update:modelValue', newValue);
emit('change', newValue);
},
{ deep: true }
);
// 添加指导老师
const addAdvisor = () => {
// 使用函数式更新,确保响应性
advisors.value = [
...advisors.value,
{
teacher: null,
name: '',
phone: '',
dept: '',
email: ''
}
];
advisors.value.push({
teacher: null,
name: '',
phone: '',
dept: '',
email: '',
userId: ''
});
};
// 删除指导老师
const removeAdvisor = (index: number) => {
// 使用函数式更新,确保响应性
advisors.value = advisors.value.filter((_, i) => i !== index);
advisors.value.splice(index, 1);
};
// 监听teacher选择变化,自动填充相关信息
watch(
() => advisors.value.map((advisor) => advisor.teacher),
(newTeachers, oldTeachers) => {
console.log('AdvisorManager - teacher选择变化监听器触发');
console.log('newTeachers:', newTeachers);
console.log('oldTeachers:', oldTeachers);
newTeachers.forEach((teacher, index) => {
const oldTeacher = oldTeachers?.[index];
console.log(`检查第${index}个指导老师:`, {
newTeacher: teacher,
oldTeacher: oldTeacher,
hasChanged: teacher && (!oldTeacher || teacher.userId !== oldTeacher?.userId)
});
// 检查是否是新选择的老师或者老师发生了变化
if (teacher && (!oldTeacher || teacher.userId !== oldTeacher.userId)) {
console.log('选择了新的指导老师:', teacher);
const advisor = advisors.value[index];
if (advisor) {
// 自动填充老师信息
advisor.name = teacher.nickName || '';
advisor.phone = teacher.phonenumber || '';
advisor.dept = teacher.deptName || '';
advisor.email = teacher.email || '';
console.log('自动填充的信息:', {
name: advisor.name,
phone: advisor.phone,
dept: advisor.dept,
email: advisor.email
});
}
} else if (!teacher && oldTeacher) {
// 清空选择时重置所有信息
console.log('清空指导老师选择');
const advisor = advisors.value[index];
if (advisor) {
advisor.name = '';
advisor.phone = '';
advisor.dept = '';
advisor.email = '';
}
}
const handleTeacherChange = (advisor: AdvisorVO, teacher: TeacherVO | null) => {
console.log('handleTeacherChange called with teacher:', teacher);
if (teacher) {
// 自动填充老师信息
advisor.name = teacher.nickName || '';
advisor.phone = teacher.phonenumber || '暂无电话';
advisor.dept = teacher.deptName || '';
advisor.email = teacher.email || '暂无邮箱';
advisor.userId = teacher.userId?.toString() || '';
console.log('Updated advisor:', {
name: advisor.name,
phone: advisor.phone,
dept: advisor.dept,
email: advisor.email,
userId: advisor.userId
});
},
{ deep: true, immediate: false }
);
} else {
// 清空选择时重置所有信息
advisor.name = '';
advisor.phone = '';
advisor.dept = '';
advisor.email = '';
advisor.userId = '';
}
};
// 移除有问题的深度监听,改为在TeacherSelector的change事件中处理
</script>
<style scoped>
@@ -61,7 +61,7 @@
</el-col>
<el-col :span="12">
<div class="detail-item">
<label class="detail-label">关联社团</label>
<label class="detail-label">关联社团/队伍</label>
<div class="detail-value">{{ activity.relatedClubName || '暂无' }}</div>
</div>
</el-col>
@@ -203,7 +203,10 @@
<div class="detail-item">
<label class="detail-label">是否需要审核</label>
<div class="detail-value">
<dict-tag :options="sys_yes_no" :value="activity.needApproval" />
<!-- 临时解决方案:直接显示中文文本 -->
<el-tag v-if="activity.needApproval === 1" type="warning" size="small">需要审核</el-tag>
<el-tag v-else-if="activity.needApproval === 0" type="success" size="small">无需审核</el-tag>
<span v-else class="text-gray-400">{{ activity.needApproval }}</span>
</div>
</div>
</el-col>
@@ -339,25 +342,6 @@
<!-- 活动评分标签页 -->
<el-tab-pane label="活动评分" name="rating" v-if="activity.id">
<ActivityRatingView :activity-id="activity.id" />
<!-- 统计信息区域(只在活动评分标签页显示) -->
<div class="statistics-section">
<el-divider content-position="left">统计信息</el-divider>
<el-row :gutter="20">
<el-col :span="8">
<el-statistic title="报名人数" :value="activity.registrationCount || 0" />
</el-col>
<el-col :span="8">
<el-statistic title="平均评分" :value="activity.averageRating || 0" :precision="1" />
</el-col>
<el-col :span="8">
<div class="statistic-item">
<div class="statistic-title">创建时间</div>
<div class="statistic-value">{{ activity.createTime || '暂无' }}</div>
</div>
</el-col>
</el-row>
</div>
</el-tab-pane>
</el-tabs>
</div>
@@ -366,12 +350,13 @@
<script setup lang="ts">
import { computed, ref, onMounted, watch } from 'vue';
import { StglActivityVO, CoOrganizerInfo, InstructorInfo } from '@/api/stgl/activity/types';
import { StglActivityVo } from '@/api/stgl/activityregisterhall/types';
import ActivityRatingView from './ActivityRatingView.vue';
import ActivityRegistrationView from './ActivityRegistrationView.vue';
import { listByIds } from '@/api/system/oss';
interface Props {
activity: StglActivityVO;
activity: StglActivityVO | StglActivityVo;
}
const props = defineProps<Props>();
@@ -381,6 +366,8 @@ const { stgl_activity_type, stgl_activity_status, stgl_registration_type, stgl_r
proxy?.useDict('stgl_activity_type', 'stgl_activity_status', 'stgl_registration_type', 'stgl_registration_status', 'sys_yes_no')
);
// 当前激活的Tab页签
const activeTab = ref('basic');
@@ -414,8 +401,8 @@ const loadAttachments = async () => {
summaryAttachmentList.value = [];
achievementsAttachmentList.value = [];
// 加载总结报告附件
if (props.activity.summaryAttachments) {
// 加载活动总结附件
if (props.activity && 'summaryAttachments' in props.activity && props.activity.summaryAttachments) {
// 检查数据格式:如果以 [ 开头,说明是JSON格式;否则是ossId格式
const attachmentStr = String(props.activity.summaryAttachments);
if (attachmentStr.trim().startsWith('[')) {
@@ -423,7 +410,7 @@ const loadAttachments = async () => {
// JSON格式:直接解析
summaryAttachmentList.value = JSON.parse(attachmentStr);
} catch (error) {
console.error('总结报告附件JSON解析失败:', error);
console.error('活动总结附件JSON解析失败:', error);
summaryAttachmentList.value = [];
}
} else {
@@ -438,7 +425,7 @@ const loadAttachments = async () => {
ossId: oss.ossId
}));
} catch (apiError) {
console.error('获取总结报告附件信息失败:', apiError);
console.error('获取活动总结附件信息失败:', apiError);
summaryAttachmentList.value = [];
}
}
@@ -446,7 +433,7 @@ const loadAttachments = async () => {
}
// 加载活动成果附件
if (props.activity.achievementsAttachments) {
if (props.activity && 'achievementsAttachments' in props.activity && props.activity.achievementsAttachments) {
// 检查数据格式:如果以 [ 开头,说明是JSON格式;否则是ossId格式
const attachmentStr = String(props.activity.achievementsAttachments);
if (attachmentStr.trim().startsWith('[')) {
@@ -622,26 +609,7 @@ onMounted(() => {
border: 1px solid #e9ecef;
}
.statistics-section {
margin-top: 20px;
margin-bottom: 20px;
}
.statistic-item {
text-align: center;
}
.statistic-title {
font-size: 14px;
color: #909399;
margin-bottom: 8px;
}
.statistic-value {
font-size: 24px;
font-weight: 500;
color: #303133;
}
/* 附件显示区域样式 */
.attachment-list {
@@ -1,6 +1,6 @@
<template>
<el-dialog :title="`${activityName}活动报名管理`" :model-value="visible" @update:model-value="handleVisibleChange" width="1400px" append-to-body>
<el-tabs v-model="activeTab" type="border-card">
<el-tabs v-model="activeTab" type="border-card" @tab-change="handleTabChange">
<!-- 已报名标签页 -->
<el-tab-pane label="已报名" name="registered">
<RegisteredMembersTab
@@ -16,6 +16,7 @@
:activity-id="activityId"
:activity-name="activityName"
ref="pendingTabRef"
@refresh-other-tabs="handleRefreshOtherTabs"
/>
</el-tab-pane>
</el-tabs>
@@ -80,6 +81,18 @@ const handleTabChange = (tabName: string) => {
});
};
// 处理其他标签页刷新
const handleRefreshOtherTabs = () => {
// 如果当前在待审核标签页,刷新已报名标签页
if (activeTab.value === 'pending' && registeredTabRef.value) {
registeredTabRef.value.getList();
}
// 如果当前在已报名标签页,刷新待审核标签页
else if (activeTab.value === 'registered' && pendingTabRef.value) {
pendingTabRef.value.getList();
}
};
// 关闭弹窗
const handleClose = () => {
emit('update:visible', false);
@@ -93,6 +106,15 @@ defineExpose({
} else if (activeTab.value === 'pending' && pendingTabRef.value) {
pendingTabRef.value.getList();
}
},
refreshAllTabs: () => {
// 刷新所有标签页
if (registeredTabRef.value) {
registeredTabRef.value.getList();
}
if (pendingTabRef.value) {
pendingTabRef.value.getList();
}
}
});
</script>
@@ -5,31 +5,85 @@
</div>
<div v-else>
<div class="rating-summary">
<el-row :gutter="20">
<el-col :span="8">
<el-statistic title="总评分数" :value="ratings.length" />
</el-col>
<el-col :span="8">
<el-statistic title="平均评分" :value="averageRating" :precision="1" />
</el-col>
<el-col :span="8">
<el-statistic title="最高评分" :value="maxRating" />
</el-col>
</el-row>
</div>
<el-divider />
<el-table :data="ratings" height="400" border>
<el-table-column label="评分人" prop="userName" width="120" />
<el-table-column label="评分" prop="ratingScore" width="120" align="center">
<!-- 移除统计信息部分 -->
<el-table :data="ratings" height="600" border stripe>
<!-- 基本信息 - 减少宽度 -->
<el-table-column label="评分人信息" width="160" fixed="left">
<template #default="scope">
<el-rate :model-value="scope.row.ratingScore" disabled size="small" />
<div class="user-info-compact">
<div class="user-name">{{ scope.row.userName || '未知用户' }}</div>
<div class="user-detail" v-if="scope.row.studentNo">{{ scope.row.studentNo }}</div>
<div class="user-detail" v-if="scope.row.phoneNumber">{{ scope.row.phoneNumber }}</div>
</div>
</template>
</el-table-column>
<!-- 所属信息 - 减少宽度 -->
<el-table-column label="所属信息" width="140">
<template #default="scope">
<div class="dept-info-compact">
<div class="dept-detail" v-if="scope.row.deptName">{{ scope.row.deptName }}</div>
<div class="dept-detail" v-if="scope.row.majorName">{{ scope.row.majorName }}</div>
</div>
</template>
</el-table-column>
<!-- 总评分 - 减少宽度 -->
<el-table-column label="总评分" width="100" align="center">
<template #default="scope">
<div class="rating-display-compact">
<el-rate :model-value="scope.row.ratingScore" disabled size="small" />
<div class="rating-number">{{ scope.row.ratingScore }}</div>
</div>
</template>
</el-table-column>
<!-- 各项分数 - 减少宽度 -->
<el-table-column label="内容" width="70" align="center">
<template #default="scope">
<div v-if="scope.row.contentScore" class="score-badge-small">
{{ scope.row.contentScore }}
</div>
<span v-else class="no-score">-</span>
</template>
</el-table-column>
<el-table-column label="组织" width="70" align="center">
<template #default="scope">
<div v-if="scope.row.organizationScore" class="score-badge-small">
{{ scope.row.organizationScore }}
</div>
<span v-else class="no-score">-</span>
</template>
</el-table-column>
<el-table-column label="体验" width="70" align="center">
<template #default="scope">
<div v-if="scope.row.experienceScore" class="score-badge-small">
{{ scope.row.experienceScore }}
</div>
<span v-else class="no-score">-</span>
</template>
</el-table-column>
<!-- 评分备注 - 扩大宽度,取消tooltip,直接显示完整内容 -->
<el-table-column label="评分备注" min-width="300">
<template #default="scope">
<div class="rating-comment-full">
{{ scope.row.ratingComment || '无备注' }}
</div>
</template>
</el-table-column>
<!-- 评分时间 - 简化显示格式,减少宽度 -->
<el-table-column label="评分时间" width="120" align="center" fixed="right">
<template #default="scope">
<div class="rating-time-compact">
{{ formatDateTimeCompact(scope.row.ratingTime) }}
</div>
</template>
</el-table-column>
<el-table-column label="评分备注" prop="ratingComment" show-overflow-tooltip />
<el-table-column label="评分时间" prop="ratingTime" width="160" align="center" />
</el-table>
</div>
</div>
@@ -49,19 +103,47 @@ const props = defineProps<Props>();
const ratings = ref<ActivityRatingVO[]>([]);
const loading = ref(false);
// 计算平均评分
// 修复平均评分计算 - 确保数值类型正确转换
const averageRating = computed(() => {
if (ratings.value.length === 0) return 0;
const sum = ratings.value.reduce((total, rating) => total + rating.ratingScore, 0);
return sum / ratings.value.length;
const sum = ratings.value.reduce((total, rating) => {
// 确保评分是数字类型
const score = typeof rating.ratingScore === 'string' ? parseFloat(rating.ratingScore) : rating.ratingScore;
return total + (isNaN(score) ? 0 : score);
}, 0);
return Math.round((sum / ratings.value.length) * 10) / 10; // 保留一位小数
});
// 计算最高评分
const maxRating = computed(() => {
if (ratings.value.length === 0) return 0;
return Math.max(...ratings.value.map((rating) => rating.ratingScore));
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;
}));
});
// 简化的日期时间格式化
const formatDateTimeCompact = (dateStr?: string): string => {
if (!dateStr) return '';
const date = new Date(dateStr);
return date.toLocaleString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
};
// 加载评分数据
const loadRatings = async () => {
if (!props.activityId) return;
@@ -70,6 +152,7 @@ const loadRatings = async () => {
try {
const res = await getActivityRatings(props.activityId);
ratings.value = res.data || [];
console.log('加载的评分数据:', ratings.value);
} catch (error: any) {
// 如果是404错误,说明后端暂未实现该功能,静默处理
if (error.response?.status === 404) {
@@ -93,12 +176,100 @@ onMounted(() => {
padding: 16px;
}
.rating-summary {
margin-bottom: 20px;
}
.empty-state {
text-align: center;
padding: 60px 20px;
}
/* 紧凑的评分人信息样式 */
.user-info-compact {
padding: 4px 2px;
line-height: 1.3;
}
.user-name {
font-weight: 600;
color: #303133;
font-size: 13px;
margin-bottom: 2px;
}
.user-detail {
font-size: 11px;
color: #606266;
line-height: 1.2;
}
/* 紧凑的所属信息样式 */
.dept-info-compact {
padding: 4px 2px;
line-height: 1.3;
}
.dept-detail {
font-size: 11px;
color: #606266;
line-height: 1.2;
}
/* 紧凑的评分显示样式 */
.rating-display-compact {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
}
.rating-number {
font-size: 11px;
color: #606266;
font-weight: 500;
}
/* 小号分数徽章样式 */
.score-badge-small {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
background: linear-gradient(135deg, #409eff 0%, #67c23a 100%);
color: white;
border-radius: 50%;
font-size: 11px;
font-weight: 600;
}
.no-score {
color: #c0c4cc;
font-size: 11px;
}
/* 完整评分备注样式 */
.rating-comment-full {
padding: 8px 12px;
font-size: 13px;
line-height: 1.5;
color: #303133;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
max-height: none;
}
/* 紧凑的时间显示样式 */
.rating-time-compact {
font-size: 11px;
color: #606266;
line-height: 1.3;
}
/* 表格行高优化 */
:deep(.el-table .el-table__row) {
height: auto !important;
}
:deep(.el-table .el-table__cell) {
padding: 8px 0 !important;
}
</style>
@@ -6,21 +6,16 @@
<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-col :span="8">
<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-col :span="8">
<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-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-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
@@ -38,27 +33,12 @@
<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:edit']">批量通过</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: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-col>
<el-col :span="1.5">
<el-dropdown class="mt-[1px]">
<el-button>
更多
<el-icon class="el-icon--right"><ArrowDown /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Download" @click="handleExport" v-hasPermi="['stgl:activityRegister:export']">导出数据</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<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>
</el-row>
</template>
@@ -100,19 +80,16 @@
</template>
</el-table-column>
<el-table-column label="申请理由" align="center" prop="remark" width="200" show-overflow-tooltip />
<el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-width" fixed="right">
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width" fixed="right">
<template #default="scope">
<el-tooltip content="查看" placement="top">
<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:edit']"></el-button>
</el-tooltip>
<el-tooltip content="拒绝" placement="top">
<el-button link type="danger" icon="Close" @click="handleReject(scope.row)" v-hasPermi="['stgl:activityRegister:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['stgl:activityRegister:remove']"></el-button>
<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>
</el-table-column>
@@ -122,8 +99,9 @@
</el-card>
<!-- 查看成员详情对话框 -->
<el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="800px" append-to-body>
<el-descriptions :column="2" border>
<el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="900px" append-to-body>
<!-- 基本信息 -->
<el-descriptions :column="2" border class="mb-4">
<el-descriptions-item label="学号">{{ viewForm.studentNo }}</el-descriptions-item>
<el-descriptions-item label="姓名">{{ viewForm.studentName }}</el-descriptions-item>
<el-descriptions-item label="性别">
@@ -136,11 +114,53 @@
<el-descriptions-item label="报名类型">
<dict-tag :options="stgl_registration_type" :value="viewForm.registrationType" />
</el-descriptions-item>
<el-descriptions-item label="团队名称">{{ viewForm.teamName || '-' }}</el-descriptions-item>
<el-descriptions-item label="申请理由" :span="2">{{ viewForm.remark || '-' }}</el-descriptions-item>
<el-descriptions-item label="报名时间">{{ parseTime(viewForm.registrationTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
</el-descriptions>
<!-- 团队信息(仅团队报名时显示) -->
<div v-if="Number(viewForm.registrationType) === 2 && teamInfo" class="team-info-section">
<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>
<el-descriptions-item label="队长姓名">{{ teamInfo.leaderName }}</el-descriptions-item>
<el-descriptions-item label="队长学号">{{ teamInfo.leaderStudentNo }}</el-descriptions-item>
<el-descriptions-item label="队长联系方式">{{ teamInfo.leaderPhone || '-' }}</el-descriptions-item>
<el-descriptions-item label="队长院系">{{ teamInfo.leaderDepartment || '-' }}</el-descriptions-item>
<el-descriptions-item label="团队简介" :span="2">{{ teamInfo.description || '暂无描述' }}</el-descriptions-item>
</el-descriptions>
<!-- 队员列表 -->
<div v-if="teamMembers.length > 0" class="team-members-section">
<h4 class="text-base font-medium mb-3">队员列表</h4>
<el-table :data="teamMembers" border stripe size="small">
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="姓名" prop="studentName" width="100" align="center" />
<el-table-column label="学号" prop="studentNo" width="120" align="center" />
<el-table-column label="性别" prop="gender" width="80" align="center">
<template #default="scope">
<dict-tag :options="sys_user_sex" :value="scope.row.gender" v-if="scope.row.gender" />
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="联系方式" prop="phone" width="120" align="center" />
<el-table-column label="院系" prop="department" width="120" align="center" />
<el-table-column label="专业" prop="major" width="150" align="center" />
<el-table-column label="班级" prop="className" width="100" align="center" />
<el-table-column label="角色" width="80" align="center">
<template #default="scope">
<el-tag v-if="scope.row.isLeader === 1" type="success" size="small">队长</el-tag>
<el-tag v-else type="info" size="small">队员</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelView">关 闭</el-button>
@@ -182,12 +202,25 @@ import {
listActivityRegister,
getActivityRegister,
delActivityRegister,
updateActivityRegister,
exportActivityRegister
approveActivityRegister,
batchApproveActivityRegister
} from '@/api/stgl/activityregister';
import { StglActivityRegistrationVo, StglActivityRegistrationForm, StglActivityRegistrationQuery } from '@/api/stgl/activityregister/types';
import { getTeamsByActivityId } from '@/api/stgl/activityteaminfo';
import { StglActivityTeamInfoVo, TeamMemberInfo } from '@/api/stgl/activityteaminfo/types';
import { getStudent } from '@/api/xg/student';
import { StudentVO } from '@/api/xg/student/types';
import { parseTime } from '@/utils/ruoyi';
import { ArrowDown } from '@element-plus/icons-vue';
// 扩展的类型定义,包含后端返回的所有字段
interface ExtendedStglActivityRegistrationVo extends StglActivityRegistrationVo {
gender?: string;
phone?: string;
email?: string;
department?: string;
major?: string;
className?: string;
}
interface Props {
activityId: string | number;
@@ -201,7 +234,7 @@ const { sys_user_sex, stgl_registration_type, stgl_approval_status } = toRefs<an
proxy?.useDict('sys_user_sex', 'stgl_registration_type', 'stgl_approval_status')
);
const memberList = ref<StglActivityRegistrationVo[]>([]);
const memberList = ref<ExtendedStglActivityRegistrationVo[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
@@ -223,10 +256,11 @@ const approvalDialog = reactive<DialogOption>({
title: ''
});
const viewForm = reactive<StglActivityRegistrationVo>({
const viewForm = reactive<ExtendedStglActivityRegistrationVo>({
id: undefined,
activityId: undefined,
studentId: undefined,
activityName: '',
userId: undefined,
studentName: '',
studentNo: '',
gender: '',
@@ -234,16 +268,28 @@ const viewForm = reactive<StglActivityRegistrationVo>({
major: '',
className: '',
phone: '',
email: '',
registrationType: undefined,
teamId: undefined,
teamName: '',
approvalStatus: undefined,
registrationTime: '',
approvalTime: '',
remark: '',
score: '',
scoreTime: ''
scoreTime: '',
approvalBy: undefined,
approvalRemark: '',
scoreBy: undefined,
createTime: '',
updateTime: ''
});
// 团队信息相关数据
const teamInfo = ref<StglActivityTeamInfoVo | null>(null);
const teamMembers = ref<TeamMemberInfo[]>([]);
const loadingTeamInfo = ref(false);
const approvalForm = reactive({
ids: [] as Array<string | number>,
approvalStatus: 1,
@@ -269,15 +315,16 @@ const approvalRules = reactive({
});
const data = reactive<PageData<StglActivityRegistrationForm, StglActivityRegistrationQuery>>({
form: {} as StglActivityRegistrationForm,
queryParams: {
pageNum: 1,
pageSize: 10,
activityId: props.activityId,
approvalStatus: 0, // 待审核状态
approvalStatus: '0', // 待审核状态,使用字符串类型
studentName: undefined,
studentNo: undefined,
phone: undefined
}
studentNo: undefined
},
rules: {}
});
const { queryParams } = toRefs(data);
@@ -285,17 +332,14 @@ const { queryParams } = toRefs(data);
/** 查询成员列表 */
const getList = async () => {
loading.value = true;
try {
// 构建查询参数,查询待审核的报名记录
const queryData = { ...queryParams.value };
// 确保查询待审核状态(approvalStatus=0)
queryData.approvalStatus = 0;
console.log('PendingMembersTab: getList 开始查询,参数:', queryData);
// 确保查询待审核状态(approvalStatus='0')
queryData.approvalStatus = '0';
const res = await listActivityRegister(queryData);
console.log('PendingMembersTab: 查询结果:', res);
memberList.value = res.rows;
total.value = res.total;
@@ -320,23 +364,22 @@ const resetQuery = () => {
pageNum: 1,
pageSize: 10,
activityId: props.activityId,
approvalStatus: 0,
approvalStatus: '0',
studentName: undefined,
studentNo: undefined,
phone: undefined
studentNo: undefined
};
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: ActivityRegisterVO[]) => {
const handleSelectionChange = (selection: ExtendedStglActivityRegistrationVo[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 批量通过操作 */
const handleApprove = (row?: StglActivityRegistrationVo) => {
const handleApprove = (row?: ExtendedStglActivityRegistrationVo) => {
const _ids = row?.id ? [row.id] : ids.value;
if (_ids.length === 0) {
proxy?.$modal.msgError('请选择要审核的记录');
@@ -351,7 +394,7 @@ const handleApprove = (row?: StglActivityRegistrationVo) => {
};
/** 批量拒绝操作 */
const handleReject = (row?: StglActivityRegistrationVo) => {
const handleReject = (row?: ExtendedStglActivityRegistrationVo) => {
const _ids = row?.id ? [row.id] : ids.value;
if (_ids.length === 0) {
proxy?.$modal.msgError('请选择要审核的记录');
@@ -372,20 +415,29 @@ const submitApproval = () => {
buttonLoading.value = true;
try {
// 批量更新审核状态
for (const id of approvalForm.ids) {
await updateActivityRegister({
id,
approvalStatus: approvalForm.approvalStatus,
approvalRemark: approvalForm.approvalRemark,
approvalTime: new Date()
} as StglActivityRegistrationForm);
if (approvalForm.ids.length === 1) {
// 单个审核
await approveActivityRegister(
approvalForm.ids[0],
approvalForm.approvalStatus,
approvalForm.approvalRemark
);
} else {
// 批量审核
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) {
console.error('审核操作失败:', error);
proxy?.$modal.msgError('审核操作失败');
@@ -402,7 +454,7 @@ const cancelApproval = () => {
};
/** 删除按钮操作 */
const handleDelete = async (row?: StglActivityRegistrationVo) => {
const handleDelete = async (row?: ExtendedStglActivityRegistrationVo) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除选中的活动报名记录?');
@@ -421,34 +473,294 @@ const handleDelete = async (row?: StglActivityRegistrationVo) => {
};
/** 查看按钮操作 */
const handleView = async (row: StglActivityRegistrationVo) => {
const handleView = async (row: ExtendedStglActivityRegistrationVo) => {
const res = await getActivityRegister(row.id);
Object.assign(viewForm, res.data);
viewDialog.visible = true;
viewDialog.title = '查看活动报名详情';
// 如果是团队报名,获取团队信息
if (Number(viewForm.registrationType) === 2 && viewForm.teamId) {
await loadTeamInfo(viewForm.teamId);
} else {
// 清空团队信息
teamInfo.value = null;
teamMembers.value = [] as TeamMemberInfo[];
}
};
/** 获取团队信息 */
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
);
if (leaderMember) {
leaderPhone = leaderMember.phone || '暂无联系方式';
leaderDepartment = leaderMember.department || '暂无院系信息';
leaderMajor = leaderMember.major || '';
leaderClassName = leaderMember.className || '';
console.log('PendingMembersTab: 从teamMembers中找到队长信息:', leaderMember);
}
}
}
} catch (parseError) {
console.error('PendingMembersTab: 解析团队成员JSON失败:', parseError);
}
// 如果从teamMembers中没有找到队长信息,尝试通过leaderUserId获取
if (leaderPhone === '暂无联系方式' || leaderDepartment === '暂无院系信息') {
try {
if (teamData.leaderUserId) {
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);
console.log('PendingMembersTab: 学生数据字段:', {
phone: studentData.phone,
collegeName: studentData.collegeName,
majorName: studentData.majorName,
className: studentData.className
});
// 只有在teamMembers中没有找到时才使用API获取的信息
if (leaderPhone === '暂无联系方式') {
leaderPhone = studentData.phone || '暂无联系方式';
}
if (leaderDepartment === '暂无院系信息') {
leaderDepartment = studentData.collegeName || '暂无院系信息';
}
if (!leaderMajor) {
leaderMajor = studentData.majorName || '';
}
if (!leaderClassName) {
leaderClassName = studentData.className || '';
}
console.log('PendingMembersTab: 获取到队长详细信息:', { leaderPhone, leaderDepartment, studentData });
} else {
console.log('PendingMembersTab: getStudent API返回数据为空');
}
}
} catch (studentError) {
console.log('PendingMembersTab: 获取队长详细信息失败:', studentError);
}
}
// 如果仍然没有获取到队长信息,尝试从报名详情中获取
if (leaderPhone === '暂无联系方式' && viewForm.phone) {
leaderPhone = viewForm.phone;
console.log('PendingMembersTab: 使用报名详情中的联系方式:', leaderPhone);
}
if (leaderDepartment === '暂无院系信息' && viewForm.department) {
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
);
if (leaderMember) {
if (leaderPhone === '暂无联系方式') {
leaderPhone = leaderMember.phone || '暂无联系方式';
}
if (leaderDepartment === '暂无院系信息') {
leaderDepartment = leaderMember.department || '暂无院系信息';
}
if (!leaderMajor) {
leaderMajor = leaderMember.major || '';
}
if (!leaderClassName) {
leaderClassName = leaderMember.className || '';
}
console.log('PendingMembersTab: 从teamMembers中找到匹配leaderUserId的成员信息:', leaderMember);
}
}
} catch (parseError) {
console.error('PendingMembersTab: 备用方案解析团队成员JSON失败:', parseError);
}
}
// 更新团队信息
teamInfo.value = {
id: teamData.id,
teamName: teamData.teamName,
activityId: teamData.activityId,
leaderUserId: teamData.leaderUserId,
teamSize: teamData.teamSize || 1,
leaderName: teamData.leaderName,
leaderStudentNo: teamData.leaderStudentNo,
leaderPhone: leaderPhone,
leaderDepartment: leaderDepartment,
description: teamData.description || '',
teamMembers: teamData.teamMembers || '[]'
};
console.log('PendingMembersTab: 最终设置的团队信息:', teamInfo.value);
// 解析团队成员信息
if (teamData.teamMembers) {
try {
const membersData = JSON.parse(teamData.teamMembers);
if (Array.isArray(membersData)) {
teamMembers.value = membersData.map((member: any) => ({
userId: member.userId,
studentNo: member.studentNo,
studentName: member.studentName,
gender: member.gender,
phone: member.phone,
email: member.email,
department: member.department,
major: member.major,
className: member.className,
isLeader: member.isLeader
}));
} else {
teamMembers.value = [];
}
} catch (parseError) {
console.error('解析团队成员JSON失败:', parseError);
teamMembers.value = [];
}
} else {
teamMembers.value = [];
}
} else {
throw new Error('未找到对应的团队信息');
}
} else {
throw new Error('API返回数据为空或格式错误');
}
} 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);
if (studentResponse.data) {
const studentData = studentResponse.data;
leaderPhone = studentData.phone || '暂无联系方式';
leaderDepartment = studentData.collegeName || '暂无院系信息';
console.log('PendingMembersTab: 获取到队长详细信息:', studentData);
}
}
} catch (studentError) {
console.log('PendingMembersTab: 获取队长详细信息失败,使用报名详情中的信息:', studentError);
// 如果获取学生信息失败,尝试使用报名详情中的字段
if (viewForm.phone) {
leaderPhone = viewForm.phone;
}
if (viewForm.department) {
leaderDepartment = viewForm.department;
}
}
// 构造团队信息对象,优先使用获取到的详细信息
teamInfo.value = {
id: viewForm.teamId,
teamName: viewForm.teamName,
activityId: viewForm.activityId,
leaderUserId: viewForm.userId,
teamSize: 1, // 默认值,实际应该从后端获取
leaderName: viewForm.studentName,
leaderStudentNo: viewForm.studentNo,
leaderPhone: leaderPhone,
leaderDepartment: leaderDepartment,
description: viewForm.remark || '暂无团队简介',
teamMembers: '[]' // JSON格式的字符串
};
// 团队成员信息暂时为空,因为报名详情中没有这个字段
teamMembers.value = [] as TeamMemberInfo[];
console.log('PendingMembersTab: 使用报名详情构造的团队信息:', teamInfo.value);
} else {
teamInfo.value = null;
teamMembers.value = [] as TeamMemberInfo[];
}
}
} catch (error) {
console.error('PendingMembersTab: 处理团队信息失败:', error);
proxy?.$modal.msgError('处理团队信息失败');
teamInfo.value = null;
teamMembers.value = [] as TeamMemberInfo[];
} finally {
loadingTeamInfo.value = false;
}
};
/** 取消查看对话框 */
const cancelView = () => {
viewDialog.visible = false;
// 清空团队信息
teamInfo.value = null;
teamMembers.value = [] as TeamMemberInfo[];
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'stgl/activity/register/export',
{
...queryParams.value
},
`pending_activity_register_${new Date().getTime()}.xlsx`
);
};
// 暴露给父组件的方法
defineExpose({
getList
});
// 定义事件
const emit = defineEmits<{
refreshOtherTabs: []
}>();
// 通知父组件刷新其他标签页
const notifyRefreshOtherTabs = () => {
emit('refreshOtherTabs');
};
onMounted(() => {
getList();
});
@@ -466,4 +778,29 @@ onMounted(() => {
white-space: pre-wrap;
word-break: break-word;
}
/* 团队信息区域样式 */
.team-info-section {
margin-top: 20px;
}
.team-members-section {
margin-top: 20px;
}
.text-lg {
font-size: 16px;
}
.font-medium {
font-weight: 500;
}
.mb-3 {
margin-bottom: 12px;
}
.mb-4 {
margin-bottom: 16px;
}
</style>
@@ -17,11 +17,6 @@
</el-form-item>
</el-col>
<el-col :span="6">
<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-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
@@ -46,6 +41,9 @@
<el-col :span="1.5">
<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>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@@ -159,7 +157,7 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="团队名称" prop="teamName" v-if="form.registrationType === '2' || form.registrationType === '3'">
<el-form-item label="团队名称" prop="teamName" v-if="form.registrationType === 2 || form.registrationType === 3">
<el-input v-model="form.teamName" placeholder="请输入团队名称" />
</el-form-item>
</el-col>
@@ -179,8 +177,9 @@
</el-dialog>
<!-- 查看成员详情对话框 -->
<el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="800px" append-to-body>
<el-descriptions :column="2" border>
<el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="900px" append-to-body>
<!-- 基本信息 -->
<el-descriptions :column="2" border class="mb-4">
<el-descriptions-item label="学号">{{ viewForm.studentNo }}</el-descriptions-item>
<el-descriptions-item label="姓名">{{ viewForm.studentName }}</el-descriptions-item>
<el-descriptions-item label="性别">
@@ -193,12 +192,54 @@
<el-descriptions-item label="报名类型">
<dict-tag :options="stgl_registration_type" :value="viewForm.registrationType" />
</el-descriptions-item>
<el-descriptions-item label="团队名称">{{ viewForm.teamName || '-' }}</el-descriptions-item>
<el-descriptions-item label="申请理由">{{ viewForm.remark || '-' }}</el-descriptions-item>
<el-descriptions-item label="申请理由" :span="2">{{ viewForm.remark || '-' }}</el-descriptions-item>
<el-descriptions-item label="报名时间">{{ parseTime(viewForm.registrationTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
<el-descriptions-item label="成绩">{{ viewForm.score || '-' }}</el-descriptions-item>
</el-descriptions>
<!-- 团队信息(仅团队报名时显示) -->
<div v-if="Number(viewForm.registrationType) === 2 && teamInfo" class="team-info-section">
<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>
<el-descriptions-item label="队长姓名">{{ teamInfo.leaderName }}</el-descriptions-item>
<el-descriptions-item label="队长学号">{{ teamInfo.leaderStudentNo }}</el-descriptions-item>
<el-descriptions-item label="队长联系方式">{{ teamInfo.leaderPhone || '-' }}</el-descriptions-item>
<el-descriptions-item label="队长院系">{{ teamInfo.leaderDepartment || '-' }}</el-descriptions-item>
<el-descriptions-item label="团队简介" :span="2">{{ teamInfo.description || '暂无描述' }}</el-descriptions-item>
</el-descriptions>
<!-- 队员列表 -->
<div v-if="teamMembers.length > 0" class="team-members-section">
<h4 class="text-base font-medium mb-3">队员列表</h4>
<el-table :data="teamMembers" border stripe size="small">
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="姓名" prop="studentName" width="100" align="center" />
<el-table-column label="学号" prop="studentNo" width="120" align="center" />
<el-table-column label="性别" prop="gender" width="80" align="center">
<template #default="scope">
<dict-tag :options="sys_user_sex" :value="scope.row.gender" v-if="scope.row.gender" />
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="联系方式" prop="phone" width="120" align="center" />
<el-table-column label="院系" prop="department" width="120" align="center" />
<el-table-column label="专业" prop="major" width="150" align="center" />
<el-table-column label="班级" prop="className" width="100" align="center" />
<el-table-column label="角色" width="80" align="center">
<template #default="scope">
<el-tag v-if="scope.row.isLeader === 1" type="success" size="small">队长</el-tag>
<el-tag v-else type="info" size="small">队员</el-tag>
</template>
</el-table-column>
</el-table>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelView">关 闭</el-button>
@@ -253,24 +294,51 @@ import {
updateActivityRegister,
exportActivityRegister
} from '@/api/stgl/activityregister';
import { ActivityRegisterVO, ActivityRegisterForm, ActivityRegisterQuery } from '@/api/stgl/activityregister/types';
import { StglActivityRegistrationVo, StglActivityRegistrationForm, StglActivityRegistrationQuery } from '@/api/stgl/activityregister/types';
import { getTeamsByActivityId } from '@/api/stgl/activityteaminfo';
import { StglActivityTeamInfoVo, TeamMemberInfo } from '@/api/stgl/activityteaminfo/types';
import { getStudent } from '@/api/xg/student';
import { StudentVO } from '@/api/xg/student/types';
import StudentSelector from '@/components/StudentSelector/index.vue';
import { parseTime } from '@/utils/ruoyi';
import { globalHeaders } from '@/utils/request';
import { ArrowDown, UploadFilled } from '@element-plus/icons-vue';
import { UploadFilled } from '@element-plus/icons-vue';
import type { UploadInstance } from 'element-plus';
// 扩展的类型定义,包含后端返回的所有字段
interface ExtendedStglActivityRegistrationVo extends StglActivityRegistrationVo {
gender?: string;
phone?: string;
email?: string;
department?: string;
major?: string;
className?: string;
}
// 扩展ActivityRegisterForm以包含选择的学生对象和额外字段
interface ExtendedActivityRegisterForm extends StglActivityRegistrationForm {
selectedStudent?: StudentVO | null;
gender?: string;
phone?: string;
email?: string;
department?: string;
major?: string;
className?: string;
registrationTime?: string;
approvalTime?: string;
scoreTime?: string;
approvalBy?: string | number;
approvalRemark?: string;
scoreBy?: string | number;
createTime?: string;
updateTime?: string;
}
interface Props {
activityId: string | number;
activityName: string;
}
// 扩展ActivityRegisterForm以包含选择的学生对象
interface ExtendedActivityRegisterForm extends ActivityRegisterForm {
selectedStudent?: StudentVO | null;
}
const props = defineProps<Props>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -278,7 +346,7 @@ const { sys_user_sex, stgl_registration_type, stgl_approval_status } = toRefs<an
proxy?.useDict('sys_user_sex', 'stgl_registration_type', 'stgl_approval_status')
);
const memberList = ref<ActivityRegisterVO[]>([]);
const memberList = ref<ExtendedStglActivityRegistrationVo[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
@@ -300,10 +368,11 @@ const viewDialog = reactive<DialogOption>({
title: ''
});
const viewForm = reactive<ActivityRegisterVO>({
const viewForm = reactive<ExtendedStglActivityRegistrationVo>({
id: undefined,
activityId: undefined,
studentId: undefined,
activityName: '',
userId: undefined,
studentName: '',
studentNo: '',
gender: '',
@@ -311,20 +380,32 @@ const viewForm = reactive<ActivityRegisterVO>({
major: '',
className: '',
phone: '',
email: '',
registrationType: undefined,
teamId: undefined,
teamName: '',
approvalStatus: undefined,
registrationTime: '',
approvalTime: '',
remark: '',
score: '',
scoreTime: ''
scoreTime: '',
approvalBy: undefined,
approvalRemark: '',
scoreBy: undefined,
createTime: '',
updateTime: ''
});
// 团队信息相关数据
const teamInfo = ref<StglActivityTeamInfoVo | null>(null);
const teamMembers = ref<TeamMemberInfo[]>([]);
const loadingTeamInfo = ref(false);
const initFormData: ExtendedActivityRegisterForm = {
id: undefined,
activityId: props.activityId,
studentId: undefined,
userId: undefined,
selectedStudent: null,
studentName: '',
studentNo: '',
@@ -333,26 +414,31 @@ const initFormData: ExtendedActivityRegisterForm = {
major: '',
className: '',
phone: '',
registrationType: '1',
registrationType: 1,
teamId: undefined,
teamName: '',
approvalStatus: 1,
registrationTime: undefined,
approvalTime: undefined,
remark: '',
score: '',
scoreTime: undefined
scoreTime: undefined,
approvalBy: undefined,
approvalRemark: '',
scoreBy: undefined,
createTime: '',
updateTime: ''
};
const data = reactive<PageData<ExtendedActivityRegisterForm, ActivityRegisterQuery>>({
const data = reactive<PageData<ExtendedActivityRegisterForm, StglActivityRegistrationQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
activityId: props.activityId,
approvalStatus: undefined,
approvalStatus: '1', // 使用字符串类型,符合查询接口定义
studentName: undefined,
studentNo: undefined,
phone: undefined
studentNo: undefined
},
rules: {
selectedStudent: [{ required: true, message: '请选择学生', trigger: 'change' }],
@@ -390,7 +476,7 @@ const getList = async () => {
// 如果状态字段为空或未选择,则查询已通过(approvalStatus=1)的报名记录
if (!queryData.approvalStatus) {
queryData.approvalStatus = 1;
queryData.approvalStatus = '1';
}
console.log('RegisteredMembersTab: getList 开始查询,参数:', queryData);
@@ -433,16 +519,15 @@ const resetQuery = () => {
pageNum: 1,
pageSize: 10,
activityId: props.activityId,
approvalStatus: 1,
approvalStatus: '1',
studentName: undefined,
studentNo: undefined,
phone: undefined
studentNo: undefined
};
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: ActivityRegisterVO[]) => {
const handleSelectionChange = (selection: ExtendedStglActivityRegistrationVo[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
@@ -456,7 +541,7 @@ const handleAdd = () => {
};
/** 修改按钮操作 */
const handleUpdate = async (row?: ActivityRegisterVO) => {
const handleUpdate = async (row?: ExtendedStglActivityRegistrationVo) => {
reset();
const _id = row?.id || ids.value[0];
const res = await getActivityRegister(_id);
@@ -464,15 +549,17 @@ const handleUpdate = async (row?: ActivityRegisterVO) => {
// 设置学生选择器数据
if (res.data.studentName) {
// 使用扩展类型来访问额外字段
const extendedData = res.data as ExtendedStglActivityRegistrationVo;
form.value.selectedStudent = {
id: res.data.studentId || 0,
id: res.data.userId || 0,
studentNo: res.data.studentNo || '',
name: res.data.studentName,
gender: res.data.gender || '0',
phone: res.data.phone || '',
collegeName: res.data.department || '',
majorName: res.data.major || '',
className: res.data.className || ''
gender: extendedData.gender || '0',
phone: extendedData.phone || '',
collegeName: extendedData.department || '',
majorName: extendedData.major || '',
className: extendedData.className || ''
} as StudentVO;
}
@@ -495,9 +582,9 @@ const submitForm = () => {
try {
if (form.value.id) {
await updateActivityRegister(submitData as ActivityRegisterForm);
await updateActivityRegister(submitData as StglActivityRegistrationForm);
} else {
await addActivityRegister(submitData as ActivityRegisterForm);
await addActivityRegister(submitData as StglActivityRegistrationForm);
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
@@ -512,7 +599,7 @@ const submitForm = () => {
};
/** 删除按钮操作 */
const handleDelete = async (row?: ActivityRegisterVO) => {
const handleDelete = async (row?: ExtendedStglActivityRegistrationVo) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除选中的活动报名记录?');
@@ -531,22 +618,281 @@ const handleDelete = async (row?: ActivityRegisterVO) => {
};
/** 查看按钮操作 */
const handleView = async (row: ActivityRegisterVO) => {
const handleView = async (row: ExtendedStglActivityRegistrationVo) => {
const res = await getActivityRegister(row.id);
Object.assign(viewForm, res.data);
viewDialog.visible = true;
viewDialog.title = '查看活动报名详情';
// 如果是团队报名,获取团队信息
if (Number(viewForm.registrationType) === 2 && viewForm.teamId) {
await loadTeamInfo(viewForm.teamId);
} else {
// 清空团队信息
teamInfo.value = null;
teamMembers.value = [] as TeamMemberInfo[];
}
};
/** 获取团队信息 */
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
);
if (leaderMember) {
leaderPhone = leaderMember.phone || '暂无联系方式';
leaderDepartment = leaderMember.department || '暂无院系信息';
leaderMajor = leaderMember.major || '';
leaderClassName = leaderMember.className || '';
console.log('RegisteredMembersTab: 从teamMembers中找到队长信息:', leaderMember);
}
}
}
} catch (parseError) {
console.error('RegisteredMembersTab: 解析团队成员JSON失败:', parseError);
}
// 如果从teamMembers中没有找到队长信息,尝试通过leaderUserId获取
if (leaderPhone === '暂无联系方式' || leaderDepartment === '暂无院系信息') {
try {
if (teamData.leaderUserId) {
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);
console.log('RegisteredMembersTab: 学生数据字段:', {
phone: studentData.phone,
collegeName: studentData.collegeName,
majorName: studentData.majorName,
className: studentData.className
});
// 只有在teamMembers中没有找到时才使用API获取的信息
if (leaderPhone === '暂无联系方式') {
leaderPhone = studentData.phone || '暂无联系方式';
}
if (leaderDepartment === '暂无院系信息') {
leaderDepartment = studentData.collegeName || '暂无院系信息';
}
if (!leaderMajor) {
leaderMajor = studentData.majorName || '';
}
if (!leaderClassName) {
leaderClassName = studentData.className || '';
}
console.log('RegisteredMembersTab: 获取到队长详细信息:', { leaderPhone, leaderDepartment, studentData });
} else {
console.log('RegisteredMembersTab: getStudent API返回数据为空');
}
}
} catch (studentError) {
console.log('RegisteredMembersTab: 获取队长详细信息失败:', studentError);
}
}
// 如果仍然没有获取到队长信息,尝试从报名详情中获取
if (leaderPhone === '暂无联系方式' && viewForm.phone) {
leaderPhone = viewForm.phone;
console.log('RegisteredMembersTab: 使用报名详情中的联系方式:', leaderPhone);
}
if (leaderDepartment === '暂无院系信息' && viewForm.department) {
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
);
if (leaderMember) {
if (leaderPhone === '暂无联系方式') {
leaderPhone = leaderMember.phone || '暂无联系方式';
}
if (leaderDepartment === '暂无院系信息') {
leaderDepartment = leaderMember.department || '暂无院系信息';
}
if (!leaderMajor) {
leaderMajor = leaderMember.major || '';
}
if (!leaderClassName) {
leaderClassName = leaderMember.className || '';
}
console.log('RegisteredMembersTab: 从teamMembers中找到匹配leaderUserId的成员信息:', leaderMember);
}
}
} catch (parseError) {
console.error('RegisteredMembersTab: 备用方案解析团队成员JSON失败:', parseError);
}
}
// 更新团队信息
teamInfo.value = {
id: teamData.id,
teamName: teamData.teamName,
activityId: teamData.activityId,
leaderUserId: teamData.leaderUserId,
teamSize: teamData.teamSize || 1,
leaderName: teamData.leaderName,
leaderStudentNo: teamData.leaderStudentNo,
leaderPhone: leaderPhone,
leaderDepartment: leaderDepartment,
description: teamData.description || '',
teamMembers: teamData.teamMembers || '[]'
};
console.log('RegisteredMembersTab: 最终设置的团队信息:', teamInfo.value);
// 解析团队成员信息
if (teamData.teamMembers) {
try {
const membersData = JSON.parse(teamData.teamMembers);
if (Array.isArray(membersData)) {
teamMembers.value = membersData.map((member: any) => ({
userId: member.userId,
studentNo: member.studentNo,
studentName: member.studentName,
gender: member.gender,
phone: member.phone,
email: member.email,
department: member.department,
major: member.major,
className: member.className,
isLeader: member.isLeader
}));
} else {
teamMembers.value = [];
}
} catch (parseError) {
console.error('解析团队成员JSON失败:', parseError);
teamMembers.value = [];
}
} else {
teamMembers.value = [];
}
} else {
throw new Error('未找到对应的团队信息');
}
} else {
throw new Error('API返回数据为空或格式错误');
}
} 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);
if (studentResponse.data) {
const studentData = studentResponse.data;
leaderPhone = studentData.phone || '暂无联系方式';
leaderDepartment = studentData.collegeName || '暂无院系信息';
console.log('RegisteredMembersTab: 获取到队长详细信息:', studentData);
}
}
} catch (studentError) {
console.log('RegisteredMembersTab: 获取队长详细信息失败,使用报名详情中的信息:', studentError);
// 如果获取学生信息失败,尝试使用报名详情中的字段
if (viewForm.phone) {
leaderPhone = viewForm.phone;
}
if (viewForm.department) {
leaderDepartment = viewForm.department;
}
}
// 构造团队信息对象,优先使用获取到的详细信息
teamInfo.value = {
id: viewForm.teamId,
teamName: viewForm.teamName,
activityId: viewForm.activityId,
leaderUserId: viewForm.userId,
teamSize: 1, // 默认值,实际应该从后端获取
leaderName: viewForm.studentName,
leaderStudentNo: viewForm.studentNo,
leaderPhone: leaderPhone,
leaderDepartment: leaderDepartment,
description: viewForm.remark || '暂无团队简介',
teamMembers: '[]' // JSON格式的字符串
};
// 团队成员信息暂时为空,因为报名详情中没有这个字段
teamMembers.value = [] as TeamMemberInfo[];
console.log('RegisteredMembersTab: 使用报名详情构造的团队信息:', teamInfo.value);
} else {
teamInfo.value = null;
teamMembers.value = [] as TeamMemberInfo[];
}
}
} catch (error) {
console.error('RegisteredMembersTab: 处理团队信息失败:', error);
proxy?.$modal.msgError('处理团队信息失败');
teamInfo.value = null;
teamMembers.value = [] as TeamMemberInfo[];
} finally {
loadingTeamInfo.value = false;
}
};
/** 取消查看对话框 */
const cancelView = () => {
viewDialog.visible = false;
// 清空团队信息
teamInfo.value = null;
teamMembers.value = [] as TeamMemberInfo[];
};
/** 学生选择变化处理 */
const handleStudentChange = (student: StudentVO | null) => {
if (student) {
form.value.studentId = student.id;
form.value.userId = student.id;
form.value.studentNo = student.studentNo;
form.value.studentName = student.name;
form.value.gender = student.gender;
@@ -555,7 +901,7 @@ const handleStudentChange = (student: StudentVO | null) => {
form.value.className = student.className;
form.value.phone = student.phone;
} else {
form.value.studentId = undefined;
form.value.userId = undefined;
form.value.studentNo = '';
form.value.studentName = '';
form.value.gender = '';
@@ -573,7 +919,7 @@ const handleExport = () => {
{
...queryParams.value
},
`activity_register_${new Date().getTime()}.xlsx`
`registered_activity_register_${new Date().getTime()}.xlsx`
);
};
@@ -631,4 +977,29 @@ onMounted(() => {
white-space: pre-wrap;
word-break: break-word;
}
/* 团队信息区域样式 */
.team-info-section {
margin-top: 20px;
}
.team-members-section {
margin-top: 20px;
}
.text-lg {
font-size: 16px;
}
.font-medium {
font-weight: 500;
}
.mb-3 {
margin-bottom: 12px;
}
.mb-4 {
margin-bottom: 16px;
}
</style>
+90 -31
View File
@@ -62,7 +62,7 @@
</template>
</el-table-column>
<el-table-column label="主办方" align="center" prop="organizerName" width="120" show-overflow-tooltip />
<el-table-column label="关联社团" align="center" width="120" show-overflow-tooltip>
<el-table-column label="关联社团/队伍" align="center" width="120" show-overflow-tooltip>
<template #default="scope">
<span v-if="scope.row.relatedClubName">{{ scope.row.relatedClubName }}</span>
<span v-else class="text-gray-400">未关联</span>
@@ -101,6 +101,12 @@
<el-tag type="info" size="small">{{ scope.row.registrationCount || 0 }}</el-tag>
</template>
</el-table-column>
<el-table-column label="审核设置" align="center" width="100">
<template #default="scope">
<el-tag v-if="scope.row.needApproval === 0" type="success" size="small">无需审核</el-tag>
<el-tag v-else type="warning" size="small">需要审核</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180" fixed="right">
<template #default="scope">
<el-tooltip content="查看详情" placement="top">
@@ -141,6 +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>
@@ -156,6 +163,26 @@
<el-input v-model="selectedActivity.activityName" disabled />
</el-form-item>
<!-- 审核设置提示 -->
<el-form-item label="审核设置">
<el-alert
v-if="selectedActivity.needApproval === 0"
title="无需审核"
type="success"
description="该活动设置为无需审核,报名后将自动通过"
:closable="false"
show-icon
/>
<el-alert
v-else
title="需要审核"
type="info"
description="该活动设置为需要审核,报名后需要等待管理员审核通过"
:closable="false"
show-icon
/>
</el-form-item>
<el-form-item label="报名类型" prop="registrationType">
<el-radio-group v-model="registerForm.registrationType" @change="handleRegistrationTypeChange">
<el-radio v-if="isRegistrationTypeAllowed(1)" :value="1">个人报名</el-radio>
@@ -228,16 +255,11 @@
</el-form-item>
<!-- 团队负责人信息 -->
<el-divider content-position="left">团队负责人</el-divider>
<el-divider content-position="left">团队负责人(当前用户)</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="负责人姓名" prop="presidentStudent">
<StudentSelector
v-model="registerForm.presidentStudent"
placeholder="请选择团队负责人"
:showStudentNo="false"
@change="handlePresidentChange"
/>
<el-form-item label="负责人姓名" prop="studentName">
<el-input v-model="registerForm.studentName" readonly />
</el-form-item>
</el-col>
<el-col :span="12">
@@ -314,6 +336,7 @@
<script setup name="StglActivityRegisterHall" lang="ts">
import { nextTick } from 'vue';
import { CircleCheck, Clock } from '@element-plus/icons-vue';
import {
listRegistrableActivities,
getActivityDetail,
@@ -322,7 +345,7 @@ import {
getMyRegistrations
} from '@/api/stgl/activityregisterhall';
import { StglActivityVO, StglActivityQuery } from '@/api/stgl/activity/types';
import { StglActivityRegistrationForm, TeamMemberInfo } from '@/api/stgl/activityregisterhall/types';
import { StglActivityRegistrationForm, TeamMemberInfo, StglActivityVo } from '@/api/stgl/activityregisterhall/types';
import ActivityDetailView from '@/views/stgl/activity/components/ActivityDetailView.vue';
import TeamMemberManager from '@/components/TeamMemberManager/index.vue';
import VicePresidentManager from '@/components/VicePresidentManager/index.vue';
@@ -331,13 +354,15 @@ import { getPersonalInfo } from '@/api/xg/xsjcxx/studentMsg/studentMsg/index';
import type { StudentMsgForm } from '@/api/xg/xsjcxx/studentMsg/studentMsg/types';
import { StudentVO } from '@/api/xg/student/types';
import { VicePresidentVO } from '@/components/VicePresidentManager/index.vue';
import { getUser } from '@/api/system/user';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { stgl_activity_type, stgl_registration_type, stgl_registration_status } = toRefs<any>(
proxy?.useDict('stgl_activity_type', 'stgl_registration_type', 'stgl_registration_status')
);
const activityList = ref<StglActivityVO[]>([]);
// 修改类型定义,使用后端返回的StglActivityVo类型
const activityList = ref<StglActivityVo[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
@@ -356,8 +381,8 @@ const registerDialog = reactive<DialogOption>({
title: ''
});
const selectedActivity = ref<StglActivityVO>({} as StglActivityVO);
const viewForm = ref<StglActivityVO>({} as StglActivityVO);
const selectedActivity = ref<StglActivityVo>({} as StglActivityVo);
const viewForm = ref<StglActivityVo>({} as StglActivityVo);
// 用户报名状态缓存
const userRegistrationStatus = ref<Record<number, boolean>>({});
@@ -469,7 +494,7 @@ const resetQuery = () => {
};
/** 查看按钮操作 */
const handleView = async (row: StglActivityVO) => {
const handleView = async (row: StglActivityVo) => {
const res = await getActivityDetail(row.id);
viewForm.value = res.data;
viewDialog.visible = true;
@@ -477,7 +502,7 @@ const handleView = async (row: StglActivityVO) => {
};
/** 报名按钮操作 */
const handleRegister = async (row: StglActivityVO) => {
const handleRegister = async (row: StglActivityVo) => {
selectedActivity.value = row;
resetRegisterForm();
@@ -547,16 +572,28 @@ const handleRegistrationTypeChange = async (value: number) => {
// 自动填充当前用户信息
await loadUserInfoToForm();
} else if (value === 2) {
// 团队报名,清空个人信息,让用户选择团队负责人
registerForm.studentName = undefined;
registerForm.studentNo = undefined;
registerForm.gender = undefined;
registerForm.phone = undefined;
registerForm.email = undefined;
registerForm.department = undefined;
registerForm.major = undefined;
registerForm.className = undefined;
registerForm.presidentStudent = null;
// 团队报名,自动填充当前用户信息作为团队负责人
await loadUserInfoToForm();
// 设置团队负责人为学生对象(用于StudentSelector组件)
// 使用类型断言避免类型错误,因为我们只需要这些字段用于显示
registerForm.presidentStudent = {
id: 0, // 临时ID,实际提交时不需要
name: registerForm.studentName || '',
studentNo: registerForm.studentNo || '',
gender: registerForm.gender || '',
phone: registerForm.phone || '',
email: registerForm.email || '',
collegeId: '', // 必需的字段
collegeName: registerForm.department || '',
majorId: '', // 必需的字段
majorName: registerForm.major || '',
classId: '', // 必需的字段
className: registerForm.className || '',
grade: '', // 必需的字段
studentStatus: '', // 必需的字段
enrollmentYear: 0 // 必需的字段
} as StudentVO;
}
};
@@ -572,8 +609,14 @@ const handlePresidentChange = async (student: StudentVO | null) => {
registerForm.major = student.majorName || '';
registerForm.className = student.className || '';
// 清空email,让用户手动填写或通过其他方式获取
registerForm.email = undefined;
// 获取用户邮箱
try {
const userRes = await getUser(student.id);
registerForm.email = userRes.data?.user?.email || '';
} catch (error) {
console.error('获取用户邮箱失败:', error);
registerForm.email = ''; // 设置为空,让用户手动填写或通过其他方式获取
}
} else {
// 清空负责人信息
registerForm.studentName = undefined;
@@ -598,7 +641,7 @@ const handleTeamMembersNewChange = (members: VicePresidentVO[]) => {
studentName: member.name || member.student?.name || '',
gender: member.student?.gender || '',
phone: member.phone || member.student?.phone || '',
email: '', // VicePresidentVO可能没有email字段
email: '', // VicePresidentVO没有email字段,设置为空
department: member.dept || member.student?.collegeName || '',
major: member.major || member.student?.majorName || '',
className: member.class || member.student?.className || '',
@@ -663,6 +706,15 @@ const submitRegister = () => {
remark: registerForm.remark
};
// 根据活动的审核设置自动设置报名状态
// 如果活动设置为不需要审核(needApproval = 0),则自动设置为通过状态(approvalStatus = 1)
// 如果活动设置为需要审核(needApproval = 1),则保持默认的待审核状态(approvalStatus = 0)
if (selectedActivity.value.needApproval === 0) {
submitData.approvalStatus = 1; // 自动通过
} else {
submitData.approvalStatus = 0; // 待审核
}
// 只有团队报名时才发送团队相关字段
if (registerForm.registrationType === 2) {
submitData.teamName = registerForm.teamName;
@@ -678,7 +730,14 @@ const submitRegister = () => {
// 个人报名时不发送任何团队相关字段
await registerActivity(submitData);
proxy?.$modal.msgSuccess('报名成功,请等待审核');
// 根据审核设置显示不同的成功消息
if (selectedActivity.value.needApproval === 0) {
proxy?.$modal.msgSuccess('报名成功!您的报名已自动通过审核');
} else {
proxy?.$modal.msgSuccess('报名成功,请等待审核');
}
registerDialog.visible = false;
// 更新报名状态
@@ -695,13 +754,13 @@ const submitRegister = () => {
};
/** 判断是否可以报名 */
const canRegister = (activity: StglActivityVO): boolean => {
const canRegister = (activity: StglActivityVo): boolean => {
// 报名状态为0(未开始)或1(报名中),且用户未报名
return [0, 1].includes(activity.registrationStatus || 0) && !isRegistered(activity);
};
/** 判断用户是否已报名 */
const isRegistered = (activity: StglActivityVO): boolean => {
const isRegistered = (activity: StglActivityVo): boolean => {
return userRegistrationStatus.value[activity.id as number] || false;
};
+104 -86
View File
@@ -46,7 +46,7 @@
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="关联社团" prop="relatedClubId">
<el-form-item label="关联社团/队伍" prop="relatedClubId">
<ClubSelector v-model="queryParams.relatedClub" @change="handleQueryRelatedClubChange" />
</el-form-item>
</el-col>
@@ -125,7 +125,7 @@
</template>
</el-table-column>
<el-table-column label="主办方" align="center" prop="organizerName" width="120" show-overflow-tooltip />
<el-table-column label="关联社团" align="center" width="120" show-overflow-tooltip>
<el-table-column label="关联社团/队伍" align="center" width="120" show-overflow-tooltip>
<template #default="scope">
<span v-if="scope.row.relatedClubName">{{ scope.row.relatedClubName }}</span>
<span v-else class="text-gray-400">未关联</span>
@@ -270,7 +270,7 @@
v-hasPermi="['stgl:activity:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="成员审核" placement="top">
<el-tooltip content="报名管理" placement="top">
<el-button
link
type="info"
@@ -358,7 +358,7 @@
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="关联社团" prop="relatedClubId">
<el-form-item label="关联社团/队伍" prop="relatedClubId">
<ClubSelector v-model="form.relatedClub" @change="handleRelatedClubChange" :disabled="isRelatedClubDisabled" />
</el-form-item>
</el-col>
@@ -407,7 +407,8 @@
<AdvisorManager
:key="`advisor-manager-${form.id || 'new'}-${formKey}`"
v-model="form.advisors"
@change="handleAdvisorsChange"
@change="handleAdvisorsChange"
:initial-advisors="form.advisors"
/>
</el-tab-pane>
@@ -707,7 +708,7 @@ const initFormData: StglActivityForm = {
coOrganizerList: [],
coOrganizers: undefined,
instructorList: [],
instructors: undefined,
instructors: '[]',
advisors: [],
registrationType: undefined,
registrationStart: undefined,
@@ -864,31 +865,37 @@ const cancel = () => {
/** 表单重置 */
const reset = () => {
// 使用nextTick来避免递归更新
nextTick(() => {
form.value = { ...initFormData };
// 确保指导老师字段被正确初始化
form.value.advisors = [];
form.value.instructorList = [];
form.value.instructors = undefined;
// 重置关联社团禁用状态
isRelatedClubDisabled.value = false;
// 重置审批表单
approveForm.id = undefined;
approveForm.activityName = '';
approveForm.approvalResult = 'approve';
approveForm.rejectReason = '';
// 更新formKey以强制重新渲染组件
formKey.value++;
activeTab.value = 'basic';
nextTick(() => {
activityFormRef.value?.resetFields();
});
// 直接重置,避免使用nextTick导致的递归更新
form.value = { ...initFormData };
// 确保指导老师字段被正确初始化
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();
}, 0);
};
/** 搜索按钮操作 */
@@ -921,7 +928,7 @@ const handleAdd = () => {
// 确保指导老师字段被正确初始化
form.value.advisors = [];
form.value.instructorList = [];
form.value.instructors = undefined;
form.value.instructors = '[]';
// 更新formKey以强制重新渲染组件
formKey.value++;
@@ -1038,8 +1045,25 @@ const handleUpdate = async (row?: StglActivityVO) => {
// 处理AdvisorManager组件的指导老师数据
if (res.data.instructors) {
try {
(res.data as any).advisors = JSON.parse(res.data.instructors);
const instructorsData = JSON.parse(res.data.instructors);
console.log('解析的指导老师数据:', instructorsData);
// 确保数据格式正确
if (Array.isArray(instructorsData)) {
(res.data as any).advisors = instructorsData.map((advisor: any) => ({
...advisor,
// 确保必要字段存在
name: advisor.name || advisor.teacher?.nickName || '',
dept: advisor.dept || advisor.teacher?.deptName || '',
phone: advisor.phone || advisor.teacher?.phonenumber || '',
email: advisor.email || advisor.teacher?.email || '',
userId: advisor.userId || advisor.teacher?.userId || ''
}));
} else {
(res.data as any).advisors = [];
}
} catch (error) {
console.warn('解析指导老师JSON数据失败:', error);
(res.data as any).advisors = [];
}
} else {
@@ -1047,6 +1071,17 @@ const handleUpdate = async (row?: StglActivityVO) => {
}
Object.assign(form.value, res.data);
// 确保指导老师数据正确设置
console.log('修改活动 - 设置表单数据后的状态:', {
advisors: form.value.advisors,
instructorList: form.value.instructorList,
instructors: form.value.instructors
});
// 更新formKey以强制重新渲染组件
formKey.value++;
dialog.visible = true;
dialog.title = '修改活动信息';
};
@@ -1059,6 +1094,12 @@ const submitForm = () => {
// 准备提交的数据
const submitData = { ...form.value } as any;
console.log('提交前 - 原始表单数据:', {
advisors: submitData.advisors,
instructorList: submitData.instructorList,
instructors: submitData.instructors
});
// 新增时自动设置活动状态为0(草稿)
if (!submitData.id) {
@@ -1085,64 +1126,36 @@ const submitForm = () => {
// 处理承办方数据
if (submitData.coOrganizerList && submitData.coOrganizerList.length > 0) {
submitData.coOrganizers = JSON.stringify(submitData.coOrganizerList);
} else {
submitData.coOrganizers = '[]'; // 修复:使用空JSON数组而不是undefined
}
// 处理指导老师数据
if (submitData.instructorList && submitData.instructorList.length > 0) {
submitData.instructors = JSON.stringify(submitData.instructorList);
}
// 处理AdvisorManager组件的指导老师数据
if (submitData.advisors && submitData.advisors.length > 0) {
console.log('提交前 - 原始advisors数据:', submitData.advisors);
// 确保advisors数据完整
submitData.advisors = submitData.advisors.map((advisor: any) => {
const processedAdvisor = {
...advisor,
name: advisor.name || advisor.teacher?.nickName || '',
dept: advisor.dept || advisor.teacher?.deptName || '',
phone: advisor.phone || advisor.teacher?.phonenumber || '',
email: advisor.email || advisor.teacher?.email || ''
};
console.log('处理后的指导老师数据:', processedAdvisor);
return processedAdvisor;
});
submitData.instructors = JSON.stringify(submitData.advisors);
console.log('提交的指导老师JSON数据:', submitData.instructors);
// 同步第一个指导老师信息到instructorList(保持兼容性)
if (submitData.advisors.length > 0) {
submitData.instructorList = submitData.advisors.map((advisor: any) => ({
name: advisor.name,
dept: advisor.dept,
phone: advisor.phone,
email: advisor.email
}));
console.log('同步的instructorList数据:', submitData.instructorList);
}
} else {
console.log('提交时 - 没有指导老师数据或数据为空');
submitData.instructors = '[]';
submitData.advisors = [];
submitData.instructors = '';
submitData.instructorList = [];
}
try {
if (form.value.id) {
await updateStglActivity(submitData);
} else {
await addStglActivity(submitData);
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;
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
} catch (error) {
console.error('提交失败:', error);
} finally {
buttonLoading.value = false;
}
}
});
};
@@ -1220,7 +1233,9 @@ const confirmCopy = async () => {
try {
await copyStglActivity({
sourceId: copyForm.sourceId!,
newActivityName: copyForm.newActivityName
newActivityName: copyForm.newActivityName,
activityStatus: '0', // 复制后的活动状态设置为0(草稿)
registrationStatus: '0' // 复制后的报名状态设置为0(未开始)
});
proxy?.$modal.msgSuccess('复制成功');
copyDialog.visible = false;
@@ -1361,9 +1376,7 @@ const handleInstructorsChange = (instructors: InstructorInfo[]) => {
/** 指导老师变化处理 */
const handleAdvisorsChange = (advisors: AdvisorVO[]) => {
console.log('handleAdvisorsChange - 指导老师数据变化:', advisors);
form.value.advisors = advisors;
console.log('handleAdvisorsChange - 更新后的form.advisors:', form.value.advisors);
};
/** 报名方式变化处理 */
@@ -1479,11 +1492,11 @@ const confirmApprove = async () => {
const endTime = new Date(activity.endTime);
if (now < startTime) {
newStatus = '1'; // 未开始
newStatus = '3'; // 已通过(活动还未开始)
} else if (now >= startTime && now <= endTime) {
newStatus = '4'; // 进行中
newStatus = '4'; // 进行中(活动正在进行)
} else {
newStatus = '5'; // 已结束
newStatus = '5'; // 已结束(活动已结束)
}
successMessage = '审批通过';
} else {
@@ -1586,6 +1599,11 @@ const hasActivityOperationPermission = (activity: StglActivityVO): boolean => {
return activity.createBy === userStore.userId;
}
// 志愿者队长只能操作自己创建的活动
if (proxy?.$auth.hasRole('stgl_volunteer_captain')) {
return activity.createBy === userStore.userId;
}
return false;
};
+4 -21
View File
@@ -52,14 +52,6 @@
</span>
</template>
</el-tab-pane>
<el-tab-pane label="我的社团" name="my">
<template #label>
<span class="tab-label">
<i class="el-icon-user-solid"></i>
我的社团
</span>
</template>
</el-tab-pane>
</el-tabs>
<!-- 工具栏 -->
@@ -462,7 +454,7 @@
<script setup name="StglRegister" lang="ts">
import { Document } from '@element-plus/icons-vue';
import { listStglClubRegister, listMyClubs, applyJoinClub, quitClub } from '@/api/stgl/register';
import { listStglClubRegister, applyJoinClub, quitClub } from '@/api/stgl/register';
import { StglClubRegisterVO, StglClubRegisterQuery, StglRegisterForm } from '@/api/stgl/register/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -473,7 +465,7 @@ const selectedClub = ref<StglClubRegisterVO | null>(null);
const loading = ref(true);
const showSearch = ref(true);
const total = ref(0);
const activeTab = ref('all');
const activeTab = ref('all'); // 固定为社团大厅
const viewMode = ref('card');
const applyLoading = ref(false);
const charterDownloading = ref(false); // 新增:章程下载加载状态
@@ -530,15 +522,7 @@ const getRoleTagType = (roleType: number): 'success' | 'warning' | 'info' | 'pri
const getList = async () => {
loading.value = true;
try {
let res;
switch (activeTab.value) {
case 'my':
res = await listMyClubs(queryParams);
break;
default:
res = await listStglClubRegister(queryParams);
break;
}
const res = await listStglClubRegister(queryParams);
clubList.value = res.rows;
total.value = res.total;
} catch (error) {
@@ -564,9 +548,8 @@ const resetQuery = () => {
/** 标签页切换 */
const handleTabChange = (tabName: string) => {
// 由于只有一个标签页,此函数可以简化或移除
activeTab.value = tabName;
queryParams.pageNum = 1;
getList();
};
/** 查看详情 */
@@ -17,7 +17,7 @@
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="联系方式" prop="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>
@@ -486,7 +486,14 @@ const getList = async () => {
delete queryData.status; // 删除单个status字段
}
// 确保phone参数正确传递到params中
if (queryData.params.phone) {
// 保持phone参数在params中,后端会从params中读取
console.log('JoinedMembersTab: phone参数:', queryData.params.phone);
}
console.log('JoinedMembersTab: 最终查询参数:', queryData);
console.log('JoinedMembersTab: phone参数值:', queryData.params?.phone);
const res = await listStglMember(queryData);
console.log('JoinedMembersTab: 查询结果:', res);
@@ -17,7 +17,7 @@
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="联系方式" prop="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>
@@ -290,7 +290,14 @@ const getList = async () => {
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);
+454 -44
View File
@@ -28,7 +28,8 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<!-- 隐藏是否评分过滤条件 -->
<!-- <el-col :span="6">
<el-form-item label="是否评分" prop="isRated">
<el-select
v-model="queryParams.isRated"
@@ -41,7 +42,7 @@
<el-option label="未评分" value="0" />
</el-select>
</el-form-item>
</el-col>
</el-col> -->
</el-row>
<el-row>
@@ -103,6 +104,16 @@
<span>{{ parseTime(scope.row.myRegistrationTime, '{y}-{m}-{d} {h}:{i}') }}</span>
</template>
</el-table-column>
<el-table-column label="报名状态" align="center" prop="myRegistrationStatus" width="100">
<template #default="scope">
<el-tag
:type="scope.row.myRegistrationStatus === '1' ? 'success' : scope.row.myRegistrationStatus === '2' ? 'danger' : 'warning'"
size="small"
>
{{ scope.row.myRegistrationStatus === '1' ? '已通过' : scope.row.myRegistrationStatus === '2' ? '已拒绝' : '待审核' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="我的评分" align="center" width="120">
<template #default="scope">
<div v-if="scope.row.myRatingStatus === 1" class="rating-cell">
@@ -160,9 +171,10 @@
</el-card>
<!-- 查看活动详情对话框 -->
<el-dialog title="活动详情" v-model="detailOpen" width="1000px" append-to-body>
<el-tabs type="border-card">
<el-tab-pane label="基本信息">
<el-dialog title="活动详情" v-model="detailOpen" width="1000px" append-to-body @close="handleDetailClose">
<div class="detail-content">
<!-- 基本信息 -->
<div class="detail-section">
<el-descriptions :column="2" border>
<el-descriptions-item label="活动名称">{{ detailForm.activityName }}</el-descriptions-item>
<el-descriptions-item label="活动类型">
@@ -170,6 +182,7 @@
</el-descriptions-item>
<el-descriptions-item label="活动主题">{{ detailForm.activityTheme || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="主办方">{{ detailForm.organizerName || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="承办方">{{ detailForm.undertakerName || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="关联组织">
<span v-if="detailForm.organizationName">
{{ detailForm.organizationName }}
@@ -179,6 +192,12 @@
</span>
<span v-else>未关联</span>
</el-descriptions-item>
<el-descriptions-item label="指导老师">
<span v-if="detailForm.instructors && detailForm.instructors.length > 0">
{{ getInstructorNames(detailForm.instructors) }}
</span>
<span v-else>暂无</span>
</el-descriptions-item>
<el-descriptions-item label="活动地点">{{ detailForm.location || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="开始时间">{{ parseTime(detailForm.startTime, '{y}-{m}-{d} {h}:{i}') }}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ parseTime(detailForm.endTime, '{y}-{m}-{d} {h}:{i}') }}</el-descriptions-item>
@@ -188,17 +207,9 @@
<el-descriptions-item label="报名状态">
<dict-tag :options="stgl_registration_status" :value="detailForm.registrationStatus" />
</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<el-tab-pane label="我的报名信息">
<el-descriptions :column="2" border>
<el-descriptions-item label="报名时间">{{ parseTime(detailForm.myRegistrationTime, '{y}-{m}-{d} {h}:{i}') }}</el-descriptions-item>
<el-descriptions-item label="报名状态">
<dict-tag :options="stgl_my_registration_status" :value="detailForm.myRegistrationStatus" />
</el-descriptions-item>
<el-descriptions-item label="评分状态">
<el-tag v-if="detailForm.myRatingStatus === 1" type="success">已评分</el-tag>
<el-tag v-else type="info">未评分</el-tag>
<el-descriptions-item label="报名类型">
<dict-tag :options="stgl_registration_type" :value="detailForm.registrationType" />
</el-descriptions-item>
<el-descriptions-item label="我的评分" v-if="detailForm.myRatingStatus === 1">
<div class="rating-cell">
@@ -213,19 +224,111 @@
<span class="rating-text">{{ detailForm.averageRating?.toFixed(1) }}</span>
</div>
</el-descriptions-item>
<el-descriptions-item label="活动简介" :span="2">
<div class="view-description">
{{ detailForm.description || '暂无简介' }}
</div>
</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<el-tab-pane label="活动海报" v-if="detailForm.posterUrl">
</div>
<!-- 活动海报 -->
<div v-if="detailForm.posterUrl" class="detail-section">
<h3 class="section-title">活动海报</h3>
<div class="view-poster">
<image-preview :src="detailForm.posterUrl" :width="300" :height="400" />
</div>
</el-tab-pane>
<el-tab-pane label="活动简介">
<div class="view-description">
{{ detailForm.description || '暂无简介' }}
</div>
<!-- 我的队伍信息 -->
<div v-if="detailForm.registrationType === '2'" class="detail-section">
<h3 class="section-title">我的队伍信息</h3>
<div v-if="teamInfoLoading" class="loading-container">
<el-skeleton :rows="3" animated />
</div>
</el-tab-pane>
</el-tabs>
<div v-else-if="teamInfo" class="team-info-content">
<!-- 队伍基本信息 -->
<el-descriptions :column="2" border class="team-basic-info">
<el-descriptions-item label="队伍名称" class="team-name-col">
<template #label>
<span class="team-name-label">队伍名称</span>
</template>
<span class="team-name-value">{{ teamInfo.teamName }}</span>
</el-descriptions-item>
<el-descriptions-item label="团队人数" class="team-size-col">{{ teamInfo.teamSize }}人</el-descriptions-item>
</el-descriptions>
<!-- 队长详细信息 -->
<div class="team-leader-section">
<h4 class="subsection-title">队长信息</h4>
<el-descriptions :column="2" border>
<el-descriptions-item label="姓名">{{ teamInfo.leaderName }}</el-descriptions-item>
<el-descriptions-item label="学号">{{ teamInfo.leaderStudentNo }}</el-descriptions-item>
<el-descriptions-item label="院系">{{ teamInfo.leaderDepartment || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="专业">{{ teamInfo.leaderMajor || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="班级">{{ teamInfo.leaderClassName || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ teamInfo.leaderPhone || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="邮箱" :span="2">{{ teamInfo.leaderEmail || '暂无' }}</el-descriptions-item>
</el-descriptions>
</div>
<!-- 队员详细信息 -->
<div v-if="parseTeamMembers(teamInfo.teamMembers).length > 0" class="team-members-section">
<h4 class="subsection-title">队员信息</h4>
<el-table :data="parseTeamMembers(teamInfo.teamMembers)" border stripe size="small" class="members-table">
<el-table-column label="姓名" prop="studentName" align="center" width="100" />
<el-table-column label="学号" prop="studentNo" align="center" width="120" />
<el-table-column label="院系" prop="department" align="center" min-width="120" show-overflow-tooltip>
<template #default="{ row }">
{{ row.department || '暂无' }}
</template>
</el-table-column>
<el-table-column label="专业" prop="major" align="center" min-width="120" show-overflow-tooltip>
<template #default="{ row }">
{{ row.major || '暂无' }}
</template>
</el-table-column>
<el-table-column label="班级" prop="className" align="center" width="100" show-overflow-tooltip>
<template #default="{ row }">
{{ row.className || '暂无' }}
</template>
</el-table-column>
<el-table-column label="联系电话" prop="phone" align="center" width="120" show-overflow-tooltip>
<template #default="{ row }">
{{ row.phone || '暂无' }}
</template>
</el-table-column>
<el-table-column label="邮箱" prop="email" align="center" min-width="150" show-overflow-tooltip>
<template #default="{ row }">
{{ row.email || '暂无' }}
</template>
</el-table-column>
<el-table-column label="身份" align="center" width="80">
<template #default="{ row }">
<el-tag
:type="row.isLeader === 1 ? 'success' : 'info'"
size="small"
>
{{ row.isLeader === 1 ? '队长' : '队员' }}
</el-tag>
</template>
</el-table-column>
</el-table>
</div>
<div v-else class="no-members">
<el-empty description="暂无队员信息" :image-size="60" />
</div>
</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-empty>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="detailOpen = false">关 闭</el-button>
@@ -279,7 +382,15 @@
</el-dialog>
<!-- 签到对话框 -->
<el-dialog title="活动签到" v-model="attendanceOpen" width="560px" append-to-body>
<el-dialog
title="活动签到"
v-model="attendanceOpen"
width="560px"
append-to-body
:close-on-click-modal="false"
:close-on-press-escape="true"
class="attendance-dialog"
>
<div class="attendance-content">
<div class="attendance-actions">
<el-button
@@ -306,7 +417,7 @@
</div>
<!-- 签到记录 -->
<div class="attendance-records">
<div class="attendance-records" v-if="attendanceRecords.length > 0">
<div class="records-header">
<span class="records-title">签到记录</span>
<el-button
@@ -325,6 +436,7 @@
size="small"
class="records-table"
v-loading="recordsLoading"
:max-height="getTableMaxHeight()"
>
<el-table-column prop="signInTime" label="签到时间" width="200">
<template #default="{ row }">
@@ -348,10 +460,6 @@
</template>
</el-table-column>
</el-table>
<div v-if="attendanceRecords.length === 0" class="no-records">
<el-empty description="暂无签到记录" :image-size="60" />
</div>
</div>
</div>
</el-dialog>
@@ -359,25 +467,29 @@
</template>
<script setup name="MyActivity" lang="ts">
import { ref, reactive, toRefs, getCurrentInstance, ComponentInternalInstance, computed, nextTick } from 'vue';
import { ref, reactive, toRefs, getCurrentInstance, ComponentInternalInstance, computed, nextTick, onMounted } from 'vue';
import { getMyActivities, rateActivity } from '@/api/stgl/my';
import type { MyActivityVO, ActivityRatingForm, MyActivityQuery } from '@/api/stgl/my/types';
import { Calendar, Location, Clock, Check, Close } from '@element-plus/icons-vue';
import { getInfo } from '@/api/login'; // 添加获取用户详细信息的API
import request from '@/utils/request';
import type { FormInstance } from 'element-plus';
import { getTeamsByActivityId } from '@/api/stgl/activityteaminfo';
import type { StglActivityTeamInfoVo } from '@/api/stgl/activityteaminfo/types';
import { listActivityRegister } from '@/api/stgl/activityregister'; // 添加获取报名记录的API
import { listStglActivity } from '@/api/stgl/activity'; // 添加获取所有活动的API
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { stgl_activity_type, stgl_activity_status, stgl_registration_status, stgl_my_registration_status } = toRefs<any>(
proxy?.useDict('stgl_activity_type', 'stgl_activity_status', 'stgl_registration_status', 'stgl_my_registration_status')
const { stgl_activity_type, stgl_activity_status, stgl_registration_status, stgl_registration_type } = toRefs<any>(
proxy?.useDict('stgl_activity_type', 'stgl_activity_status', 'stgl_registration_status', 'stgl_registration_type')
);
const queryRef = ref();
const showSearch = ref(true);
const queryParams = ref<MyActivityQuery>({
activityName: '',
activityType: '',
isRated: ''
activityType: ''
// isRated: '' // 已隐藏是否评分过滤条件
});
const activityList = ref<MyActivityVO[]>([]);
const loading = ref(true);
@@ -387,6 +499,10 @@ const ratingOpen = ref(false);
const ratingLoading = ref(false);
const ratingFormRef = ref<FormInstance>();
// 新增队伍信息相关变量
const teamInfo = ref<StglActivityTeamInfoVo | null>(null);
const teamInfoLoading = ref(false);
const ratingForm = ref<ActivityRatingForm & { activityName?: string; studentName?: string; studentNo?: string }>({
activityId: '',
contentScore: 0,
@@ -433,8 +549,29 @@ const getList = async () => {
const params = Object.fromEntries(
Object.entries(queryParams.value).filter(([_, value]) => value !== '' && value !== undefined && value !== null)
);
// 直接使用getMyActivities接口,后端已经处理了所有情况(包括用户作为队员参与的活动)
const res = await getMyActivities(params);
activityList.value = res.data;
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 => ({
id: activity.id,
activityName: activity.activityName,
registrationType: activity.registrationType,
myRegistrationStatus: activity.myRegistrationStatus
}))
});
} catch (error) {
console.error('查询我的活动列表失败:', error);
proxy?.$modal.msgError('查询我的活动列表失败');
@@ -443,6 +580,8 @@ const getList = async () => {
}
};
/** 搜索按钮操作 */
const handleQuery = () => {
getList();
@@ -455,11 +594,95 @@ const resetQuery = () => {
};
/** 查看详情按钮操作 */
const handleView = (row: MyActivityVO) => {
const handleView = async (row: MyActivityVO) => {
detailForm.value = { ...row };
detailOpen.value = true;
// 如果是团队报名,获取队伍信息
if (row.registrationType === '2') {
await getTeamInfo(row.id);
} else {
teamInfo.value = null;
}
};
/** 获取队伍信息 */
const getTeamInfo = async (activityId: string | number) => {
if (!activityId) return;
teamInfoLoading.value = true;
try {
const res = await getTeamsByActivityId(activityId);
if (res.data && res.data.length > 0) {
// 找到当前用户所在的队伍
const userInfo = await getCurrentUserDetailInfo();
if (!userInfo) {
console.warn('未获取到用户信息');
teamInfo.value = null;
return;
}
const currentUserTeam = res.data.find((team: StglActivityTeamInfoVo) => {
// 检查是否为队长
if (team.leaderStudentNo === userInfo.userName) {
return true;
}
// 检查是否为队员
if (team.teamMembers) {
try {
const members = JSON.parse(team.teamMembers);
return members.some((member: any) => member.studentNo === userInfo.userName);
} catch (error) {
console.error('解析队员信息失败:', error);
return false;
}
}
return false;
});
teamInfo.value = currentUserTeam || null;
if (currentUserTeam) {
console.log('获取到队伍信息:', {
teamName: currentUserTeam.teamName,
leaderName: currentUserTeam.leaderName,
leaderStudentNo: currentUserTeam.leaderStudentNo,
leaderPhone: currentUserTeam.leaderPhone,
leaderEmail: currentUserTeam.leaderEmail,
leaderDepartment: currentUserTeam.leaderDepartment,
leaderMajor: currentUserTeam.leaderMajor,
leaderClassName: currentUserTeam.leaderClassName,
teamMembers: currentUserTeam.teamMembers
});
} else {
console.warn('未找到当前用户所在的队伍');
}
} else {
teamInfo.value = null;
console.log('该活动暂无队伍信息');
}
} catch (error) {
console.error('获取队伍信息失败:', error);
teamInfo.value = null;
proxy?.$modal.msgError('获取队伍信息失败,请稍后重试');
} finally {
teamInfoLoading.value = false;
}
};
/** 解析队员信息 */
const parseTeamMembers = (teamMembersStr: string): any[] => {
if (!teamMembersStr) return [];
try {
return JSON.parse(teamMembersStr);
} catch (error) {
console.error('解析队员信息失败:', error);
return [];
}
};
/** 活动评分按钮操作 */
const handleRate = async (row: MyActivityVO) => {
try {
@@ -854,6 +1077,18 @@ const formatDateTime = (dateStr: string): string => {
});
};
/** 计算表格最大高度 */
const getTableMaxHeight = () => {
const recordCount = attendanceRecords.value.length;
if (recordCount <= 3) {
return 200; // 1-3条记录
} else if (recordCount <= 6) {
return 300; // 4-6条记录
} else {
return 400; // 超过6条记录,设置最大高度
}
};
/** 计算参与时长 */
const calculateDuration = (signInTime: string, signOutTime: string): string => {
if (!signInTime) return '-';
@@ -873,6 +1108,39 @@ 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 {
instructorArray = JSON.parse(instructors);
} catch (error) {
console.error('解析指导老师数据失败:', error);
return instructors; // 如果解析失败,直接返回原字符串
}
} else if (Array.isArray(instructors)) {
instructorArray = instructors;
} else {
return '暂无';
}
if (!instructorArray || instructorArray.length === 0) return '暂无';
return instructorArray
.map(instructor => instructor.name || instructor.nickName || '未知')
.filter(name => name && name !== '未知')
.join('、');
};
/** 对话框关闭时重置队伍信息 */
const handleDetailClose = () => {
teamInfo.value = null;
};
onMounted(() => {
getList();
});
@@ -947,6 +1215,28 @@ onMounted(() => {
}
/* 查看对话框样式 */
.detail-content {
padding: 0;
}
.detail-section {
margin-bottom: 24px;
}
.detail-section:last-child {
margin-bottom: 0;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin: 0 0 16px 0;
padding-bottom: 8px;
border-bottom: 2px solid #409eff;
display: inline-block;
}
.view-poster {
display: flex;
align-items: center;
@@ -964,53 +1254,173 @@ onMounted(() => {
line-height: 1.6;
white-space: pre-wrap;
word-break: break-word;
background-color: #fafafa;
}
/* 签到弹窗样式 */
.attendance-dialog :deep(.el-dialog__body) {
padding: 16px 20px;
max-height: 70vh;
overflow-y: auto;
}
.attendance-content {
padding: 20px 0;
padding: 8px 0;
}
.attendance-actions {
display: flex;
justify-content: center;
gap: 16px;
margin-bottom: 16px;
}
.action-btn {
min-width: 120px;
height: 44px;
height: 40px;
font-size: 16px;
font-weight: 500;
}
/* 签到记录样式 */
.attendance-records {
margin-top: 24px;
margin-top: 16px;
border-top: 1px solid #e4e7ed;
padding-top: 20px;
padding-top: 16px;
}
.records-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
margin-bottom: 12px;
}
.records-title {
font-size: 16px;
font-size: 15px;
font-weight: 600;
color: #303133;
}
.records-table {
margin-bottom: 16px;
margin-bottom: 12px;
}
.records-table :deep(.el-table__body-wrapper) {
overflow-y: auto;
}
.no-records {
text-align: center;
padding: 40px 0;
padding: 20px 0;
color: #909399;
}
.no-records :deep(.el-empty__image) {
width: 60px;
height: 60px;
}
.no-records :deep(.el-empty__description) {
font-size: 14px;
margin-top: 8px;
}
/* 队伍信息样式 */
.team-info-content {
margin-top: 8px;
}
/* 队伍基本信息 */
.team-basic-info {
margin-bottom: 20px;
}
/* 队伍名称列宽度控制 */
.team-basic-info :deep(.team-name-col) {
width: 30%;
}
/* 团队人数列宽度控制 */
.team-basic-info :deep(.team-size-col) {
width: 70%;
}
/* 队伍名称标签单元格宽度控制 */
.team-basic-info :deep(.team-name-label) {
width: 25%;
}
/* 队伍名称值单元格宽度控制 */
.team-basic-info :deep(.team-name-value) {
width: 20%;
}
/* 队长信息区域 */
.team-leader-section {
margin-bottom: 20px;
}
.subsection-title {
font-size: 14px;
font-weight: 600;
color: #606266;
margin: 0 0 12px 0;
padding-left: 8px;
border-left: 3px solid #409eff;
}
/* 队员信息区域 */
.team-members-section {
margin-bottom: 20px;
}
.members-table {
margin-top: 12px;
}
.members-table :deep(.el-table__header) {
background-color: #fafafa;
}
.members-table :deep(.el-table__row) {
transition: background-color 0.2s ease;
}
.members-table :deep(.el-table__row:hover) {
background-color: #f5f7fa;
}
/* 空状态样式 */
.no-members {
padding: 20px;
display: flex;
justify-content: center;
}
.team-description {
padding: 12px;
border: 1px solid #e4e7ed;
border-radius: 4px;
min-height: 60px;
font-size: 14px;
color: #303133;
line-height: 1.6;
white-space: pre-wrap;
word-break: break-word;
background-color: #fafafa;
}
/* 加载状态样式 */
.loading-container {
padding: 20px;
display: flex;
justify-content: center;
}
.no-team-info {
padding: 20px;
display: flex;
justify-content: center;
}
</style>
+454
View File
@@ -0,0 +1,454 @@
<template>
<div class="p-2 page-container">
<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="hover">
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="班级" prop="classId">
<el-select v-model="queryParams.classId" placeholder="请选择班级" clearable style="width: 100%" @change="handleClassChange">
<el-option v-for="item in classList" :key="item.id" :label="item.className" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="学生姓名" prop="studentName">
<el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="学号" prop="studentNo">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never" class="button-card table-card">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="View" @click="handleView" v-hasPermi="['stgl:stclass:query']">查看</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Download" @click="handleExport" v-hasPermi="['stgl:stclass:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="studentList" @selection-change="handleSelectionChange" @row-dblclick="handleView">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="学生姓名" align="center" prop="studentName" width="120" />
<el-table-column label="学号" align="center" prop="studentNo" width="140" />
<el-table-column label="性别" align="center" prop="gender" width="80">
<template #default="scope">
<dict-tag :options="sys_user_sex" :value="scope.row.gender" />
</template>
</el-table-column>
<el-table-column label="联系电话" align="center" prop="phone" width="120" />
<el-table-column label="加入社团" align="center" width="100">
<template #default="scope">
<el-button
v-if="scope.row.clubCount > 0"
type="primary"
link
@click="handleViewClubs(scope.row)"
style="font-size: 14px; font-weight: bold; color: #409eff"
>
{{ scope.row.clubCount }}
</el-button>
<span v-else class="no-data">0</span>
</template>
</el-table-column>
<el-table-column label="加入志愿者队伍" align="center" width="120">
<template #default="scope">
<el-button
v-if="scope.row.volunteerTeamCount > 0"
type="success"
link
@click="handleViewVolunteerTeams(scope.row)"
style="font-size: 14px; font-weight: bold; color: #67c23a"
>
{{ scope.row.volunteerTeamCount }}
</el-button>
<span v-else class="no-data">0</span>
</template>
</el-table-column>
<el-table-column label="参与活动" align="center" width="100">
<template #default="scope">
<el-button
v-if="scope.row.activityCount > 0"
type="warning"
link
@click="handleViewActivities(scope.row)"
style="font-size: 14px; font-weight: bold; color: #e6a23c"
>
{{ scope.row.activityCount }}
</el-button>
<span v-else class="no-data">0</span>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</div>
</el-card>
<!-- 社团详情弹窗 -->
<el-dialog v-model="clubDialog.visible" :title="clubDialog.title" width="800px" append-to-body>
<div v-if="clubDetails.length > 0">
<div v-for="(club, index) in clubDetails" :key="club.id" class="club-item">
<el-divider v-if="index > 0" />
<el-descriptions :column="2" border>
<el-descriptions-item label="社团名称">{{ club.clubName }}</el-descriptions-item>
<el-descriptions-item label="社团类型">
<dict-tag :options="stgl_club_type" :value="club.clubType" />
</el-descriptions-item>
<el-descriptions-item label="成立日期">{{ club.establishDate }}</el-descriptions-item>
<el-descriptions-item label="成员规模">{{ club.memberLimit }}</el-descriptions-item>
<el-descriptions-item label="社长姓名">{{ club.presidentName }}</el-descriptions-item>
<el-descriptions-item label="社长联系方式">{{ club.presidentPhone }}</el-descriptions-item>
<el-descriptions-item label="社长院系">{{ club.presidentDept }}</el-descriptions-item>
<el-descriptions-item label="社长专业">{{ club.presidentMajor }}</el-descriptions-item>
<el-descriptions-item label="社长班级">{{ club.presidentClass }}</el-descriptions-item>
<el-descriptions-item label="指导老师姓名">{{ club.advisorName }}</el-descriptions-item>
<el-descriptions-item label="指导老师部门">{{ club.advisorDept }}</el-descriptions-item>
<el-descriptions-item label="指导老师电话">{{ club.advisorPhone }}</el-descriptions-item>
</el-descriptions>
</div>
</div>
<div v-else class="no-data-container">
<span class="no-data-text">暂无社团信息</span>
</div>
</el-dialog>
<!-- 志愿者队伍详情弹窗 -->
<el-dialog v-model="volunteerTeamDialog.visible" :title="volunteerTeamDialog.title" width="800px" append-to-body>
<div v-if="volunteerTeamDetails.length > 0">
<div v-for="(team, index) in volunteerTeamDetails" :key="team.id" class="team-item">
<el-divider v-if="index > 0" />
<el-descriptions :column="2" border>
<el-descriptions-item label="队伍名称">{{ team.teamName }}</el-descriptions-item>
<el-descriptions-item label="成立日期">{{ team.establishDate }}</el-descriptions-item>
<el-descriptions-item label="队长姓名">{{ team.leaderName }}</el-descriptions-item>
<el-descriptions-item label="队长联系方式">{{ team.leaderPhone }}</el-descriptions-item>
<el-descriptions-item label="队长院系">{{ team.leaderDept }}</el-descriptions-item>
<el-descriptions-item label="队长专业">{{ team.leaderMajor }}</el-descriptions-item>
<el-descriptions-item label="队长班级">{{ team.leaderClass }}</el-descriptions-item>
<el-descriptions-item label="指导老师姓名">{{ team.advisorName }}</el-descriptions-item>
<el-descriptions-item label="指导老师部门">{{ team.advisorDept }}</el-descriptions-item>
<el-descriptions-item label="指导老师电话">{{ team.advisorPhone }}</el-descriptions-item>
</el-descriptions>
</div>
</div>
<div v-else class="no-data-container">
<span class="no-data-text">暂无志愿者队伍信息</span>
</div>
</el-dialog>
<!-- 活动详情弹窗 -->
<el-dialog v-model="activityDialog.visible" :title="activityDialog.title" width="800px" append-to-body>
<div v-if="activityDetails.length > 0">
<div v-for="(activity, index) in activityDetails" :key="activity.id" class="activity-item">
<el-divider v-if="index > 0" />
<el-descriptions :column="2" border>
<el-descriptions-item label="活动名称">{{ activity.activityName }}</el-descriptions-item>
<el-descriptions-item label="活动类型">
<dict-tag :options="stgl_activity_type" :value="activity.activityType" />
</el-descriptions-item>
<el-descriptions-item label="活动主题">{{ activity.activityTheme }}</el-descriptions-item>
<el-descriptions-item label="活动地点">{{ activity.location }}</el-descriptions-item>
<el-descriptions-item label="活动时间">{{ activity.activityTime }}</el-descriptions-item>
<el-descriptions-item label="关联社团/队伍">{{ activity.relatedOrganization }}</el-descriptions-item>
<el-descriptions-item label="指导老师姓名">{{ activity.advisorName }}</el-descriptions-item>
<el-descriptions-item label="指导老师部门">{{ activity.advisorDept }}</el-descriptions-item>
<el-descriptions-item label="指导老师电话">{{ activity.advisorPhone }}</el-descriptions-item>
</el-descriptions>
</div>
</div>
<div v-else class="no-data-container">
<span class="no-data-text">暂无活动信息</span>
</div>
</el-dialog>
</div>
</template>
<script setup name="StglClass" lang="ts">
import {
getTeacherClasses,
listStglClass,
getStudentClubs,
getStudentVolunteerTeams,
getStudentActivities,
exportStglClass
} from '@/api/stgl/stclass';
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 loading = ref(false);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const studentList = ref<StglClassVO[]>([]);
const classList = ref<StglClassDetailVO[]>([]);
const queryFormRef = ref<ElFormInstance>();
const queryParams = ref<StglClassQuery>({
pageNum: 1,
pageSize: 10,
classId: undefined,
studentName: undefined,
studentNo: undefined
});
// 社团详情弹窗
const clubDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const clubDetails = ref<ClubDetailVO[]>([]);
// 志愿者队伍详情弹窗
const volunteerTeamDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const volunteerTeamDetails = ref<VolunteerTeamDetailVO[]>([]);
// 活动详情弹窗
const activityDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const activityDetails = ref<ActivityDetailVO[]>([]);
/** 查询班主任的班级列表 */
const getTeacherClassList = async () => {
try {
const res = await getTeacherClasses();
if (res.code === 200 && res.data) {
classList.value = res.data;
// 默认选择第一个班级
if (classList.value.length > 0) {
queryParams.value.classId = classList.value[0].id;
await getList();
}
}
} catch (error) {
console.error('获取班级列表失败:', error);
proxy?.$modal.msgError('获取班级列表失败');
}
};
/** 查询学生列表 */
const getList = async () => {
loading.value = true;
try {
const res = await listStglClass(queryParams.value);
if (res.code === 200) {
studentList.value = res.rows;
total.value = res.total;
}
} catch (error) {
console.error('查询学生列表失败:', error);
proxy?.$modal.msgError('查询学生列表失败');
} finally {
loading.value = false;
}
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: StglClassVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 班级切换处理 */
const handleClassChange = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 查看按钮操作 */
const handleView = (row?: StglClassVO) => {
const _row = row || studentList.value.find(item => item.id === ids.value[0]);
if (!_row) {
proxy?.$modal.msgError('请选择要查看的学生');
return;
}
// 这里可以跳转到学生详情页面或显示学生详细信息
proxy?.$modal.msgInfo(`查看学生:${_row.studentName}`);
};
/** 查看社团详情 */
const handleViewClubs = async (row: StglClassVO) => {
try {
const res = await getStudentClubs(row.id);
if (res.code === 200) {
clubDetails.value = res.data || [];
clubDialog.title = `${row.studentName} - 社团信息`;
clubDialog.visible = true;
}
} catch (error) {
console.error('获取社团详情失败:', error);
proxy?.$modal.msgError('获取社团详情失败');
}
};
/** 查看志愿者队伍详情 */
const handleViewVolunteerTeams = async (row: StglClassVO) => {
try {
const res = await getStudentVolunteerTeams(row.id);
if (res.code === 200) {
volunteerTeamDetails.value = res.data || [];
volunteerTeamDialog.title = `${row.studentName} - 志愿者队伍信息`;
volunteerTeamDialog.visible = true;
}
} catch (error) {
console.error('获取志愿者队伍详情失败:', error);
proxy?.$modal.msgError('获取志愿者队伍详情失败');
}
};
/** 查看活动详情 */
const handleViewActivities = async (row: StglClassVO) => {
try {
const res = await getStudentActivities(row.id);
if (res.code === 200) {
activityDetails.value = res.data || [];
activityDialog.title = `${row.studentName} - 活动信息`;
activityDialog.visible = true;
}
} catch (error) {
console.error('获取活动详情失败:', error);
proxy?.$modal.msgError('获取活动详情失败');
}
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'stgl/stclass/export',
queryParams.value,
`班级学生参与情况_${new Date().getTime()}.xlsx`
);
};
onMounted(() => {
getTeacherClassList();
});
</script>
<style scoped>
/* 页面容器样式 */
.page-container {
padding: 8px;
background-color: #f5f5f5;
}
/* 表格卡片布局 */
.table-card {
margin-top: 0;
border-radius: 12px;
border: 1px solid #e4e7ed;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.table-card :deep(.el-card__body) {
padding: 0px 20px 20px 20px !important;
}
/* 移除按钮栏底部边框 */
.button-card :deep(.el-card__header) {
border-bottom: none !important;
background: linear-gradient(135deg, #fafbff 0%, #f8fafc 100%);
padding: 16px 20px;
}
/* 搜索按钮容器样式 */
.search-btn-container {
display: flex;
justify-content: flex-end;
margin-top: 10px;
.el-form-item {
margin-bottom: 0 !important;
}
}
/* 分页容器样式 */
.pagination-container {
display: flex;
justify-content: flex-end;
padding: 16px 20px 0 0 !important;
background-color: #fff;
margin-top: 0px !important;
}
/* 无数据样式 */
.no-data {
color: #909399;
font-size: 14px;
}
.no-data-container {
display: flex;
justify-content: center;
align-items: center;
height: 200px;
}
.no-data-text {
color: #909399;
font-size: 16px;
}
/* 详情弹窗样式 */
.club-item,
.team-item,
.activity-item {
margin-bottom: 20px;
}
.club-item:last-child,
.team-item:last-child,
.activity-item:last-child {
margin-bottom: 0;
}
</style>
@@ -46,15 +46,15 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['stgl:volunteerMember:add']">新增</el-button>
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['stgl:volunteermember:add']">新增</el-button>
</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 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 type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['stgl:volunteermember:remove']"
>删除</el-button
>
</el-col>
@@ -67,8 +67,8 @@
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Download" @click="importTemplate">下载模板</el-dropdown-item>
<el-dropdown-item icon="Top" @click="handleImport" v-hasPermi="['stgl:volunteerMember:import']">导入数据</el-dropdown-item>
<el-dropdown-item icon="Download" @click="handleExport" v-hasPermi="['stgl:volunteerMember:export']">导出数据</el-dropdown-item>
<el-dropdown-item icon="Top" @click="handleImport" v-hasPermi="['stgl:volunteermember:import']">导入数据</el-dropdown-item>
<el-dropdown-item icon="Download" @click="handleExport" v-hasPermi="['stgl:volunteermember:export']">导出数据</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -122,7 +122,7 @@
size="default"
style="font-size: 16px; margin-right: 8px; color: blue"
@click="handleView(scope.row)"
v-hasPermi="['stgl:volunteerMember:query']"
v-hasPermi="['stgl:volunteermember:query']"
></el-button>
</el-tooltip>
@@ -160,7 +160,7 @@
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleUpdate(scope.row)"
v-hasPermi="['stgl:volunteerMember:edit']"
v-hasPermi="['stgl:volunteermember:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
@@ -171,7 +171,7 @@
size="default"
style="font-size: 16px"
@click="handleDelete(scope.row)"
v-hasPermi="['stgl:volunteerMember:remove']"
v-hasPermi="['stgl:volunteermember:remove']"
></el-button>
</el-tooltip>
</template>
@@ -445,10 +445,19 @@ const getList = async () => {
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 = { statusList: ['2', '4'] } as any;
// 使用params传递多状态查询,传递字符串格式
(queryData.params as any).statusList = '2,4';
delete queryData.status; // 删除单个status字段
}
@@ -228,10 +228,20 @@ const getList = async () => {
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传递多状态查询
queryData.params = { statusList: ['0', '3'] } as any;
// 使用params传递多状态查询,保留现有params内容,传递字符串格式
(queryData.params as any).statusList = '0,3';
delete queryData.status; // 删除单个status字段
}
@@ -240,16 +250,25 @@ const getList = async () => {
const res = await listVolunteerMember(queryData);
console.log('PendingMembersTab: 查询结果:', res);
memberList.value = res.rows;
total.value = res.total;
// 调试:检查数据完整性
if (memberList.value.length > 0) {
console.log('PendingMembersTab: 第一条成员数据:', memberList.value[0]);
console.log('PendingMembersTab: props.teamId:', props.teamId);
// 防御性编程:检查响应数据结构
if (!res || !res.rows) {
console.warn('PendingMembersTab: API返回数据格式异常:', res);
memberList.value = [];
total.value = 0;
} else {
memberList.value = res.rows;
total.value = res.total;
// 调试:检查数据完整性
if (memberList.value.length > 0) {
console.log('PendingMembersTab: 第一条成员数据:', memberList.value[0]);
console.log('PendingMembersTab: props.teamId:', props.teamId);
}
}
} catch (error) {
console.error('PendingMembersTab: 查询成员列表失败:', error);
memberList.value = [];
total.value = 0;
proxy?.$modal.msgError('查询成员列表失败');
} finally {
loading.value = false;
+2 -2
View File
@@ -770,7 +770,7 @@ const data = reactive({
advisorEmail: null,
advisorUserId: null,
status: '0',
sighOpen: 'Y',
sighOpen: 'N',
remark: null
} as any,
detailForm: {} as any,
@@ -864,7 +864,7 @@ function reset() {
advisorEmail: null,
advisorUserId: null,
status: '0',
sighOpen: 'Y',
sighOpen: 'N',
remark: null
};