stgl1.2
This commit is contained in:
1 parent
4c2154bfde
commit
e1980bd202
12 files changed
+5154
No files matched your search
+40
@@ -0,0 +1,40 @@
|
||||
package org.dromara.stgl.domain.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 社团活动批量审核请求对象
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-01-31
|
||||
*/
|
||||
@Data
|
||||
public class StglBatchApprovalBo {
|
||||
|
||||
/**
|
||||
* 报名ID数组
|
||||
*/
|
||||
@NotEmpty(message = "审核ID列表不能为空")
|
||||
private List<Long> ids;
|
||||
|
||||
/**
|
||||
* 审核状态 1-通过 2-驳回
|
||||
*/
|
||||
@NotNull(message = "审核状态不能为空")
|
||||
@Min(value = 1, message = "审核状态值无效")
|
||||
@Max(value = 2, message = "审核状态值无效")
|
||||
private Integer approvalStatus;
|
||||
|
||||
/**
|
||||
* 审核意见
|
||||
*/
|
||||
@Size(max = 500, message = "审核意见不能超过500个字符")
|
||||
private String approvalRemark;
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
package org.dromara.stgl.utils;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.system.domain.SysUserRole;
|
||||
import org.dromara.system.mapper.SysUserRoleMapper;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 指导老师角色分配工具类
|
||||
* 用于在社团、志愿者队伍、活动等模块中自动分配指导老师角色
|
||||
*
|
||||
* @author Cursor AI
|
||||
* @date 2025-01-03
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class AdvisorRoleAssigner {
|
||||
|
||||
private final SysUserRoleMapper sysUserRoleMapper;
|
||||
|
||||
/**
|
||||
* 指导老师角色ID
|
||||
*/
|
||||
private static final Long ADVISOR_ROLE_ID = 1959880215953952769L;
|
||||
|
||||
/**
|
||||
* 为指导老师分配角色
|
||||
*
|
||||
* @param advisorUserIds 指导老师用户ID列表
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void assignAdvisorRole(List<Long> advisorUserIds) {
|
||||
if (advisorUserIds == null || advisorUserIds.isEmpty()) {
|
||||
log.info("没有指导老师需要分配角色");
|
||||
return;
|
||||
}
|
||||
|
||||
List<SysUserRole> userRoles = new ArrayList<>();
|
||||
for (Long userId : advisorUserIds) {
|
||||
if (userId != null) {
|
||||
// 检查用户是否已有该角色
|
||||
if (!hasUserRole(userId, ADVISOR_ROLE_ID)) {
|
||||
SysUserRole userRole = new SysUserRole();
|
||||
userRole.setUserId(userId);
|
||||
userRole.setRoleId(ADVISOR_ROLE_ID);
|
||||
userRoles.add(userRole);
|
||||
} else {
|
||||
log.debug("用户 {} 已有指导老师角色,跳过分配", userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!userRoles.isEmpty()) {
|
||||
try {
|
||||
// 批量插入用户角色关系
|
||||
boolean success = sysUserRoleMapper.insertBatch(userRoles);
|
||||
if (success) {
|
||||
log.info("成功为 {} 个指导老师分配角色,角色ID: {}", userRoles.size(), ADVISOR_ROLE_ID);
|
||||
} else {
|
||||
log.error("批量分配指导老师角色失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("分配指导老师角色时发生异常", e);
|
||||
throw new RuntimeException("分配指导老师角色失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 为单个指导老师分配角色
|
||||
*
|
||||
* @param advisorUserId 指导老师用户ID
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void assignAdvisorRole(Long advisorUserId) {
|
||||
if (advisorUserId == null) {
|
||||
log.warn("指导老师用户ID为空,跳过角色分配");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查用户是否已有该角色
|
||||
if (!hasUserRole(advisorUserId, ADVISOR_ROLE_ID)) {
|
||||
try {
|
||||
SysUserRole userRole = new SysUserRole();
|
||||
userRole.setUserId(advisorUserId);
|
||||
userRole.setRoleId(ADVISOR_ROLE_ID);
|
||||
|
||||
int result = sysUserRoleMapper.insert(userRole);
|
||||
if (result > 0) {
|
||||
log.info("成功为用户 {} 分配指导老师角色,角色ID: {}", advisorUserId, ADVISOR_ROLE_ID);
|
||||
} else {
|
||||
log.error("为用户 {} 分配指导老师角色失败", advisorUserId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("为用户 {} 分配指导老师角色时发生异常", advisorUserId, e);
|
||||
throw new RuntimeException("分配指导老师角色失败", e);
|
||||
}
|
||||
} else {
|
||||
log.debug("用户 {} 已有指导老师角色,跳过分配", advisorUserId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户是否已有指定角色
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param roleId 角色ID
|
||||
* @return 是否已有该角色
|
||||
*/
|
||||
private boolean hasUserRole(Long userId, Long roleId) {
|
||||
try {
|
||||
// 查询用户角色关系
|
||||
List<Long> roleIds = sysUserRoleMapper.selectUserIdsByRoleId(roleId);
|
||||
return roleIds.contains(userId);
|
||||
} catch (Exception e) {
|
||||
log.error("检查用户角色关系时发生异常,用户ID: {}, 角色ID: {}", userId, roleId, e);
|
||||
// 如果查询失败,假设用户没有该角色,继续分配
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指导老师角色
|
||||
*
|
||||
* @param advisorUserId 指导老师用户ID
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void removeAdvisorRole(Long advisorUserId) {
|
||||
if (advisorUserId == null) {
|
||||
log.warn("指导老师用户ID为空,跳过角色移除");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 删除用户角色关系
|
||||
int result = sysUserRoleMapper.delete(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SysUserRole>()
|
||||
.eq(SysUserRole::getUserId, advisorUserId)
|
||||
.eq(SysUserRole::getRoleId, ADVISOR_ROLE_ID)
|
||||
);
|
||||
|
||||
if (result > 0) {
|
||||
log.info("成功移除用户 {} 的指导老师角色", advisorUserId);
|
||||
} else {
|
||||
log.debug("用户 {} 没有指导老师角色,无需移除", advisorUserId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("移除用户 {} 的指导老师角色时发生异常", advisorUserId, e);
|
||||
throw new RuntimeException("移除指导老师角色失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量移除指导老师角色
|
||||
*
|
||||
* @param advisorUserIds 指导老师用户ID列表
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void removeAdvisorRoles(List<Long> advisorUserIds) {
|
||||
if (advisorUserIds == null || advisorUserIds.isEmpty()) {
|
||||
log.info("没有指导老师需要移除角色");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 批量删除用户角色关系
|
||||
int result = sysUserRoleMapper.delete(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SysUserRole>()
|
||||
.in(SysUserRole::getUserId, advisorUserIds)
|
||||
.eq(SysUserRole::getRoleId, ADVISOR_ROLE_ID)
|
||||
);
|
||||
|
||||
if (result > 0) {
|
||||
log.info("成功移除 {} 个用户的指导老师角色", result);
|
||||
} else {
|
||||
log.debug("没有用户需要移除指导老师角色");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("批量移除指导老师角色时发生异常", e);
|
||||
throw new RuntimeException("批量移除指导老师角色失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<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-tab-pane label="已报名" name="registered">
|
||||
<RegisteredMembersTab
|
||||
:activity-id="activityId"
|
||||
:activity-name="activityName"
|
||||
ref="registeredTabRef"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 待审核标签页 -->
|
||||
<el-tab-pane label="待审核" name="pending">
|
||||
<PendingMembersTab
|
||||
:activity-id="activityId"
|
||||
:activity-name="activityName"
|
||||
ref="pendingTabRef"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, nextTick } from 'vue';
|
||||
import RegisteredMembersTab from './RegisteredMembersTab.vue';
|
||||
import PendingMembersTab from './PendingMembersTab.vue';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
activityId: string | number;
|
||||
activityName: string;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const activeTab = ref('registered');
|
||||
const registeredTabRef = ref();
|
||||
const pendingTabRef = ref();
|
||||
|
||||
// 处理弹窗显示状态变化
|
||||
const handleVisibleChange = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 监听弹窗显示状态,当弹窗打开时刷新数据
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
// 弹窗打开时刷新当前标签页数据
|
||||
nextTick(() => {
|
||||
if (activeTab.value === 'registered' && registeredTabRef.value) {
|
||||
registeredTabRef.value.getList();
|
||||
} else if (activeTab.value === 'pending' && pendingTabRef.value) {
|
||||
pendingTabRef.value.getList();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 标签页切换时刷新数据
|
||||
const handleTabChange = (tabName: string) => {
|
||||
nextTick(() => {
|
||||
if (tabName === 'registered' && registeredTabRef.value) {
|
||||
registeredTabRef.value.getList();
|
||||
} else if (tabName === 'pending' && pendingTabRef.value) {
|
||||
pendingTabRef.value.getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
emit('update:visible', false);
|
||||
};
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
refreshData: () => {
|
||||
if (activeTab.value === 'registered' && registeredTabRef.value) {
|
||||
registeredTabRef.value.getList();
|
||||
} else if (activeTab.value === 'pending' && pendingTabRef.value) {
|
||||
pendingTabRef.value.getList();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__content) {
|
||||
padding: 20px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,840 @@
|
||||
<template>
|
||||
<div class="activity-registration-view">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="always" style="padding: 8px 16px;">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0;">
|
||||
<el-row :gutter="16" align="middle">
|
||||
<el-col :span="5">
|
||||
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0;">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0;">
|
||||
<el-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>
|
||||
<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">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5" v-if="!isViewMode">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['stgl:activityRegister:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5" v-if="!isViewMode">
|
||||
<el-button icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['stgl:activityRegister:edit']">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5" v-if="!isViewMode">
|
||||
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['stgl:activityRegister:remove']">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5" v-if="!isViewMode">
|
||||
<el-button type="success" icon="Download" @click="handleExport" v-hasPermi="['stgl:activityRegister:export']">导出</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5" v-if="!isViewMode">
|
||||
<el-button type="warning" icon="Upload" @click="handleImport" v-hasPermi="['stgl:activityRegister:import']">导入</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="memberList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" v-if="!isViewMode" />
|
||||
<el-table-column label="序号" type="index" width="55" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="approvalStatus" width="80">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="stgl_approval_status" :value="scope.row.approvalStatus" v-if="scope.row.approvalStatus !== undefined" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="学号" align="center" prop="studentNo" width="120" />
|
||||
<el-table-column label="姓名" align="center" prop="studentName" width="80" />
|
||||
<el-table-column label="性别" align="center" prop="gender" width="60">
|
||||
<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="联系方式" align="center" prop="phone" width="120" />
|
||||
<el-table-column label="院系" align="center" prop="department" width="120" />
|
||||
<el-table-column label="专业" align="center" prop="major" width="180" />
|
||||
<el-table-column label="班级" align="center" prop="className" width="120" />
|
||||
<el-table-column label="报名类型" align="center" prop="registrationType" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="stgl_registration_type" :value="scope.row.registrationType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="团队名称" align="center" prop="teamName" width="150" show-overflow-tooltip />
|
||||
<el-table-column label="报名时间" align="center" prop="registrationTime" width="100">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.registrationTime, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="签到时间" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.latestSignInTime"
|
||||
link
|
||||
type="primary"
|
||||
@click="showSignRecords(scope.row)"
|
||||
class="time-button"
|
||||
>
|
||||
{{ formatSignTime(scope.row.latestSignInTime) }}
|
||||
</el-button>
|
||||
<span v-else class="text-gray-400">未签到</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="签退时间" align="center" width="120">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.latestSignOutTime"
|
||||
link
|
||||
type="primary"
|
||||
@click="showSignRecords(scope.row)"
|
||||
class="time-button"
|
||||
>
|
||||
{{ formatSignTime(scope.row.latestSignOutTime) }}
|
||||
</el-button>
|
||||
<span v-else class="text-gray-400">未签退</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="成绩" align="center" prop="score" width="80">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.score || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<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" v-if="!isViewMode">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['stgl:activityRegister:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top" v-if="!isViewMode">
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['stgl:activityRegister:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
|
||||
<!-- 添加或修改活动报名对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body v-if="!isViewMode">
|
||||
<el-form ref="memberFormRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="姓名" prop="selectedStudent">
|
||||
<StudentSelector v-model="form.selectedStudent" placeholder="请选择学生" :showStudentNo="false" @change="handleStudentChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input v-model="form.studentNo" placeholder="学号" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="性别" prop="gender">
|
||||
<el-select v-model="form.gender" placeholder="性别" disabled>
|
||||
<el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="院系" prop="department">
|
||||
<el-input v-model="form.department" placeholder="院系" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业" prop="major">
|
||||
<el-input v-model="form.major" placeholder="专业" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班级" prop="className">
|
||||
<el-input v-model="form.className" placeholder="班级" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系方式" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="联系方式" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="报名类型" prop="registrationType">
|
||||
<el-select v-model="form.registrationType" placeholder="请选择报名类型" style="width: 100%">
|
||||
<el-option v-for="dict in stgl_registration_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<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>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="申请理由" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入申请理由" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看成员详情对话框 -->
|
||||
<el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="800px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="姓名">{{ viewForm.studentName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学号">{{ viewForm.studentNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
<dict-tag :options="sys_user_sex" :value="viewForm.gender" v-if="viewForm.gender" />
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="联系方式">{{ viewForm.phone || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="院系">{{ viewForm.department || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业">{{ viewForm.major || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="班级">{{ viewForm.className || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报名类型">
|
||||
<dict-tag :options="stgl_registration_type" :value="viewForm.registrationType" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="团队名称" v-if="viewForm.registrationType === '2' || viewForm.registrationType === '3'">
|
||||
{{ viewForm.teamName || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="报名时间">{{ parseTime(viewForm.registrationTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请理由" :span="2">
|
||||
<div class="view-description">{{ viewForm.remark || '无' }}</div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="viewDialog.visible = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 签到记录弹窗 -->
|
||||
<el-dialog v-model="signRecordsDialog.visible" :title="signRecordsDialog.title" width="800px" append-to-body>
|
||||
<div v-loading="signRecordsLoading">
|
||||
<el-table :data="signRecords" border stripe>
|
||||
<el-table-column label="序号" type="index" width="60" align="center" />
|
||||
<el-table-column label="签到时间" align="center" width="160">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.signInTime">{{ formatSignTime(scope.row.signInTime) }}</span>
|
||||
<span v-else class="text-gray-400">未签到</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="签到地点" align="center" prop="signInLocation" width="150" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.signInLocation || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="签退时间" align="center" width="160">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.signOutTime">{{ formatSignTime(scope.row.signOutTime) }}</span>
|
||||
<span v-else class="text-gray-400">未签退</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="签退地点" align="center" prop="signOutLocation" width="150" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.signOutLocation || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.overallStatus === 0" type="info">未签到</el-tag>
|
||||
<el-tag v-else-if="scope.row.overallStatus === 1" type="success">已签到</el-tag>
|
||||
<el-tag v-else-if="scope.row.overallStatus === 2" type="warning">已签退</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="signRecordsDialog.visible = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 导入对话框 -->
|
||||
<el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body v-if="!isViewMode">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:limit="1"
|
||||
accept=".xlsx, .xls"
|
||||
:headers="upload.headers"
|
||||
:action="upload.url"
|
||||
:disabled="upload.isUploading"
|
||||
:on-progress="handleFileUploadProgress"
|
||||
:on-success="handleFileSuccess"
|
||||
:auto-upload="false"
|
||||
drag
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip text-center">
|
||||
<div class="el-upload__tip">
|
||||
<el-checkbox v-model="upload.updateSupport" />
|
||||
是否更新已经存在的报名数据
|
||||
</div>
|
||||
<span>仅允许导入xls、xlsx格式文件。</span>
|
||||
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
||||
<el-button @click="upload.open = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getCurrentInstance, onMounted, reactive, ref, toRefs } from 'vue';
|
||||
import {
|
||||
listActivityRegister,
|
||||
getActivityRegister,
|
||||
delActivityRegister,
|
||||
addActivityRegister,
|
||||
updateActivityRegister,
|
||||
exportActivityRegister
|
||||
} from '@/api/stgl/activityregister';
|
||||
import { ActivityRegisterVO, ActivityRegisterForm, ActivityRegisterQuery } from '@/api/stgl/activityregister/types';
|
||||
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 type { UploadInstance } from 'element-plus';
|
||||
import request from '@/utils/request';
|
||||
|
||||
interface Props {
|
||||
activityId: string | number;
|
||||
isViewMode?: boolean;
|
||||
}
|
||||
|
||||
// 扩展ActivityRegisterForm以包含选择的学生对象
|
||||
interface ExtendedActivityRegisterForm extends ActivityRegisterForm {
|
||||
selectedStudent?: StudentVO | null;
|
||||
}
|
||||
|
||||
// 扩展ActivityRegisterVO以包含签到信息
|
||||
interface ExtendedActivityRegisterVO extends ActivityRegisterVO {
|
||||
latestSignInTime?: string;
|
||||
latestSignOutTime?: string;
|
||||
}
|
||||
|
||||
// 签到记录类型
|
||||
interface SignRecord {
|
||||
id: string | number;
|
||||
signInTime?: string;
|
||||
signOutTime?: string;
|
||||
signInLocation?: string;
|
||||
signOutLocation?: string;
|
||||
overallStatus: number;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { sys_user_sex, stgl_registration_type, stgl_approval_status } = toRefs<any>(
|
||||
proxy?.useDict('sys_user_sex', 'stgl_registration_type', 'stgl_approval_status')
|
||||
);
|
||||
|
||||
const memberList = ref<ExtendedActivityRegisterVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const memberFormRef = ref<ElFormInstance>();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const viewDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const signRecordsDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const viewForm = reactive<ActivityRegisterVO>({
|
||||
id: undefined,
|
||||
activityId: undefined,
|
||||
studentId: undefined,
|
||||
studentName: '',
|
||||
studentNo: '',
|
||||
gender: '',
|
||||
department: '',
|
||||
major: '',
|
||||
className: '',
|
||||
phone: '',
|
||||
registrationType: undefined,
|
||||
teamName: '',
|
||||
approvalStatus: undefined,
|
||||
registrationTime: '',
|
||||
approvalTime: '',
|
||||
remark: '',
|
||||
score: '',
|
||||
scoreTime: ''
|
||||
});
|
||||
|
||||
const initFormData: ExtendedActivityRegisterForm = {
|
||||
id: undefined,
|
||||
activityId: props.activityId,
|
||||
studentId: undefined,
|
||||
selectedStudent: null,
|
||||
studentName: '',
|
||||
studentNo: '',
|
||||
gender: '',
|
||||
department: '',
|
||||
major: '',
|
||||
className: '',
|
||||
phone: '',
|
||||
registrationType: '1',
|
||||
teamName: '',
|
||||
approvalStatus: 1,
|
||||
registrationTime: undefined,
|
||||
approvalTime: undefined,
|
||||
remark: '',
|
||||
score: '',
|
||||
scoreTime: undefined
|
||||
};
|
||||
|
||||
const data = reactive<PageData<ExtendedActivityRegisterForm, ActivityRegisterQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
activityId: props.activityId,
|
||||
approvalStatus: undefined,
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
phone: undefined
|
||||
},
|
||||
rules: {
|
||||
selectedStudent: [{ required: true, message: '请选择学生', trigger: 'change' }],
|
||||
registrationType: [{ required: true, message: '请选择报名类型', trigger: 'change' }],
|
||||
teamName: [{ required: true, message: '请输入团队名称', trigger: 'blur' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
// 签到记录相关
|
||||
const signRecords = ref<SignRecord[]>([]);
|
||||
const signRecordsLoading = ref(false);
|
||||
|
||||
// 上传相关
|
||||
const uploadRef = ref<UploadInstance>();
|
||||
const upload = reactive<{
|
||||
open: boolean;
|
||||
title: string;
|
||||
isUploading: boolean;
|
||||
updateSupport: boolean;
|
||||
url: string;
|
||||
headers: { Authorization: string };
|
||||
}>({
|
||||
open: false,
|
||||
title: '',
|
||||
isUploading: false,
|
||||
updateSupport: false,
|
||||
url: import.meta.env.VITE_APP_BASE_API + '/stgl/activity/register/importData',
|
||||
headers: globalHeaders()
|
||||
});
|
||||
|
||||
/** 查询成员列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
// 构建查询参数,查询已审核通过的报名记录
|
||||
const queryData = { ...queryParams.value };
|
||||
|
||||
// 如果状态字段为空或未选择,则查询已通过(approvalStatus=1)的报名记录
|
||||
if (!queryData.approvalStatus) {
|
||||
queryData.approvalStatus = 1;
|
||||
}
|
||||
|
||||
console.log('ActivityRegistrationView: getList 开始查询,参数:', queryData);
|
||||
|
||||
const res = await listActivityRegister(queryData);
|
||||
console.log('ActivityRegistrationView: 查询结果:', res);
|
||||
|
||||
// 为每个成员添加签到信息
|
||||
const membersWithSignInfo = await Promise.all(
|
||||
res.rows.map(async (member) => {
|
||||
try {
|
||||
// 查询该用户该活动的最新签到记录
|
||||
const signRes = await request({
|
||||
url: '/stgl/activityAttendance/getAllByActivityAndUser',
|
||||
method: 'get',
|
||||
params: {
|
||||
activityId: props.activityId,
|
||||
studentNo: member.studentNo
|
||||
}
|
||||
});
|
||||
|
||||
if (signRes.code === 200 && signRes.data && signRes.data.length > 0) {
|
||||
// 获取最新的签到和签退时间
|
||||
const latestSignIn = signRes.data
|
||||
.filter((record: any) => record.signInTime)
|
||||
.sort((a: any, b: any) => new Date(b.signInTime).getTime() - new Date(a.signInTime).getTime())[0];
|
||||
|
||||
const latestSignOut = signRes.data
|
||||
.filter((record: any) => record.signOutTime)
|
||||
.sort((a: any, b: any) => new Date(b.signOutTime).getTime() - new Date(a.signOutTime).getTime())[0];
|
||||
|
||||
return {
|
||||
...member,
|
||||
latestSignInTime: latestSignIn?.signInTime,
|
||||
latestSignOutTime: latestSignOut?.signOutTime
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('获取签到信息失败:', error);
|
||||
}
|
||||
|
||||
return {
|
||||
...member,
|
||||
latestSignInTime: undefined,
|
||||
latestSignOutTime: undefined
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
memberList.value = membersWithSignInfo;
|
||||
total.value = res.total;
|
||||
} catch (error) {
|
||||
console.error('ActivityRegistrationView: 查询成员列表失败:', error);
|
||||
proxy?.$modal.msgError('查询成员列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 显示签到记录 */
|
||||
const showSignRecords = async (member: ExtendedActivityRegisterVO) => {
|
||||
signRecordsDialog.visible = true;
|
||||
signRecordsDialog.title = `${member.studentName}(${member.studentNo})的签到记录`;
|
||||
signRecordsLoading.value = true;
|
||||
|
||||
try {
|
||||
const res = await request({
|
||||
url: '/stgl/activityAttendance/getAllByActivityAndUser',
|
||||
method: 'get',
|
||||
params: {
|
||||
activityId: props.activityId,
|
||||
studentNo: member.studentNo
|
||||
}
|
||||
});
|
||||
|
||||
if (res.code === 200) {
|
||||
signRecords.value = res.data || [];
|
||||
} else {
|
||||
signRecords.value = [];
|
||||
proxy?.$modal.msgError(res.msg || '获取签到记录失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取签到记录失败:', error);
|
||||
signRecords.value = [];
|
||||
proxy?.$modal.msgError('获取签到记录失败');
|
||||
} finally {
|
||||
signRecordsLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 格式化签到时间 */
|
||||
const formatSignTime = (timeStr: string): string => {
|
||||
if (!timeStr) return '';
|
||||
const date = new Date(timeStr);
|
||||
return date.toLocaleString('zh-CN', {
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData, activityId: props.activityId };
|
||||
memberFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
queryParams.value = {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
activityId: props.activityId,
|
||||
approvalStatus: 1,
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
phone: undefined
|
||||
};
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: ExtendedActivityRegisterVO[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加活动报名';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: ExtendedActivityRegisterVO) => {
|
||||
reset();
|
||||
const _id = row?.id || ids.value[0];
|
||||
const res = await getActivityRegister(_id);
|
||||
Object.assign(form.value, res.data);
|
||||
|
||||
// 设置学生选择器数据
|
||||
if (res.data.studentName) {
|
||||
form.value.selectedStudent = {
|
||||
id: res.data.studentId || 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 || ''
|
||||
} as StudentVO;
|
||||
}
|
||||
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改活动报名';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
memberFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
|
||||
// 创建提交数据,排除selectedStudent字段
|
||||
const { selectedStudent, ...submitData } = form.value;
|
||||
|
||||
// 确保包含必要字段
|
||||
submitData.activityId = props.activityId;
|
||||
submitData.approvalStatus = 1; // 新增的报名默认为已通过状态
|
||||
|
||||
try {
|
||||
if (form.value.id) {
|
||||
await updateActivityRegister(submitData as ActivityRegisterForm);
|
||||
} else {
|
||||
await addActivityRegister(submitData as ActivityRegisterForm);
|
||||
}
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error);
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: ExtendedActivityRegisterVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除选中的活动报名记录?');
|
||||
|
||||
try {
|
||||
if (Array.isArray(_ids)) {
|
||||
await delActivityRegister(_ids.join(','));
|
||||
} else {
|
||||
await delActivityRegister(_ids);
|
||||
}
|
||||
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/** 查看按钮操作 */
|
||||
const handleView = async (row: ExtendedActivityRegisterVO) => {
|
||||
const res = await getActivityRegister(row.id);
|
||||
Object.assign(viewForm, res.data);
|
||||
viewDialog.visible = true;
|
||||
viewDialog.title = '查看活动报名详情';
|
||||
};
|
||||
|
||||
/** 学生选择变化处理 */
|
||||
const handleStudentChange = (student: StudentVO | null) => {
|
||||
if (student) {
|
||||
form.value.studentId = student.id;
|
||||
form.value.studentNo = student.studentNo;
|
||||
form.value.studentName = student.name;
|
||||
form.value.gender = student.gender;
|
||||
form.value.department = student.collegeName;
|
||||
form.value.major = student.majorName;
|
||||
form.value.className = student.className;
|
||||
form.value.phone = student.phone;
|
||||
} else {
|
||||
form.value.studentId = undefined;
|
||||
form.value.studentNo = '';
|
||||
form.value.studentName = '';
|
||||
form.value.gender = '';
|
||||
form.value.department = '';
|
||||
form.value.major = '';
|
||||
form.value.className = '';
|
||||
form.value.phone = '';
|
||||
}
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'stgl/activity/register/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`activity_register_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
/** 导入模板下载 */
|
||||
const importTemplate = () => {
|
||||
proxy?.download('stgl/activity/register/importTemplate', {}, `活动报名导入模板_${new Date().getTime()}.xlsx`);
|
||||
};
|
||||
|
||||
/** 导入按钮操作 */
|
||||
const handleImport = () => {
|
||||
upload.title = '活动报名导入';
|
||||
upload.open = true;
|
||||
};
|
||||
|
||||
/** 提交上传文件 */
|
||||
const submitFileForm = () => {
|
||||
uploadRef.value?.submit();
|
||||
};
|
||||
|
||||
/** 文件上传中处理 */
|
||||
const handleFileUploadProgress = () => {
|
||||
upload.isUploading = true;
|
||||
};
|
||||
|
||||
/** 文件上传成功处理 */
|
||||
const handleFileSuccess = (response: any) => {
|
||||
upload.open = false;
|
||||
upload.isUploading = false;
|
||||
uploadRef.value?.clearFiles();
|
||||
proxy?.$modal.msgSuccess(response.msg);
|
||||
if (response.msg.includes('成功')) {
|
||||
getList();
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
getList
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.activity-registration-view {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.view-description {
|
||||
padding: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
min-height: 80px;
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.time-button {
|
||||
padding: 0;
|
||||
height: auto;
|
||||
font-size: 14px;
|
||||
color: #409eff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.time-button:hover {
|
||||
color: #66b1ff;
|
||||
}
|
||||
|
||||
.text-gray-400 {
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,469 @@
|
||||
<template>
|
||||
<div class="pending-members-tab">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="always" style="padding: 8px 16px;">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0;">
|
||||
<el-row :gutter="16" align="middle">
|
||||
<el-col :span="5">
|
||||
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0;">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0;">
|
||||
<el-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>
|
||||
<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">
|
||||
<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-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-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="memberList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="序号" type="index" width="55" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="approvalStatus" width="80">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="stgl_approval_status" :value="scope.row.approvalStatus" v-if="scope.row.approvalStatus !== undefined" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="学号" align="center" prop="studentNo" width="120" />
|
||||
<el-table-column label="姓名" align="center" prop="studentName" width="80" />
|
||||
<el-table-column label="性别" align="center" prop="gender" width="60">
|
||||
<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="联系方式" align="center" prop="phone" width="120" />
|
||||
<el-table-column label="院系" align="center" prop="department" width="120" />
|
||||
<el-table-column label="专业" align="center" prop="major" width="180" />
|
||||
<el-table-column label="班级" align="center" prop="className" width="120" />
|
||||
<el-table-column label="报名类型" align="center" prop="registrationType" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="stgl_registration_type" :value="scope.row.registrationType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="团队名称" align="center" prop="teamName" width="150" show-overflow-tooltip />
|
||||
<el-table-column label="报名时间" align="center" prop="registrationTime" width="100">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.registrationTime, '{y}-{m}-{d}') }}</span>
|
||||
</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">
|
||||
<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-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
|
||||
<!-- 查看成员详情对话框 -->
|
||||
<el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="800px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="学号">{{ viewForm.studentNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ viewForm.studentName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
<dict-tag :options="sys_user_sex" :value="viewForm.gender" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="院系">{{ viewForm.department }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业">{{ viewForm.major }}</el-descriptions-item>
|
||||
<el-descriptions-item label="班级">{{ viewForm.className }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系方式">{{ viewForm.phone }}</el-descriptions-item>
|
||||
<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>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelView">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审核操作对话框 -->
|
||||
<el-dialog :title="approvalDialog.title" v-model="approvalDialog.visible" width="500px" append-to-body>
|
||||
<el-form ref="approvalFormRef" :model="approvalForm" :rules="approvalRules" label-width="80px">
|
||||
<el-form-item label="审核结果" prop="approvalStatus">
|
||||
<el-radio-group v-model="approvalForm.approvalStatus">
|
||||
<el-radio :label="1">通过</el-radio>
|
||||
<el-radio :label="2">拒绝</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="审核意见" prop="approvalRemark">
|
||||
<el-input
|
||||
v-model="approvalForm.approvalRemark"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
:placeholder="approvalForm.approvalStatus === 1 ? '请输入通过意见(可选)' : '请输入拒绝原因(必填)'"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitApproval">确 定</el-button>
|
||||
<el-button @click="cancelApproval">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getCurrentInstance, onMounted, reactive, ref, toRefs } from 'vue';
|
||||
import {
|
||||
listActivityRegister,
|
||||
getActivityRegister,
|
||||
delActivityRegister,
|
||||
updateActivityRegister,
|
||||
exportActivityRegister
|
||||
} from '@/api/stgl/activityregister';
|
||||
import { StglActivityRegistrationVo, StglActivityRegistrationForm, StglActivityRegistrationQuery } from '@/api/stgl/activityregister/types';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
import { ArrowDown } from '@element-plus/icons-vue';
|
||||
|
||||
interface Props {
|
||||
activityId: string | number;
|
||||
activityName: string;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { sys_user_sex, stgl_registration_type, stgl_approval_status } = toRefs<any>(
|
||||
proxy?.useDict('sys_user_sex', 'stgl_registration_type', 'stgl_approval_status')
|
||||
);
|
||||
|
||||
const memberList = ref<StglActivityRegistrationVo[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const approvalFormRef = ref<ElFormInstance>();
|
||||
|
||||
const viewDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const approvalDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const viewForm = reactive<StglActivityRegistrationVo>({
|
||||
id: undefined,
|
||||
activityId: undefined,
|
||||
studentId: undefined,
|
||||
studentName: '',
|
||||
studentNo: '',
|
||||
gender: '',
|
||||
department: '',
|
||||
major: '',
|
||||
className: '',
|
||||
phone: '',
|
||||
registrationType: undefined,
|
||||
teamName: '',
|
||||
approvalStatus: undefined,
|
||||
registrationTime: '',
|
||||
approvalTime: '',
|
||||
remark: '',
|
||||
score: '',
|
||||
scoreTime: ''
|
||||
});
|
||||
|
||||
const approvalForm = reactive({
|
||||
ids: [] as Array<string | number>,
|
||||
approvalStatus: 1,
|
||||
approvalRemark: ''
|
||||
});
|
||||
|
||||
const approvalRules = reactive({
|
||||
approvalStatus: [{ required: true, message: '请选择审核结果', trigger: 'change' }],
|
||||
approvalRemark: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入拒绝原因',
|
||||
trigger: 'blur',
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
if (approvalForm.approvalStatus === 2 && !value) {
|
||||
callback(new Error('拒绝时必须填写拒绝原因'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const data = reactive<PageData<StglActivityRegistrationForm, StglActivityRegistrationQuery>>({
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
activityId: props.activityId,
|
||||
approvalStatus: 0, // 待审核状态
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
phone: undefined
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
const res = await listActivityRegister(queryData);
|
||||
console.log('PendingMembersTab: 查询结果:', res);
|
||||
|
||||
memberList.value = res.rows;
|
||||
total.value = res.total;
|
||||
} catch (error) {
|
||||
console.error('PendingMembersTab: 查询成员列表失败:', error);
|
||||
proxy?.$modal.msgError('查询成员列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
queryParams.value = {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
activityId: props.activityId,
|
||||
approvalStatus: 0,
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
phone: undefined
|
||||
};
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: ActivityRegisterVO[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 批量通过操作 */
|
||||
const handleApprove = (row?: StglActivityRegistrationVo) => {
|
||||
const _ids = row?.id ? [row.id] : ids.value;
|
||||
if (_ids.length === 0) {
|
||||
proxy?.$modal.msgError('请选择要审核的记录');
|
||||
return;
|
||||
}
|
||||
|
||||
approvalForm.ids = _ids;
|
||||
approvalForm.approvalStatus = 1;
|
||||
approvalForm.approvalRemark = '';
|
||||
approvalDialog.visible = true;
|
||||
approvalDialog.title = '批量通过审核';
|
||||
};
|
||||
|
||||
/** 批量拒绝操作 */
|
||||
const handleReject = (row?: StglActivityRegistrationVo) => {
|
||||
const _ids = row?.id ? [row.id] : ids.value;
|
||||
if (_ids.length === 0) {
|
||||
proxy?.$modal.msgError('请选择要审核的记录');
|
||||
return;
|
||||
}
|
||||
|
||||
approvalForm.ids = _ids;
|
||||
approvalForm.approvalStatus = 2;
|
||||
approvalForm.approvalRemark = '';
|
||||
approvalDialog.visible = true;
|
||||
approvalDialog.title = '批量拒绝审核';
|
||||
};
|
||||
|
||||
/** 提交审核 */
|
||||
const submitApproval = () => {
|
||||
approvalFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
|
||||
try {
|
||||
// 批量更新审核状态
|
||||
for (const id of approvalForm.ids) {
|
||||
await updateActivityRegister({
|
||||
id,
|
||||
approvalStatus: approvalForm.approvalStatus,
|
||||
approvalRemark: approvalForm.approvalRemark,
|
||||
approvalTime: new Date()
|
||||
} as StglActivityRegistrationForm);
|
||||
}
|
||||
|
||||
const actionText = approvalForm.approvalStatus === 1 ? '通过' : '拒绝';
|
||||
proxy?.$modal.msgSuccess(`批量${actionText}成功`);
|
||||
approvalDialog.visible = false;
|
||||
await getList();
|
||||
} catch (error) {
|
||||
console.error('审核操作失败:', error);
|
||||
proxy?.$modal.msgError('审核操作失败');
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 取消审核对话框 */
|
||||
const cancelApproval = () => {
|
||||
approvalDialog.visible = false;
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: StglActivityRegistrationVo) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除选中的活动报名记录?');
|
||||
|
||||
try {
|
||||
if (Array.isArray(_ids)) {
|
||||
await delActivityRegister(_ids.join(','));
|
||||
} else {
|
||||
await delActivityRegister(_ids);
|
||||
}
|
||||
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/** 查看按钮操作 */
|
||||
const handleView = async (row: StglActivityRegistrationVo) => {
|
||||
const res = await getActivityRegister(row.id);
|
||||
Object.assign(viewForm, res.data);
|
||||
viewDialog.visible = true;
|
||||
viewDialog.title = '查看活动报名详情';
|
||||
};
|
||||
|
||||
/** 取消查看对话框 */
|
||||
const cancelView = () => {
|
||||
viewDialog.visible = false;
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'stgl/activity/register/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`pending_activity_register_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
getList
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.view-description {
|
||||
padding: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
min-height: 80px;
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,634 @@
|
||||
<template>
|
||||
<div class="registered-members-tab">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="always" style="padding: 8px 16px;">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0;">
|
||||
<el-row :gutter="16" align="middle">
|
||||
<el-col :span="5">
|
||||
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0;">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0;">
|
||||
<el-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>
|
||||
<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">
|
||||
<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:activityRegister:add']">新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['stgl:activityRegister:edit']">修改</el-button>
|
||||
</el-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>
|
||||
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="memberList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="序号" type="index" width="55" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="approvalStatus" width="80">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="stgl_approval_status" :value="scope.row.approvalStatus" v-if="scope.row.approvalStatus !== undefined" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="学号" align="center" prop="studentNo" width="120" />
|
||||
<el-table-column label="姓名" align="center" prop="studentName" width="80" />
|
||||
<el-table-column label="性别" align="center" prop="gender" width="60">
|
||||
<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="联系方式" align="center" prop="phone" width="120" />
|
||||
<el-table-column label="院系" align="center" prop="department" width="120" />
|
||||
<el-table-column label="专业" align="center" prop="major" width="180" />
|
||||
<el-table-column label="班级" align="center" prop="className" width="120" />
|
||||
<el-table-column label="报名类型" align="center" prop="registrationType" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="stgl_registration_type" :value="scope.row.registrationType" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="团队名称" align="center" prop="teamName" width="150" show-overflow-tooltip />
|
||||
<el-table-column label="报名时间" align="center" prop="registrationTime" width="100">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.registrationTime, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="成绩" align="center" prop="score" width="80">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.score || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<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="primary" icon="Edit" @click="handleUpdate(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-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
|
||||
<!-- 添加或修改活动报名对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body>
|
||||
<el-form ref="memberFormRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="姓名" prop="selectedStudent">
|
||||
<StudentSelector v-model="form.selectedStudent" placeholder="请选择学生" :showStudentNo="false" @change="handleStudentChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input v-model="form.studentNo" placeholder="学号" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="性别" prop="gender">
|
||||
<el-select v-model="form.gender" placeholder="性别" disabled>
|
||||
<el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="院系" prop="department">
|
||||
<el-input v-model="form.department" placeholder="院系" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业" prop="major">
|
||||
<el-input v-model="form.major" placeholder="专业" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班级" prop="className">
|
||||
<el-input v-model="form.className" placeholder="班级" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系方式" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="联系方式" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="报名类型" prop="registrationType">
|
||||
<el-select v-model="form.registrationType" placeholder="请选择报名类型" style="width: 100%">
|
||||
<el-option v-for="dict in stgl_registration_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<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>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="申请理由" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入申请理由" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看成员详情对话框 -->
|
||||
<el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="800px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="学号">{{ viewForm.studentNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ viewForm.studentName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
<dict-tag :options="sys_user_sex" :value="viewForm.gender" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="院系">{{ viewForm.department }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业">{{ viewForm.major }}</el-descriptions-item>
|
||||
<el-descriptions-item label="班级">{{ viewForm.className }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系方式">{{ viewForm.phone }}</el-descriptions-item>
|
||||
<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="报名时间">{{ parseTime(viewForm.registrationTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="成绩">{{ viewForm.score || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelView">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 导入对话框 -->
|
||||
<el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:limit="1"
|
||||
accept=".xlsx, .xls"
|
||||
:headers="upload.headers"
|
||||
:action="upload.url"
|
||||
:disabled="upload.isUploading"
|
||||
:on-progress="handleFileUploadProgress"
|
||||
:on-success="handleFileSuccess"
|
||||
:auto-upload="false"
|
||||
drag
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip text-center">
|
||||
<div class="el-upload__tip">
|
||||
<el-checkbox v-model="upload.updateSupport" />
|
||||
是否更新已经存在的报名数据
|
||||
</div>
|
||||
<span>仅允许导入xls、xlsx格式文件。</span>
|
||||
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
||||
<el-button @click="upload.open = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getCurrentInstance, onMounted, reactive, ref, toRefs } from 'vue';
|
||||
import {
|
||||
listActivityRegister,
|
||||
getActivityRegister,
|
||||
delActivityRegister,
|
||||
addActivityRegister,
|
||||
updateActivityRegister,
|
||||
exportActivityRegister
|
||||
} from '@/api/stgl/activityregister';
|
||||
import { ActivityRegisterVO, ActivityRegisterForm, ActivityRegisterQuery } from '@/api/stgl/activityregister/types';
|
||||
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 type { UploadInstance } from 'element-plus';
|
||||
|
||||
interface Props {
|
||||
activityId: string | number;
|
||||
activityName: string;
|
||||
}
|
||||
|
||||
// 扩展ActivityRegisterForm以包含选择的学生对象
|
||||
interface ExtendedActivityRegisterForm extends ActivityRegisterForm {
|
||||
selectedStudent?: StudentVO | null;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { sys_user_sex, stgl_registration_type, stgl_approval_status } = toRefs<any>(
|
||||
proxy?.useDict('sys_user_sex', 'stgl_registration_type', 'stgl_approval_status')
|
||||
);
|
||||
|
||||
const memberList = ref<ActivityRegisterVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const memberFormRef = ref<ElFormInstance>();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const viewDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const viewForm = reactive<ActivityRegisterVO>({
|
||||
id: undefined,
|
||||
activityId: undefined,
|
||||
studentId: undefined,
|
||||
studentName: '',
|
||||
studentNo: '',
|
||||
gender: '',
|
||||
department: '',
|
||||
major: '',
|
||||
className: '',
|
||||
phone: '',
|
||||
registrationType: undefined,
|
||||
teamName: '',
|
||||
approvalStatus: undefined,
|
||||
registrationTime: '',
|
||||
approvalTime: '',
|
||||
remark: '',
|
||||
score: '',
|
||||
scoreTime: ''
|
||||
});
|
||||
|
||||
const initFormData: ExtendedActivityRegisterForm = {
|
||||
id: undefined,
|
||||
activityId: props.activityId,
|
||||
studentId: undefined,
|
||||
selectedStudent: null,
|
||||
studentName: '',
|
||||
studentNo: '',
|
||||
gender: '',
|
||||
department: '',
|
||||
major: '',
|
||||
className: '',
|
||||
phone: '',
|
||||
registrationType: '1',
|
||||
teamName: '',
|
||||
approvalStatus: 1,
|
||||
registrationTime: undefined,
|
||||
approvalTime: undefined,
|
||||
remark: '',
|
||||
score: '',
|
||||
scoreTime: undefined
|
||||
};
|
||||
|
||||
const data = reactive<PageData<ExtendedActivityRegisterForm, ActivityRegisterQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
activityId: props.activityId,
|
||||
approvalStatus: undefined,
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
phone: undefined
|
||||
},
|
||||
rules: {
|
||||
selectedStudent: [{ required: true, message: '请选择学生', trigger: 'change' }],
|
||||
registrationType: [{ required: true, message: '请选择报名类型', trigger: 'change' }],
|
||||
teamName: [{ required: true, message: '请输入团队名称', trigger: 'blur' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
// 上传相关
|
||||
const uploadRef = ref<UploadInstance>();
|
||||
const upload = reactive<{
|
||||
open: boolean;
|
||||
title: string;
|
||||
isUploading: boolean;
|
||||
updateSupport: boolean;
|
||||
url: string;
|
||||
headers: { Authorization: string };
|
||||
}>({
|
||||
open: false,
|
||||
title: '',
|
||||
isUploading: false,
|
||||
updateSupport: false,
|
||||
url: import.meta.env.VITE_APP_BASE_API + '/stgl/activity/register/importData',
|
||||
headers: globalHeaders()
|
||||
});
|
||||
|
||||
/** 查询成员列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
// 构建查询参数,查询已审核通过的报名记录
|
||||
const queryData = { ...queryParams.value };
|
||||
|
||||
// 如果状态字段为空或未选择,则查询已通过(approvalStatus=1)的报名记录
|
||||
if (!queryData.approvalStatus) {
|
||||
queryData.approvalStatus = 1;
|
||||
}
|
||||
|
||||
console.log('RegisteredMembersTab: getList 开始查询,参数:', queryData);
|
||||
|
||||
const res = await listActivityRegister(queryData);
|
||||
console.log('RegisteredMembersTab: 查询结果:', res);
|
||||
|
||||
memberList.value = res.rows;
|
||||
total.value = res.total;
|
||||
} catch (error) {
|
||||
console.error('RegisteredMembersTab: 查询成员列表失败:', error);
|
||||
proxy?.$modal.msgError('查询成员列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData, activityId: props.activityId };
|
||||
memberFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
queryParams.value = {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
activityId: props.activityId,
|
||||
approvalStatus: 1,
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
phone: undefined
|
||||
};
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: ActivityRegisterVO[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
reset();
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加活动报名';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: ActivityRegisterVO) => {
|
||||
reset();
|
||||
const _id = row?.id || ids.value[0];
|
||||
const res = await getActivityRegister(_id);
|
||||
Object.assign(form.value, res.data);
|
||||
|
||||
// 设置学生选择器数据
|
||||
if (res.data.studentName) {
|
||||
form.value.selectedStudent = {
|
||||
id: res.data.studentId || 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 || ''
|
||||
} as StudentVO;
|
||||
}
|
||||
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改活动报名';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
memberFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
|
||||
// 创建提交数据,排除selectedStudent字段
|
||||
const { selectedStudent, ...submitData } = form.value;
|
||||
|
||||
// 确保包含必要字段
|
||||
submitData.activityId = props.activityId;
|
||||
submitData.approvalStatus = 1; // 新增的报名默认为已通过状态
|
||||
|
||||
try {
|
||||
if (form.value.id) {
|
||||
await updateActivityRegister(submitData as ActivityRegisterForm);
|
||||
} else {
|
||||
await addActivityRegister(submitData as ActivityRegisterForm);
|
||||
}
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
} catch (error) {
|
||||
console.error('提交失败:', error);
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: ActivityRegisterVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除选中的活动报名记录?');
|
||||
|
||||
try {
|
||||
if (Array.isArray(_ids)) {
|
||||
await delActivityRegister(_ids.join(','));
|
||||
} else {
|
||||
await delActivityRegister(_ids);
|
||||
}
|
||||
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
} catch (error) {
|
||||
console.error('删除失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
/** 查看按钮操作 */
|
||||
const handleView = async (row: ActivityRegisterVO) => {
|
||||
const res = await getActivityRegister(row.id);
|
||||
Object.assign(viewForm, res.data);
|
||||
viewDialog.visible = true;
|
||||
viewDialog.title = '查看活动报名详情';
|
||||
};
|
||||
|
||||
/** 取消查看对话框 */
|
||||
const cancelView = () => {
|
||||
viewDialog.visible = false;
|
||||
};
|
||||
|
||||
/** 学生选择变化处理 */
|
||||
const handleStudentChange = (student: StudentVO | null) => {
|
||||
if (student) {
|
||||
form.value.studentId = student.id;
|
||||
form.value.studentNo = student.studentNo;
|
||||
form.value.studentName = student.name;
|
||||
form.value.gender = student.gender;
|
||||
form.value.department = student.collegeName;
|
||||
form.value.major = student.majorName;
|
||||
form.value.className = student.className;
|
||||
form.value.phone = student.phone;
|
||||
} else {
|
||||
form.value.studentId = undefined;
|
||||
form.value.studentNo = '';
|
||||
form.value.studentName = '';
|
||||
form.value.gender = '';
|
||||
form.value.department = '';
|
||||
form.value.major = '';
|
||||
form.value.className = '';
|
||||
form.value.phone = '';
|
||||
}
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'stgl/activity/register/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`activity_register_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
/** 导入模板下载 */
|
||||
const importTemplate = () => {
|
||||
proxy?.download('stgl/activity/register/importTemplate', {}, `活动报名导入模板_${new Date().getTime()}.xlsx`);
|
||||
};
|
||||
|
||||
/** 导入按钮操作 */
|
||||
const handleImport = () => {
|
||||
upload.title = '活动报名导入';
|
||||
upload.open = true;
|
||||
};
|
||||
|
||||
/** 提交上传文件 */
|
||||
const submitFileForm = () => {
|
||||
uploadRef.value?.submit();
|
||||
};
|
||||
|
||||
/** 文件上传中处理 */
|
||||
const handleFileUploadProgress = () => {
|
||||
upload.isUploading = true;
|
||||
};
|
||||
|
||||
/** 文件上传成功处理 */
|
||||
const handleFileSuccess = (response: any) => {
|
||||
upload.open = false;
|
||||
upload.isUploading = false;
|
||||
uploadRef.value?.clearFiles();
|
||||
proxy?.$modal.msgSuccess(response.msg);
|
||||
if (response.msg.includes('成功')) {
|
||||
getList();
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
getList
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.view-description {
|
||||
padding: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
min-height: 80px;
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,835 @@
|
||||
<template>
|
||||
<div class="joined-members-tab">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="always" style="padding: 8px 16px">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0">
|
||||
<el-row :gutter="16" align="middle">
|
||||
<el-col :span="5">
|
||||
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0">
|
||||
<el-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.params.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>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-tooltip :content="getAddButtonTooltip" placement="top" :disabled="!isAddButtonDisabled">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" :disabled="isAddButtonDisabled" v-hasPermi="['stgl:clubmember:add']">
|
||||
新增
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['stgl:clubmember:edit']"
|
||||
>修改</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['stgl:clubmember: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="importTemplate">下载模板</el-dropdown-item>
|
||||
<el-dropdown-item icon="Top" @click="handleImport" v-hasPermi="['stgl:clubmember:import']">导入数据</el-dropdown-item>
|
||||
<el-dropdown-item icon="Download" @click="handleExport" v-hasPermi="['stgl:clubmember:export']">导出数据</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="memberList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="序号" type="index" width="55" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" width="80">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="stgl_member_status" :value="scope.row.status" v-if="scope.row.status" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="学号" align="center" prop="studentNo" width="120" />
|
||||
<el-table-column label="姓名" align="center" prop="studentName" width="80" />
|
||||
<el-table-column label="性别" align="center" prop="gender" width="60">
|
||||
<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="联系方式" align="center" prop="phone" width="120" />
|
||||
<el-table-column label="院系" align="center" prop="department" width="120" />
|
||||
<el-table-column label="专业" align="center" prop="major" width="180" />
|
||||
<el-table-column label="班级" align="center" prop="className" width="120" />
|
||||
<el-table-column label="成员角色" align="center" prop="roleType" width="100">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.roleType">{{ scope.row.roleType }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="加入时间" align="center" prop="joinTime" width="100">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.joinTime ? parseTime(scope.row.joinTime, '{y}-{m}-{d}') : '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="220" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="info"
|
||||
icon="View"
|
||||
size="default"
|
||||
style="font-size: 16px; margin-right: 8px; color: blue"
|
||||
@click="handleView(scope.row)"
|
||||
v-hasPermi="['stgl:clubmember:query']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="Edit"
|
||||
size="default"
|
||||
style="font-size: 16px; margin-right: 8px"
|
||||
@click="handleUpdate(scope.row)"
|
||||
v-hasPermi="['stgl:clubmember:edit']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
icon="Delete"
|
||||
size="default"
|
||||
style="font-size: 16px"
|
||||
@click="handleDelete(scope.row)"
|
||||
v-hasPermi="['stgl:clubmember:remove']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
|
||||
<!-- 添加或修改成员对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="600px" append-to-body>
|
||||
<el-form ref="memberFormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="姓名" prop="selectedStudent">
|
||||
<StudentSelector v-model="form.selectedStudent" placeholder="请选择学生" :showStudentNo="false" @change="handleStudentChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input v-model="form.studentNo" placeholder="学号" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="性别" prop="gender">
|
||||
<el-select v-model="form.gender" placeholder="性别" disabled>
|
||||
<el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="院系" prop="department">
|
||||
<el-input v-model="form.department" placeholder="院系" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业" prop="major">
|
||||
<el-input v-model="form.major" placeholder="专业" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班级" prop="className">
|
||||
<el-input v-model="form.className" placeholder="班级" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系方式" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="联系方式" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="form.email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="成员角色" prop="roleType">
|
||||
<el-input v-model="form.roleType" placeholder="请输入成员角色" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="加入时间" prop="joinTime">
|
||||
<el-date-picker clearable v-model="form.joinTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择加入时间"> </el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看成员详情对话框 -->
|
||||
<el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="800px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="学号">{{ viewForm.studentNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ viewForm.studentName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
<dict-tag :options="sys_user_sex" :value="viewForm.gender" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="院系">{{ viewForm.department }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业">{{ viewForm.major }}</el-descriptions-item>
|
||||
<el-descriptions-item label="班级">{{ viewForm.className }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系方式">{{ viewForm.phone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱">{{ viewForm.email }}</el-descriptions-item>
|
||||
<el-descriptions-item label="成员角色">
|
||||
{{ viewForm.roleType || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<dict-tag :options="stgl_member_status" :value="viewForm.status" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="加入时间">{{ parseTime(viewForm.joinTime, '{y}-{m}-{d}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ parseTime(viewForm.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-divider content-position="left">备注信息</el-divider>
|
||||
<div class="view-description">
|
||||
{{ viewForm.remark || '暂无备注' }}
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelView">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 导入对话框 -->
|
||||
<el-dialog v-model="upload.open" :title="upload.title" width="400px" append-to-body>
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:limit="1"
|
||||
accept=".xlsx, .xls"
|
||||
:headers="upload.headers"
|
||||
:action="getImportUrl()"
|
||||
:disabled="upload.isUploading"
|
||||
:on-progress="handleFileUploadProgress"
|
||||
:on-success="handleFileSuccess"
|
||||
:on-error="handleFileError"
|
||||
:auto-upload="false"
|
||||
drag
|
||||
>
|
||||
<el-icon class="el-icon--upload">
|
||||
<i-ep-upload-filled />
|
||||
</el-icon>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="text-center el-upload__tip">
|
||||
<div class="el-upload__tip" style="color: red">仅支持新增成员数据,不更新已存在的成员</div>
|
||||
<div class="el-upload__tip" style="color: #409eff; margin-top: 5px">
|
||||
<el-icon style="margin-right: 4px"><InfoFilled /></el-icon>
|
||||
请按照模板格式填写:社团名称、学号、姓名、加入时间
|
||||
</div>
|
||||
<span>仅允许导入xls、xlsx格式文件。</span>
|
||||
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
||||
<el-button @click="upload.open = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, toRefs, onMounted, getCurrentInstance, nextTick, watch, computed } from 'vue';
|
||||
import { ArrowDown, User } from '@element-plus/icons-vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { listStglMember, getStglMember, delStglMember, addStglMember, updateStglMember } from '@/api/stgl/member';
|
||||
import { getUser } from '@/api/system/user';
|
||||
import { StglMemberVO, StglMemberQuery, StglMemberForm } from '@/api/stgl/member/types';
|
||||
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 type { UploadInstance } from 'element-plus';
|
||||
|
||||
interface Props {
|
||||
clubId: string | number;
|
||||
clubName: string;
|
||||
memberLimit?: number;
|
||||
currentMemberCount?: number;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { sys_user_sex, stgl_member_status } = toRefs<any>(proxy?.useDict('sys_user_sex', 'stgl_member_status'));
|
||||
|
||||
const memberList = ref<StglMemberVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const memberFormRef = ref<ElFormInstance>();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const viewDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const viewForm = reactive<StglMemberVO>({
|
||||
id: undefined,
|
||||
memberType: 1,
|
||||
relationId: undefined,
|
||||
clubName: '',
|
||||
userId: undefined,
|
||||
studentNo: '',
|
||||
studentName: '',
|
||||
gender: '',
|
||||
department: '',
|
||||
major: '',
|
||||
className: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
roleType: '普通成员',
|
||||
roleName: '',
|
||||
joinTime: '',
|
||||
status: '2',
|
||||
remark: '',
|
||||
createTime: ''
|
||||
});
|
||||
|
||||
const initFormData: StglMemberForm = {
|
||||
id: undefined,
|
||||
memberType: 1,
|
||||
relationId: props.clubId,
|
||||
userId: undefined,
|
||||
selectedStudent: null,
|
||||
studentNo: undefined,
|
||||
studentName: undefined,
|
||||
gender: undefined,
|
||||
department: undefined,
|
||||
major: undefined,
|
||||
className: undefined,
|
||||
phone: undefined,
|
||||
email: undefined,
|
||||
roleType: '普通成员',
|
||||
roleName: undefined,
|
||||
joinTime: undefined,
|
||||
remark: undefined
|
||||
};
|
||||
|
||||
const data = reactive<PageData<StglMemberForm, StglMemberQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
memberType: 1,
|
||||
relationId: props.clubId,
|
||||
status: undefined, // 初始为空,让getList方法处理多状态查询
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
params: {
|
||||
phone: undefined
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
selectedStudent: [{ required: true, message: '请选择学生', trigger: 'change' }],
|
||||
studentNo: [{ required: true, message: '学号不能为空', trigger: 'blur' }],
|
||||
gender: [{ required: true, message: '性别不能为空', trigger: 'change' }],
|
||||
department: [{ required: true, message: '院系不能为空', trigger: 'blur' }],
|
||||
major: [{ required: true, message: '专业不能为空', trigger: 'blur' }],
|
||||
className: [{ required: true, message: '班级不能为空', trigger: 'blur' }],
|
||||
phone: [{ required: true, message: '联系方式不能为空', trigger: 'blur' }],
|
||||
roleType: [{ required: true, message: '成员角色不能为空', trigger: 'blur' }],
|
||||
joinTime: [{ required: true, message: '加入时间不能为空', trigger: 'blur' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
// 计算属性:判断新增按钮是否应该被禁用
|
||||
const isAddButtonDisabled = computed(() => {
|
||||
// 使用本地成员数量,确保实时更新
|
||||
const currentCount = memberList.value.length;
|
||||
|
||||
if (!props.memberLimit || props.memberLimit <= 0) {
|
||||
return false; // 没有设置上限,允许新增
|
||||
}
|
||||
return currentCount >= props.memberLimit;
|
||||
});
|
||||
|
||||
// 计算属性:新增按钮的提示信息
|
||||
const getAddButtonTooltip = computed(() => {
|
||||
const currentCount = memberList.value.length;
|
||||
|
||||
if (!props.memberLimit || props.memberLimit <= 0) {
|
||||
return '该社团未设置成员上限';
|
||||
}
|
||||
return `当前成员:${currentCount}/${props.memberLimit}${currentCount >= props.memberLimit ? ',已达到上限' : ''}`;
|
||||
});
|
||||
|
||||
// 计算属性:成员数量标签的类型
|
||||
const getMemberCountTagType = computed(() => {
|
||||
const currentCount = memberList.value.length;
|
||||
|
||||
if (!props.memberLimit || props.memberLimit <= 0) {
|
||||
return 'info'; // 无上限时显示信息标签
|
||||
}
|
||||
if (currentCount >= props.memberLimit) {
|
||||
return 'danger'; // 达到上限时显示危险标签
|
||||
}
|
||||
if (currentCount >= props.memberLimit * 0.8) {
|
||||
return 'warning'; // 接近上限时显示警告标签
|
||||
}
|
||||
return 'success'; // 正常范围内显示成功标签
|
||||
});
|
||||
|
||||
// 导入相关
|
||||
const uploadRef = ref<UploadInstance>();
|
||||
const upload = reactive<{
|
||||
open: boolean;
|
||||
title: string;
|
||||
isUploading: boolean;
|
||||
url: string;
|
||||
headers: { Authorization: string };
|
||||
}>({
|
||||
open: false,
|
||||
title: '',
|
||||
isUploading: false,
|
||||
url: import.meta.env.VITE_APP_BASE_API + '/stgl/member/importData',
|
||||
headers: globalHeaders()
|
||||
});
|
||||
|
||||
/** 查询成员列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
// 构建查询参数,支持多状态查询
|
||||
const queryData = { ...queryParams.value };
|
||||
|
||||
console.log('JoinedMembersTab: getList 开始查询,参数:', queryData);
|
||||
|
||||
// 如果状态字段为空或未选择,则查询已加入(status=2)和已退出(status=3)的成员
|
||||
if (!queryData.status) {
|
||||
// 使用params传递多状态查询
|
||||
queryData.params = { ...queryData.params, statusList: ['2', '3'] } as any;
|
||||
delete queryData.status; // 删除单个status字段
|
||||
}
|
||||
|
||||
console.log('JoinedMembersTab: 最终查询参数:', queryData);
|
||||
|
||||
const res = await listStglMember(queryData);
|
||||
console.log('JoinedMembersTab: 查询结果:', res);
|
||||
|
||||
memberList.value = res.rows;
|
||||
total.value = res.total;
|
||||
} catch (error) {
|
||||
console.error('JoinedMembersTab: 查询成员列表失败:', error);
|
||||
proxy?.$modal.msgError('查询成员列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData, relationId: props.clubId };
|
||||
memberFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
queryParams.value = {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
memberType: 1,
|
||||
relationId: props.clubId,
|
||||
status: undefined, // 重置为空,让getList方法处理多状态查询
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
params: {
|
||||
phone: undefined
|
||||
}
|
||||
};
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: StglMemberVO[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
// 检查人员上限
|
||||
if (props.memberLimit && props.memberLimit > 0) {
|
||||
if (props.currentMemberCount && props.currentMemberCount >= props.memberLimit) {
|
||||
proxy?.$modal.msgError(`该社团成员已满,当前成员数量:${props.currentMemberCount},成员上限:${props.memberLimit},无法再新增成员`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
reset();
|
||||
form.value.joinTime = parseTime(new Date(), '{y}-{m}-{d}');
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加成员';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: StglMemberVO) => {
|
||||
reset();
|
||||
const _id = row?.id || ids.value[0];
|
||||
const res = await getStglMember(_id);
|
||||
|
||||
Object.assign(form.value, res.data);
|
||||
|
||||
if (res.data) {
|
||||
form.value.selectedStudent = {
|
||||
id: res.data.userId,
|
||||
studentNo: res.data.studentNo,
|
||||
name: res.data.studentName,
|
||||
gender: res.data.gender,
|
||||
phone: res.data.phone,
|
||||
collegeId: '',
|
||||
collegeName: res.data.department,
|
||||
majorId: '',
|
||||
majorName: res.data.major,
|
||||
classId: '',
|
||||
className: res.data.className,
|
||||
grade: '',
|
||||
studentStatus: '',
|
||||
enrollmentYear: new Date().getFullYear()
|
||||
};
|
||||
}
|
||||
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改成员';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
memberFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
try {
|
||||
if (form.value.id) {
|
||||
await updateStglMember(form.value);
|
||||
} else {
|
||||
await addStglMember(form.value);
|
||||
}
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
// 通知父组件成员数量变化
|
||||
notifyMemberCountChange();
|
||||
} catch (error) {
|
||||
console.error('操作失败:', error);
|
||||
proxy?.$modal.msgError('操作失败');
|
||||
} finally {
|
||||
buttonLoading.value = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: StglMemberVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
let confirmMessage = '';
|
||||
|
||||
if (row) {
|
||||
confirmMessage = `是否确认删除成员"${row.studentName}(${row.studentNo})"的数据项?`;
|
||||
} else {
|
||||
const selectedItems = memberList.value.filter((item) => ids.value.includes(item.id));
|
||||
if (selectedItems.length === 1) {
|
||||
confirmMessage = `是否确认删除成员"${selectedItems[0].studentName}(${selectedItems[0].studentNo})"的数据项?`;
|
||||
} else {
|
||||
const names = selectedItems.map((item) => `${item.studentName}(${item.studentNo})`).join('、');
|
||||
confirmMessage = `是否确认删除成员"${names}"的数据项?`;
|
||||
}
|
||||
}
|
||||
|
||||
await proxy?.$modal.confirm(confirmMessage);
|
||||
await delStglMember(_ids);
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
// 通知父组件成员数量变化
|
||||
notifyMemberCountChange();
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'stgl/member/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`${props.clubName}_成员信息_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
/** 查看按钮操作 */
|
||||
const handleView = async (row: StglMemberVO) => {
|
||||
Object.assign(viewForm, row);
|
||||
viewDialog.visible = true;
|
||||
viewDialog.title = '查看成员信息';
|
||||
};
|
||||
|
||||
/** 取消查看对话框 */
|
||||
const cancelView = () => {
|
||||
viewDialog.visible = false;
|
||||
};
|
||||
|
||||
/** 学生选择变化处理 */
|
||||
const handleStudentChange = async (student: StudentVO | null) => {
|
||||
if (student) {
|
||||
form.value.userId = student.id;
|
||||
form.value.studentName = student.name;
|
||||
form.value.studentNo = student.studentNo;
|
||||
form.value.gender = student.gender;
|
||||
form.value.department = student.collegeName;
|
||||
form.value.major = student.majorName;
|
||||
form.value.className = student.className;
|
||||
form.value.phone = student.phone;
|
||||
|
||||
try {
|
||||
const userRes = await getUser(student.id);
|
||||
if (userRes.data && userRes.data.user) {
|
||||
form.value.email = userRes.data.user.email || '';
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('获取用户邮箱失败:', error);
|
||||
form.value.email = '';
|
||||
}
|
||||
} else {
|
||||
form.value.userId = undefined;
|
||||
form.value.studentName = undefined;
|
||||
form.value.studentNo = undefined;
|
||||
form.value.gender = undefined;
|
||||
form.value.department = undefined;
|
||||
form.value.major = undefined;
|
||||
form.value.className = undefined;
|
||||
form.value.phone = undefined;
|
||||
form.value.email = undefined;
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
memberFormRef.value?.validateField(['selectedStudent', 'studentNo']);
|
||||
});
|
||||
};
|
||||
|
||||
/** 导入按钮操作 */
|
||||
const handleImport = () => {
|
||||
upload.title = '成员导入';
|
||||
upload.open = true;
|
||||
};
|
||||
|
||||
/** 下载模板操作 */
|
||||
const importTemplate = () => {
|
||||
proxy?.download('stgl/member/importTemplate', {}, `成员导入模板_${new Date().getTime()}.xlsx`);
|
||||
};
|
||||
|
||||
/** 文件上传中处理 */
|
||||
const handleFileUploadProgress = () => {
|
||||
upload.isUploading = true;
|
||||
};
|
||||
|
||||
/** 文件上传成功处理 */
|
||||
const handleFileSuccess = (response: any) => {
|
||||
upload.open = false;
|
||||
upload.isUploading = false;
|
||||
uploadRef.value?.clearFiles();
|
||||
|
||||
ElMessage.closeAll();
|
||||
|
||||
ElMessageBox.alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + '</div>', '导入结果', {
|
||||
dangerouslyUseHTMLString: true
|
||||
});
|
||||
getList();
|
||||
// 通知父组件成员数量变化
|
||||
notifyMemberCountChange();
|
||||
};
|
||||
|
||||
/** 文件上传失败处理 */
|
||||
const handleFileError = (error: any) => {
|
||||
upload.isUploading = false;
|
||||
uploadRef.value?.clearFiles();
|
||||
|
||||
ElMessage.closeAll();
|
||||
|
||||
console.error('文件上传失败:', error);
|
||||
ElMessage.error('导入失败,请检查文件格式或网络连接');
|
||||
};
|
||||
|
||||
/** 获取导入URL */
|
||||
const getImportUrl = () => {
|
||||
const baseUrl = upload.url;
|
||||
const params = new URLSearchParams();
|
||||
params.append('updateSupport', 'false');
|
||||
|
||||
return `${baseUrl}?${params.toString()}`;
|
||||
};
|
||||
|
||||
/** 提交上传文件 */
|
||||
const submitFileForm = () => {
|
||||
ElMessage({
|
||||
message: '正在导入数据,请耐心等待...',
|
||||
type: 'info',
|
||||
duration: 0,
|
||||
zIndex: 4000
|
||||
});
|
||||
|
||||
upload.isUploading = true;
|
||||
uploadRef.value?.submit();
|
||||
};
|
||||
|
||||
// 监听clubId变化,重新获取数据
|
||||
watch(
|
||||
() => props.clubId,
|
||||
(newClubId) => {
|
||||
console.log('JoinedMembersTab: clubId 变化:', newClubId);
|
||||
if (newClubId) {
|
||||
// 重置查询参数
|
||||
queryParams.value = {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
memberType: 1,
|
||||
relationId: newClubId,
|
||||
status: undefined, // 重置为空,让getList方法处理多状态查询
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
params: {
|
||||
phone: undefined
|
||||
}
|
||||
};
|
||||
getList();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
|
||||
// 暴露方法给父组件调用
|
||||
defineExpose({
|
||||
getList
|
||||
});
|
||||
|
||||
// 定义事件,用于通知父组件数据变化
|
||||
const emit = defineEmits<{
|
||||
memberCountChanged: [newCount: number]
|
||||
}>();
|
||||
|
||||
// 通知父组件成员数量变化
|
||||
const notifyMemberCountChange = () => {
|
||||
// 重新获取最新的成员数量
|
||||
getList().then(() => {
|
||||
const newCount = memberList.value.length;
|
||||
console.log('JoinedMembersTab: 成员数量变化,通知父组件:', newCount);
|
||||
emit('memberCountChanged', newCount);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.view-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;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<el-dialog :title="title" :model-value="visible" @update:model-value="handleVisibleChange" width="1200px" append-to-body>
|
||||
<el-tabs v-model="activeTab" type="border-card" :key="clubId" @tab-change="handleTabChange">
|
||||
<!-- 已加入标签页 -->
|
||||
<el-tab-pane label="已加入" name="joined">
|
||||
<JoinedMembersTab
|
||||
ref="joinedTabRef"
|
||||
:club-id="clubId"
|
||||
:club-name="clubName"
|
||||
:member-limit="memberLimit"
|
||||
:current-member-count="currentMemberCount"
|
||||
:key="`joined-${clubId}`"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 待审核标签页 -->
|
||||
<el-tab-pane label="待审核" name="pending">
|
||||
<PendingMembersTab
|
||||
ref="pendingTabRef"
|
||||
:club-id="clubId"
|
||||
:club-name="clubName"
|
||||
:key="`pending-${clubId}`"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, nextTick } from 'vue';
|
||||
import JoinedMembersTab from './JoinedMembersTab.vue';
|
||||
import PendingMembersTab from './PendingMembersTab.vue';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
title: string;
|
||||
clubId: string | number;
|
||||
clubName: string;
|
||||
memberLimit?: number;
|
||||
currentMemberCount?: number;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const activeTab = ref('joined');
|
||||
const joinedTabRef = ref();
|
||||
const pendingTabRef = ref();
|
||||
|
||||
// 监听visible变化
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
activeTab.value = 'joined';
|
||||
// 当弹窗打开时,触发当前标签页的数据加载
|
||||
nextTick(() => {
|
||||
if (activeTab.value === 'joined' && joinedTabRef.value) {
|
||||
joinedTabRef.value.getList();
|
||||
} else if (activeTab.value === 'pending' && pendingTabRef.value) {
|
||||
pendingTabRef.value.getList();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 监听clubId变化,重置标签页
|
||||
watch(() => props.clubId, (newClubId) => {
|
||||
console.log('MemberManagementDialog: clubId 变化:', newClubId);
|
||||
if (newClubId) {
|
||||
activeTab.value = 'joined';
|
||||
}
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
emit('update:visible', false);
|
||||
};
|
||||
|
||||
const handleVisibleChange = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
/** 标签页切换处理 */
|
||||
const handleTabChange = (tabName: string) => {
|
||||
console.log('标签页切换到:', tabName);
|
||||
|
||||
// 根据当前激活的标签页,调用对应组件的刷新方法
|
||||
if (tabName === 'joined' && joinedTabRef.value) {
|
||||
// 调用已加入成员Tab的刷新方法
|
||||
if (typeof joinedTabRef.value.getList === 'function') {
|
||||
joinedTabRef.value.getList();
|
||||
}
|
||||
} else if (tabName === 'pending' && pendingTabRef.value) {
|
||||
// 调用待审核成员Tab的刷新方法
|
||||
if (typeof pendingTabRef.value.getList === 'function') {
|
||||
pendingTabRef.value.getList();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,514 @@
|
||||
<template>
|
||||
<div class="pending-members-tab">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="always" style="padding: 8px 16px;">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0;">
|
||||
<el-row :gutter="16" align="middle">
|
||||
<el-col :span="5">
|
||||
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0;">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0;">
|
||||
<el-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.params.phone" placeholder="请输入联系方式" clearable size="small" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="search-btn-container">
|
||||
<el-form-item style="margin-bottom: 0;">
|
||||
<el-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">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="info"
|
||||
plain
|
||||
icon="Check"
|
||||
:disabled="multiple"
|
||||
@click="handleBatchApprove()"
|
||||
v-hasPermi="['stgl:clubmember:approve']"
|
||||
>批量审核</el-button
|
||||
>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="memberList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="序号" type="index" width="55" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" width="80">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="stgl_member_status" :value="scope.row.status" v-if="scope.row.status" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="学号" align="center" prop="studentNo" width="120" />
|
||||
<el-table-column label="姓名" align="center" prop="studentName" width="80" />
|
||||
<el-table-column label="性别" align="center" prop="gender" width="60">
|
||||
<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="联系方式" align="center" prop="phone" width="120" />
|
||||
<el-table-column label="院系" align="center" prop="department" width="120" />
|
||||
<el-table-column label="专业" align="center" prop="major" width="180" />
|
||||
<el-table-column label="班级" align="center" prop="className" width="120" />
|
||||
<el-table-column label="角色类型" align="center" prop="roleType" width="100">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="stgl_role_type" :value="scope.row.roleType" v-if="scope.row.roleType" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="申请时间" align="center" prop="createTime" width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" align="center" prop="remark" width="200" show-overflow-tooltip />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="查看" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="info"
|
||||
icon="View"
|
||||
size="default"
|
||||
style="font-size: 16px; margin-right: 8px; color: blue"
|
||||
@click="handleView(scope.row)"
|
||||
v-hasPermi="['stgl:clubmember:query']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="审核" placement="top">
|
||||
<el-button
|
||||
link
|
||||
type="success"
|
||||
icon="Check"
|
||||
size="default"
|
||||
style="font-size: 16px; margin-right: 8px"
|
||||
@click="handleApprove(scope.row)"
|
||||
v-hasPermi="['stgl:clubmember:approve']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
|
||||
<!-- 查看成员详情对话框 -->
|
||||
<el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="800px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="学号">{{ viewForm.studentNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ viewForm.studentName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
<dict-tag :options="sys_user_sex" :value="viewForm.gender" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="院系">{{ viewForm.department }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业">{{ viewForm.major }}</el-descriptions-item>
|
||||
<el-descriptions-item label="班级">{{ viewForm.className }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系方式">{{ viewForm.phone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱">{{ viewForm.email }}</el-descriptions-item>
|
||||
<el-descriptions-item label="角色类型">
|
||||
<dict-tag :options="stgl_role_type" :value="viewForm.roleType" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<dict-tag :options="stgl_member_status" :value="viewForm.status" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="申请时间">{{ parseTime(viewForm.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-divider content-position="left">备注信息</el-divider>
|
||||
<div class="view-description">
|
||||
{{ viewForm.remark || '暂无备注' }}
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelView">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 审核对话框 -->
|
||||
<el-dialog :title="approvalTitle" v-model="approvalOpen" width="500px" append-to-body>
|
||||
<el-form ref="approvalRef" :model="approvalForm" :rules="approvalRules" label-width="80px">
|
||||
<el-form-item label="审核结果" prop="approvalStatus">
|
||||
<el-select v-model="approvalForm.approvalStatus" placeholder="请选择审核结果" style="width: 100%">
|
||||
<el-option :value="2" label="审核通过">
|
||||
<span style="display: flex; align-items: center">
|
||||
<el-icon style="color: #67c23a; margin-right: 8px"><Check /></el-icon>
|
||||
审核通过
|
||||
</span>
|
||||
</el-option>
|
||||
<el-option :value="3" label="审核不通过">
|
||||
<span style="display: flex; align-items: center">
|
||||
<el-icon style="color: #f56c6c; margin-right: 8px"><Close /></el-icon>
|
||||
审核不通过
|
||||
</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="审核意见" prop="approvalRemark">
|
||||
<el-input v-model="approvalForm.approvalRemark" type="textarea" placeholder="请输入审核意见" :rows="4" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitApproval">确 定</el-button>
|
||||
<el-button @click="approvalOpen = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, toRefs, onMounted, getCurrentInstance, watch } from 'vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import { Check, Close } from '@element-plus/icons-vue';
|
||||
import { listStglMember, getStglMember, approveMember } from '@/api/stgl/member';
|
||||
import { StglMemberVO, StglMemberQuery } from '@/api/stgl/member/types';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
|
||||
interface Props {
|
||||
clubId: string | number;
|
||||
clubName: string;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { stgl_role_type, sys_user_sex, stgl_member_status } = toRefs<any>(proxy?.useDict('stgl_role_type', 'sys_user_sex', 'stgl_member_status'));
|
||||
|
||||
const memberList = ref<StglMemberVO[]>([]);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const approvalRef = ref<ElFormInstance>();
|
||||
|
||||
const viewDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const approvalOpen = ref(false);
|
||||
const approvalTitle = ref('');
|
||||
|
||||
const viewForm = reactive<StglMemberVO>({
|
||||
id: undefined,
|
||||
memberType: 1,
|
||||
relationId: undefined,
|
||||
clubName: '',
|
||||
userId: undefined,
|
||||
studentNo: '',
|
||||
studentName: '',
|
||||
gender: '',
|
||||
department: '',
|
||||
major: '',
|
||||
className: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
roleType: '1',
|
||||
roleName: '',
|
||||
joinTime: '',
|
||||
status: '0',
|
||||
remark: '',
|
||||
createTime: ''
|
||||
});
|
||||
|
||||
const approvalForm = reactive({
|
||||
id: undefined,
|
||||
approvalStatus: undefined,
|
||||
approvalRemark: ''
|
||||
});
|
||||
|
||||
const approvalRules = reactive({
|
||||
approvalStatus: [{ required: true, message: '请选择审核结果', trigger: 'change' }],
|
||||
approvalRemark: [{ required: true, message: '请输入审核意见', trigger: 'blur' }]
|
||||
});
|
||||
|
||||
const data = reactive({
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
memberType: 1,
|
||||
relationId: props.clubId,
|
||||
status: undefined, // 初始为空,让getList方法处理多状态查询
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
params: {
|
||||
phone: undefined
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams } = toRefs(data);
|
||||
|
||||
/** 查询成员列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
// 构建查询参数,支持多状态查询
|
||||
const queryData = { ...queryParams.value };
|
||||
|
||||
console.log('PendingMembersTab: getList 开始查询,参数:', queryData);
|
||||
|
||||
// 如果状态字段为空或未选择,则查询待审核(status=0)和已驳回(status=4)的成员
|
||||
if (!queryData.status) {
|
||||
// 使用params传递多状态查询
|
||||
queryData.params = { ...queryData.params, statusList: ['0', '4'] } as any;
|
||||
delete queryData.status; // 删除单个status字段
|
||||
}
|
||||
|
||||
console.log('PendingMembersTab: 最终查询参数:', queryData);
|
||||
|
||||
const res = await listStglMember(queryData);
|
||||
console.log('PendingMembersTab: 查询结果:', res);
|
||||
|
||||
memberList.value = res.rows;
|
||||
total.value = res.total;
|
||||
} catch (error) {
|
||||
console.error('PendingMembersTab: 查询成员列表失败:', error);
|
||||
proxy?.$modal.msgError('查询成员列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
queryParams.value = {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
memberType: 1,
|
||||
relationId: props.clubId,
|
||||
status: undefined, // 重置为空,让getList方法处理多状态查询
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
params: {
|
||||
phone: undefined
|
||||
}
|
||||
};
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: StglMemberVO[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 查看按钮操作 */
|
||||
const handleView = async (row: StglMemberVO) => {
|
||||
Object.assign(viewForm, row);
|
||||
viewDialog.visible = true;
|
||||
viewDialog.title = '查看申请信息';
|
||||
};
|
||||
|
||||
/** 取消查看对话框 */
|
||||
const cancelView = () => {
|
||||
viewDialog.visible = false;
|
||||
};
|
||||
|
||||
/** 验证成员数据完整性 */
|
||||
const validateMemberData = (member: StglMemberVO, operation: string): boolean => {
|
||||
if (!member.userId) {
|
||||
proxy?.$modal.msgError(`${operation}失败:用户ID不能为空`);
|
||||
console.error(`${operation}失败 - 用户ID缺失:`, member);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!member.relationId && !props.clubId) {
|
||||
proxy?.$modal.msgError(`${operation}失败:关联ID(社团ID)不能为空`);
|
||||
console.error(`${operation}失败 - 关联ID缺失:`, member);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!member.memberType) {
|
||||
proxy?.$modal.msgError(`${operation}失败:成员类型不能为空`);
|
||||
console.error(`${operation}失败 - 成员类型缺失:`, member);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!member.studentNo || !member.studentName) {
|
||||
proxy?.$modal.msgError(`${operation}失败:学号和姓名不能为空`);
|
||||
console.error(`${operation}失败 - 基础信息缺失:`, member);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/** 审核按钮操作 */
|
||||
const handleApprove = (row: StglMemberVO) => {
|
||||
// 验证数据完整性
|
||||
if (!validateMemberData(row, '审核')) {
|
||||
return;
|
||||
}
|
||||
|
||||
approvalForm.id = row.id;
|
||||
approvalForm.approvalStatus = undefined;
|
||||
approvalForm.approvalRemark = '';
|
||||
approvalTitle.value = '审核';
|
||||
approvalOpen.value = true;
|
||||
};
|
||||
|
||||
/** 批量审核操作 */
|
||||
const handleBatchApprove = () => {
|
||||
if (ids.value.length === 0) {
|
||||
proxy.$modal.msgError('请选择要审核的数据');
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证所有选中成员的数据完整性
|
||||
const invalidMembers = memberList.value
|
||||
.filter(member => ids.value.includes(member.id))
|
||||
.filter(member => !validateMemberData(member, '批量审核'));
|
||||
|
||||
if (invalidMembers.length > 0) {
|
||||
const invalidNames = invalidMembers.map(m => m.studentName || '未知成员').join('、');
|
||||
proxy.$modal.msgError(`以下成员数据不完整,无法审核:${invalidNames}`);
|
||||
return;
|
||||
}
|
||||
|
||||
approvalForm.id = undefined; // 标识为批量审核
|
||||
approvalForm.approvalStatus = undefined;
|
||||
approvalForm.approvalRemark = '';
|
||||
approvalTitle.value = '批量审核';
|
||||
approvalOpen.value = true;
|
||||
};
|
||||
|
||||
/** 提交审核 */
|
||||
const submitApproval = () => {
|
||||
const validateFields = ['approvalStatus', 'approvalRemark'];
|
||||
|
||||
approvalRef.value.validateField(validateFields, (valid) => {
|
||||
if (valid) {
|
||||
if (approvalForm.id) {
|
||||
// 单个审核 - 再次验证数据完整性
|
||||
const member = memberList.value.find(m => m.id === approvalForm.id);
|
||||
if (member && !validateMemberData(member, '审核')) {
|
||||
return;
|
||||
}
|
||||
|
||||
approveMember(approvalForm.id, approvalForm.approvalStatus === 2).then(() => {
|
||||
proxy.$modal.msgSuccess('审核成功');
|
||||
approvalOpen.value = false;
|
||||
getList();
|
||||
}).catch((error) => {
|
||||
console.error('审核失败:', error);
|
||||
proxy.$modal.msgError('审核失败,请检查数据完整性');
|
||||
});
|
||||
} else {
|
||||
// 批量审核 - 再次验证数据完整性
|
||||
const selectedMembers = memberList.value.filter(m => ids.value.includes(m.id));
|
||||
const invalidMembers = selectedMembers.filter(member => !validateMemberData(member, '批量审核'));
|
||||
|
||||
if (invalidMembers.length > 0) {
|
||||
const invalidNames = invalidMembers.map(m => m.studentName || '未知成员').join('、');
|
||||
proxy.$modal.msgError(`以下成员数据不完整,无法审核:${invalidNames}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const promises = ids.value.map(id =>
|
||||
approveMember(id, approvalForm.approvalStatus === 2)
|
||||
);
|
||||
Promise.all(promises).then(() => {
|
||||
proxy.$modal.msgSuccess('批量审核成功');
|
||||
approvalOpen.value = false;
|
||||
getList();
|
||||
}).catch((error) => {
|
||||
console.error('批量审核失败:', error);
|
||||
proxy.$modal.msgError('批量审核失败,请检查数据完整性');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 监听clubId变化,重新获取数据
|
||||
watch(
|
||||
() => props.clubId,
|
||||
(newClubId) => {
|
||||
console.log('PendingMembersTab: clubId 变化:', newClubId);
|
||||
if (newClubId) {
|
||||
// 重置查询参数
|
||||
queryParams.value = {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
memberType: 1,
|
||||
relationId: newClubId,
|
||||
status: undefined, // 重置为空,让getList方法处理多状态查询
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
params: {
|
||||
phone: undefined
|
||||
}
|
||||
};
|
||||
getList();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
|
||||
// 暴露方法给父组件调用
|
||||
defineExpose({
|
||||
getList
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.view-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;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,735 @@
|
||||
<template>
|
||||
<div class="joined-members-tab">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="always" style="padding: 8px 16px;">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0;">
|
||||
<el-row :gutter="16" align="middle">
|
||||
<el-col :span="5">
|
||||
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0;">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0;">
|
||||
<el-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>
|
||||
<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">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-tooltip :content="getAddButtonTooltip" placement="top" :disabled="!isAddButtonDisabled">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" :disabled="isAddButtonDisabled" v-hasPermi="['stgl:volunteermember:add']">
|
||||
新增
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['stgl:volunteermember:edit']">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['stgl:volunteermember:remove']">删除</el-button>
|
||||
</el-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="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-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="memberList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="序号" type="index" width="55" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" width="80">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="stgl_member_status" :value="scope.row.status" v-if="scope.row.status !== undefined" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="学号" align="center" prop="studentNo" width="120" />
|
||||
<el-table-column label="姓名" align="center" prop="studentName" width="80" />
|
||||
<el-table-column label="性别" align="center" prop="gender" width="60">
|
||||
<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="联系方式" align="center" prop="phone" width="120" />
|
||||
<el-table-column label="院系" align="center" prop="department" width="120" />
|
||||
<el-table-column label="专业" align="center" prop="major" width="180" />
|
||||
<el-table-column label="班级" align="center" prop="className" width="120" />
|
||||
<el-table-column label="成员角色" align="center" prop="roleType" width="100">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.roleType">{{ scope.row.roleType }}</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="加入时间" align="center" prop="joinTime" width="100">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.joinTime, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<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:volunteermember:query']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['stgl:volunteermember:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['stgl:volunteermember:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
|
||||
<!-- 添加或修改志愿者成员对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body>
|
||||
<el-form ref="memberFormRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="姓名" prop="selectedStudent">
|
||||
<StudentSelector v-model="form.selectedStudent" placeholder="请选择学生" :showStudentNo="false" @change="handleStudentChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学号" prop="studentNo">
|
||||
<el-input v-model="form.studentNo" placeholder="学号" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="性别" prop="gender">
|
||||
<el-select v-model="form.gender" placeholder="性别" disabled>
|
||||
<el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value"> </el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="院系" prop="department">
|
||||
<el-input v-model="form.department" placeholder="院系" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业" prop="major">
|
||||
<el-input v-model="form.major" placeholder="专业" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="班级" prop="className">
|
||||
<el-input v-model="form.className" placeholder="班级" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系方式" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="联系方式" readonly />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="form.email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="成员角色" prop="roleType">
|
||||
<el-input v-model="form.roleType" placeholder="请输入成员角色" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="form.status" placeholder="请选择状态">
|
||||
<el-option v-for="dict in stgl_member_status" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="加入时间" prop="joinTime">
|
||||
<el-date-picker clearable v-model="form.joinTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择加入时间"> </el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 查看成员详情对话框 -->
|
||||
<el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="800px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="学号">{{ viewForm.studentNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ viewForm.studentName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
<dict-tag :options="sys_user_sex" :value="viewForm.gender" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="院系">{{ viewForm.department }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业">{{ viewForm.major }}</el-descriptions-item>
|
||||
<el-descriptions-item label="班级">{{ viewForm.className }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系方式">{{ viewForm.phone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱">{{ viewForm.email }}</el-descriptions-item>
|
||||
<el-descriptions-item label="成员角色">
|
||||
{{ viewForm.roleType || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<dict-tag :options="stgl_member_status" :value="viewForm.status" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="加入时间">{{ parseTime(viewForm.joinTime, '{y}-{m}-{d}') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ parseTime(viewForm.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-divider content-position="left">备注信息</el-divider>
|
||||
<div class="view-description">
|
||||
{{ viewForm.remark || '暂无备注' }}
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelView">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 导入对话框 -->
|
||||
<el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:limit="1"
|
||||
accept=".xlsx, .xls"
|
||||
:headers="upload.headers"
|
||||
:action="upload.url"
|
||||
:disabled="upload.isUploading"
|
||||
:on-progress="handleFileUploadProgress"
|
||||
:on-success="handleFileSuccess"
|
||||
:auto-upload="false"
|
||||
drag
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip text-center">
|
||||
<div class="el-upload__tip">
|
||||
<el-checkbox v-model="upload.updateSupport" />
|
||||
是否更新已经存在的志愿者成员数据
|
||||
</div>
|
||||
<span>仅允许导入xls、xlsx格式文件。</span>
|
||||
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
||||
<el-button @click="upload.open = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getCurrentInstance, onMounted, reactive, ref, toRefs, computed } from 'vue';
|
||||
import {
|
||||
listVolunteerMember,
|
||||
getVolunteerMember,
|
||||
delVolunteerMember,
|
||||
addVolunteerMember,
|
||||
updateVolunteerMember,
|
||||
exportVolunteerMember
|
||||
} from '@/api/stgl/volunteermember';
|
||||
import { VolunteerMemberVO, VolunteerMemberForm, VolunteerMemberQuery } from '@/api/stgl/volunteermember/types';
|
||||
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 type { UploadInstance } from 'element-plus';
|
||||
|
||||
interface Props {
|
||||
teamId: string | number;
|
||||
teamName: string;
|
||||
memberLimit?: number;
|
||||
currentMemberCount?: number;
|
||||
}
|
||||
|
||||
// 扩展VolunteerMemberForm以包含选择的学生对象
|
||||
interface ExtendedVolunteerMemberForm extends VolunteerMemberForm {
|
||||
selectedStudent?: StudentVO | null;
|
||||
userId?: string | number; // 添加userId字段
|
||||
memberType?: number; // 添加memberType字段
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { sys_user_sex, sys_normal_disable, stgl_member_status } = toRefs<any>(proxy?.useDict('sys_user_sex', 'sys_normal_disable', 'stgl_member_status'));
|
||||
|
||||
const memberList = ref<VolunteerMemberVO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const memberFormRef = ref<ElFormInstance>();
|
||||
|
||||
const dialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const viewDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const viewForm = reactive<VolunteerMemberVO>({
|
||||
id: undefined,
|
||||
relationId: undefined,
|
||||
studentNo: '',
|
||||
studentName: '',
|
||||
gender: '',
|
||||
department: '',
|
||||
major: '',
|
||||
className: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
roleType: '',
|
||||
joinTime: '',
|
||||
status: '0',
|
||||
remark: '',
|
||||
createTime: ''
|
||||
});
|
||||
|
||||
const initFormData: ExtendedVolunteerMemberForm = {
|
||||
id: undefined,
|
||||
relationId: props.teamId,
|
||||
selectedStudent: null,
|
||||
userId: undefined,
|
||||
memberType: 2, // 志愿者成员类型
|
||||
studentNo: '',
|
||||
studentName: '',
|
||||
gender: '',
|
||||
department: '',
|
||||
major: '',
|
||||
className: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
roleType: '队员',
|
||||
joinTime: undefined,
|
||||
status: '2', // 已加入状态
|
||||
remark: undefined
|
||||
};
|
||||
|
||||
const data = reactive<PageData<ExtendedVolunteerMemberForm, VolunteerMemberQuery>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
relationId: props.teamId,
|
||||
status: undefined, // 初始为空,让getList方法处理多状态查询
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
phone: undefined
|
||||
},
|
||||
rules: {
|
||||
selectedStudent: [{ required: true, message: '请选择学生', trigger: 'change' }],
|
||||
studentNo: [{ required: true, message: '学号不能为空', trigger: 'blur' }],
|
||||
gender: [{ required: true, message: '性别不能为空', trigger: 'change' }],
|
||||
department: [{ required: true, message: '院系不能为空', trigger: 'blur' }],
|
||||
major: [{ required: true, message: '专业不能为空', trigger: 'blur' }],
|
||||
className: [{ required: true, message: '班级不能为空', trigger: 'blur' }],
|
||||
phone: [{ required: true, message: '联系方式不能为空', trigger: 'blur' }],
|
||||
roleType: [{ required: true, message: '成员角色不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
|
||||
joinTime: [{ required: true, message: '加入时间不能为空', trigger: 'blur' }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
// 计算属性:判断新增按钮是否应该被禁用
|
||||
const isAddButtonDisabled = computed(() => {
|
||||
// 使用本地成员数量,确保实时更新
|
||||
const currentCount = memberList.value.length;
|
||||
|
||||
if (!props.memberLimit || props.memberLimit <= 0) {
|
||||
return false; // 没有设置上限,允许新增
|
||||
}
|
||||
return currentCount >= props.memberLimit;
|
||||
});
|
||||
|
||||
// 计算属性:新增按钮的提示信息
|
||||
const getAddButtonTooltip = computed(() => {
|
||||
const currentCount = memberList.value.length;
|
||||
|
||||
if (!props.memberLimit || props.memberLimit <= 0) {
|
||||
return '该团队未设置成员上限';
|
||||
}
|
||||
return `当前成员:${currentCount}/${props.memberLimit}${currentCount >= props.memberLimit ? ',已达到上限' : ''}`;
|
||||
});
|
||||
|
||||
// 上传相关
|
||||
const uploadRef = ref<UploadInstance>();
|
||||
const upload = reactive<{
|
||||
open: boolean;
|
||||
title: string;
|
||||
isUploading: boolean;
|
||||
updateSupport: boolean;
|
||||
url: string;
|
||||
headers: { Authorization: string };
|
||||
}>({
|
||||
open: false,
|
||||
title: '',
|
||||
isUploading: false,
|
||||
updateSupport: false,
|
||||
url: import.meta.env.VITE_APP_BASE_API + '/stgl/volunteer/member/importData',
|
||||
headers: globalHeaders()
|
||||
});
|
||||
|
||||
/** 查询成员列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
// 构建查询参数,支持多状态查询
|
||||
const queryData = { ...queryParams.value };
|
||||
|
||||
console.log('JoinedMembersTab: getList 开始查询,参数:', queryData);
|
||||
|
||||
// 如果状态字段为空或未选择,则查询已加入(status=2)和已退出(status=4)的成员
|
||||
if (!queryData.status) {
|
||||
// 使用params传递多状态查询
|
||||
queryData.params = { statusList: ['2', '4'] } as any;
|
||||
delete queryData.status; // 删除单个status字段
|
||||
}
|
||||
|
||||
console.log('JoinedMembersTab: 最终查询参数:', queryData);
|
||||
|
||||
const res = await listVolunteerMember(queryData);
|
||||
console.log('JoinedMembersTab: 查询结果:', res);
|
||||
|
||||
memberList.value = res.rows;
|
||||
total.value = res.total;
|
||||
} catch (error) {
|
||||
console.error('JoinedMembersTab: 查询成员列表失败:', error);
|
||||
proxy?.$modal.msgError('查询成员列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
dialog.visible = false;
|
||||
};
|
||||
|
||||
/** 表单重置 */
|
||||
const reset = () => {
|
||||
form.value = { ...initFormData, relationId: props.teamId, memberType: 2, status: '2' };
|
||||
memberFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
queryParams.value = {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
relationId: props.teamId,
|
||||
status: undefined, // 重置为空,让getList方法处理多状态查询
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
phone: undefined
|
||||
};
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: VolunteerMemberVO[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
// 检查人员上限 - 使用本地成员数量,确保实时更新
|
||||
const currentCount = memberList.value.length;
|
||||
|
||||
if (props.memberLimit && props.memberLimit > 0) {
|
||||
if (currentCount >= props.memberLimit) {
|
||||
proxy?.$modal.msgError(`该团队成员已满,当前成员数量:${currentCount},成员上限:${props.memberLimit},无法再新增成员`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
reset();
|
||||
form.value.joinTime = parseTime(new Date(), '{y}-{m}-{d}');
|
||||
dialog.visible = true;
|
||||
dialog.title = '添加志愿者成员';
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: VolunteerMemberVO) => {
|
||||
reset();
|
||||
const _id = row?.id || ids.value[0];
|
||||
const res = await getVolunteerMember(_id);
|
||||
Object.assign(form.value, res.data);
|
||||
// 设置学生选择器数据
|
||||
if (res.data.studentName) {
|
||||
form.value.selectedStudent = {
|
||||
id: res.data.userId || 0, // 使用实际的userId
|
||||
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 || ''
|
||||
} as StudentVO;
|
||||
}
|
||||
dialog.visible = true;
|
||||
dialog.title = '修改志愿者成员';
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
memberFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
// 如果是新增操作,再次检查成员数量上限
|
||||
if (!form.value.id) {
|
||||
const currentCount = memberList.value.length;
|
||||
if (props.memberLimit && props.memberLimit > 0 && currentCount >= props.memberLimit) {
|
||||
proxy?.$modal.msgError(`该团队成员已满,当前成员数量:${currentCount},成员上限:${props.memberLimit},无法再新增成员`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
buttonLoading.value = true;
|
||||
|
||||
// 创建提交数据,排除selectedStudent字段,确保包含userId和memberType
|
||||
const { selectedStudent, ...submitData } = form.value;
|
||||
|
||||
// 确保memberType为2(志愿者成员)
|
||||
submitData.memberType = 2;
|
||||
|
||||
// 确保status为2(已加入状态)
|
||||
if (!submitData.status) {
|
||||
submitData.status = '2';
|
||||
}
|
||||
|
||||
if (form.value.id) {
|
||||
await updateVolunteerMember(submitData as VolunteerMemberForm).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await addVolunteerMember(submitData as VolunteerMemberForm).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
proxy?.$modal.msgSuccess('操作成功');
|
||||
dialog.visible = false;
|
||||
await getList();
|
||||
// 通知父组件成员数量变化
|
||||
notifyMemberCountChange();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: VolunteerMemberVO) => {
|
||||
const _ids = row?.id || ids.value;
|
||||
await proxy?.$modal.confirm('是否确认删除选中的志愿者成员?');
|
||||
|
||||
// 处理单个删除或批量删除
|
||||
if (Array.isArray(_ids)) {
|
||||
await delVolunteerMember(_ids.join(','));
|
||||
} else {
|
||||
await delVolunteerMember(_ids);
|
||||
}
|
||||
|
||||
proxy?.$modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
// 通知父组件成员数量变化
|
||||
notifyMemberCountChange();
|
||||
};
|
||||
|
||||
/** 查看按钮操作 */
|
||||
const handleView = async (row: VolunteerMemberVO) => {
|
||||
const res = await getVolunteerMember(row.id);
|
||||
Object.assign(viewForm, res.data);
|
||||
viewDialog.visible = true;
|
||||
viewDialog.title = '查看志愿者成员详情';
|
||||
};
|
||||
|
||||
/** 取消查看对话框 */
|
||||
const cancelView = () => {
|
||||
viewDialog.visible = false;
|
||||
};
|
||||
|
||||
/** 学生选择变化处理 */
|
||||
const handleStudentChange = (student: StudentVO | null) => {
|
||||
if (student) {
|
||||
form.value.userId = student.id; // 设置userId为学生的ID
|
||||
form.value.studentNo = student.studentNo;
|
||||
form.value.studentName = student.name;
|
||||
form.value.gender = student.gender;
|
||||
form.value.department = student.collegeName;
|
||||
form.value.major = student.majorName;
|
||||
form.value.className = student.className;
|
||||
form.value.phone = student.phone;
|
||||
} else {
|
||||
form.value.userId = undefined;
|
||||
form.value.studentNo = '';
|
||||
form.value.studentName = '';
|
||||
form.value.gender = '';
|
||||
form.value.department = '';
|
||||
form.value.major = '';
|
||||
form.value.className = '';
|
||||
form.value.phone = '';
|
||||
}
|
||||
};
|
||||
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
proxy?.download(
|
||||
'stgl/volunteer/member/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`volunteer_member_${new Date().getTime()}.xlsx`
|
||||
);
|
||||
};
|
||||
|
||||
/** 导入模板下载 */
|
||||
const importTemplate = () => {
|
||||
proxy?.download('stgl/volunteer/member/importTemplate', {}, `志愿者成员导入模板_${new Date().getTime()}.xlsx`);
|
||||
};
|
||||
|
||||
/** 导入按钮操作 */
|
||||
const handleImport = () => {
|
||||
upload.title = '志愿者成员导入';
|
||||
upload.open = true;
|
||||
};
|
||||
|
||||
/** 提交上传文件 */
|
||||
const submitFileForm = () => {
|
||||
uploadRef.value?.submit();
|
||||
};
|
||||
|
||||
/** 文件上传中处理 */
|
||||
const handleFileUploadProgress = () => {
|
||||
upload.isUploading = true;
|
||||
};
|
||||
|
||||
/** 文件上传成功处理 */
|
||||
const handleFileSuccess = (response: any) => {
|
||||
upload.open = false;
|
||||
upload.isUploading = false;
|
||||
uploadRef.value?.clearFiles();
|
||||
proxy?.$modal.msgSuccess(response.msg);
|
||||
if (response.msg.includes('成功')) {
|
||||
getList();
|
||||
// 通知父组件成员数量变化
|
||||
notifyMemberCountChange();
|
||||
|
||||
// 检查导入后是否超过成员上限
|
||||
setTimeout(() => {
|
||||
const currentCount = memberList.value.length;
|
||||
if (props.memberLimit && props.memberLimit > 0 && currentCount > props.memberLimit) {
|
||||
proxy?.$modal.msgWarning(`导入完成,但当前成员数量(${currentCount})已超过设定上限(${props.memberLimit}),建议及时调整成员数量`);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
getList
|
||||
});
|
||||
|
||||
// 定义事件,用于通知父组件数据变化
|
||||
const emit = defineEmits<{
|
||||
memberCountChanged: [newCount: number]
|
||||
}>();
|
||||
|
||||
// 通知父组件成员数量变化
|
||||
const notifyMemberCountChange = () => {
|
||||
// 重新获取最新的成员数量
|
||||
getList().then(() => {
|
||||
const newCount = memberList.value.length;
|
||||
console.log('JoinedMembersTab: 成员数量变化,通知父组件:', newCount);
|
||||
emit('memberCountChanged', newCount);
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.view-description {
|
||||
padding: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
min-height: 80px;
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<el-dialog :title="title" :model-value="visible" @update:model-value="handleVisibleChange" width="1200px" append-to-body>
|
||||
<el-tabs v-model="activeTab" type="border-card" :key="teamId" @tab-change="handleTabChange">
|
||||
<!-- 已加入标签页 -->
|
||||
<el-tab-pane label="已加入" name="joined">
|
||||
<JoinedMembersTab
|
||||
ref="joinedTabRef"
|
||||
:team-id="teamId"
|
||||
:team-name="teamName"
|
||||
:member-limit="memberLimit"
|
||||
:current-member-count="currentMemberCount"
|
||||
:key="`joined-${teamId}`"
|
||||
@member-count-changed="handleMemberCountChanged"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 待审核标签页 -->
|
||||
<el-tab-pane label="待审核" name="pending">
|
||||
<PendingMembersTab
|
||||
ref="pendingTabRef"
|
||||
:team-id="teamId"
|
||||
:team-name="teamName"
|
||||
:key="`pending-${teamId}`"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, nextTick } from 'vue';
|
||||
import JoinedMembersTab from './JoinedMembersTab.vue';
|
||||
import PendingMembersTab from './PendingMembersTab.vue';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
title: string;
|
||||
teamId: string | number;
|
||||
teamName: string;
|
||||
memberLimit?: number;
|
||||
currentMemberCount?: number;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const activeTab = ref('joined');
|
||||
const joinedTabRef = ref();
|
||||
const pendingTabRef = ref();
|
||||
|
||||
// 监听visible变化
|
||||
watch(() => props.visible, (newVal) => {
|
||||
if (newVal) {
|
||||
activeTab.value = 'joined';
|
||||
// 当弹窗打开时,触发当前标签页的数据加载
|
||||
nextTick(() => {
|
||||
if (activeTab.value === 'joined' && joinedTabRef.value) {
|
||||
joinedTabRef.value.getList();
|
||||
} else if (activeTab.value === 'pending' && pendingTabRef.value) {
|
||||
pendingTabRef.value.getList();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 监听teamId变化,重置标签页
|
||||
watch(() => props.teamId, (newTeamId) => {
|
||||
console.log('MemberManagementDialog: teamId 变化:', newTeamId);
|
||||
if (newTeamId) {
|
||||
activeTab.value = 'joined';
|
||||
}
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
emit('update:visible', false);
|
||||
};
|
||||
|
||||
const handleVisibleChange = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
/** 处理成员数量变化 */
|
||||
const handleMemberCountChanged = (newCount: number) => {
|
||||
console.log('MemberManagementDialog: 成员数量变化:', newCount);
|
||||
// 这里可以添加额外的处理逻辑,比如更新父组件的数据
|
||||
};
|
||||
|
||||
/** 标签页切换处理 */
|
||||
const handleTabChange = (tabName: string) => {
|
||||
console.log('标签页切换到:', tabName);
|
||||
|
||||
// 根据当前激活的标签页,调用对应组件的刷新方法
|
||||
if (tabName === 'joined' && joinedTabRef.value) {
|
||||
// 调用已加入成员Tab的刷新方法
|
||||
if (typeof joinedTabRef.value.getList === 'function') {
|
||||
joinedTabRef.value.getList();
|
||||
}
|
||||
} else if (tabName === 'pending' && pendingTabRef.value) {
|
||||
// 调用待审核成员Tab的刷新方法
|
||||
if (typeof pendingTabRef.value.getList === 'function') {
|
||||
pendingTabRef.value.getList();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,555 @@
|
||||
<template>
|
||||
<div class="pending-members-tab">
|
||||
<!-- 搜索区域 -->
|
||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||||
<div v-show="showSearch" class="mb-[10px]">
|
||||
<el-card shadow="always" style="padding: 8px 16px;">
|
||||
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0;">
|
||||
<el-row :gutter="16" align="middle">
|
||||
<el-col :span="5">
|
||||
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0;">
|
||||
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0;">
|
||||
<el-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>
|
||||
<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">
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" plain icon="Select" :disabled="multiple" @click="handleBatchApprove" v-hasPermi="['stgl:volunteermember:edit']">批量通过</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Close" :disabled="multiple" @click="handleBatchReject" v-hasPermi="['stgl:volunteermember:edit']">批量拒绝</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border :data="memberList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="序号" type="index" width="55" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" align="center" prop="status" width="80">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="stgl_member_status" :value="scope.row.status" v-if="scope.row.status !== undefined" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="学号" align="center" prop="studentNo" width="120" />
|
||||
<el-table-column label="姓名" align="center" prop="studentName" width="80" />
|
||||
<el-table-column label="性别" align="center" prop="gender" width="60">
|
||||
<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="联系方式" align="center" prop="phone" width="120" />
|
||||
<el-table-column label="院系" align="center" prop="department" width="120" />
|
||||
<el-table-column label="专业" align="center" prop="major" width="180" />
|
||||
<el-table-column label="班级" align="center" prop="className" width="120" />
|
||||
<el-table-column label="申请时间" align="center" prop="createTime" width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<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:volunteermember:query']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="通过" placement="top">
|
||||
<el-button link type="success" icon="Select" @click="handleApprove(scope.row)" v-hasPermi="['stgl:volunteermember:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="拒绝" placement="top">
|
||||
<el-button link type="danger" icon="Close" @click="handleReject(scope.row)" v-hasPermi="['stgl:volunteermember:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
|
||||
<!-- 查看成员详情对话框 -->
|
||||
<el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="800px" append-to-body>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="学号">{{ viewForm.studentNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ viewForm.studentName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
<dict-tag :options="sys_user_sex" :value="viewForm.gender" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="院系">{{ viewForm.department }}</el-descriptions-item>
|
||||
<el-descriptions-item label="专业">{{ viewForm.major }}</el-descriptions-item>
|
||||
<el-descriptions-item label="班级">{{ viewForm.className }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系方式">{{ viewForm.phone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="邮箱">{{ viewForm.email }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<dict-tag :options="stgl_member_status" :value="viewForm.status" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="申请时间">{{ parseTime(viewForm.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-divider content-position="left">备注信息</el-divider>
|
||||
<div class="view-description">
|
||||
{{ viewForm.remark || '暂无备注' }}
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelView">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getCurrentInstance, onMounted, reactive, ref, toRefs } from 'vue';
|
||||
import {
|
||||
listVolunteerMember,
|
||||
getVolunteerMember,
|
||||
updateVolunteerMember
|
||||
} from '@/api/stgl/volunteermember';
|
||||
import { VolunteerMemberVO, VolunteerMemberQuery } from '@/api/stgl/volunteermember/types';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
|
||||
interface Props {
|
||||
teamId: string | number;
|
||||
teamName: string;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
const { sys_user_sex, sys_normal_disable, stgl_member_status } = toRefs<any>(proxy?.useDict('sys_user_sex', 'sys_normal_disable', 'stgl_member_status'));
|
||||
|
||||
const memberList = ref<VolunteerMemberVO[]>([]);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref<Array<string | number>>([]);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
|
||||
const viewDialog = reactive<DialogOption>({
|
||||
visible: false,
|
||||
title: ''
|
||||
});
|
||||
|
||||
const viewForm = reactive<VolunteerMemberVO>({
|
||||
id: undefined,
|
||||
relationId: undefined,
|
||||
studentNo: '',
|
||||
studentName: '',
|
||||
gender: '',
|
||||
department: '',
|
||||
major: '',
|
||||
className: '',
|
||||
phone: '',
|
||||
email: '',
|
||||
roleType: '',
|
||||
joinTime: '',
|
||||
status: '1', // 待审核状态
|
||||
remark: '',
|
||||
createTime: ''
|
||||
});
|
||||
|
||||
const data = reactive<PageData<any, VolunteerMemberQuery>>({
|
||||
form: {},
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
relationId: props.teamId,
|
||||
status: undefined, // 初始为空,让getList方法处理多状态查询
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
phone: undefined
|
||||
},
|
||||
rules: {}
|
||||
});
|
||||
|
||||
const { queryParams } = toRefs(data);
|
||||
|
||||
/** 验证成员数据完整性 */
|
||||
const validateMemberData = (member: VolunteerMemberVO, operation: string): boolean => {
|
||||
if (!member.userId) {
|
||||
proxy?.$modal.msgError(`${operation}失败:用户ID不能为空`);
|
||||
console.error(`${operation}失败 - 用户ID缺失:`, member);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!member.relationId && !props.teamId) {
|
||||
proxy?.$modal.msgError(`${operation}失败:关联ID(志愿者队伍ID)不能为空`);
|
||||
console.error(`${operation}失败 - 关联ID缺失:`, member);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!member.studentNo || !member.studentName) {
|
||||
proxy?.$modal.msgError(`${operation}失败:学号和姓名不能为空`);
|
||||
console.error(`${operation}失败 - 基础信息缺失:`, member);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/** 查询成员列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
// 构建查询参数,支持多状态查询
|
||||
const queryData = { ...queryParams.value };
|
||||
|
||||
console.log('PendingMembersTab: getList 开始查询,参数:', queryData);
|
||||
|
||||
// 如果状态字段为空或未选择,则查询待审核(status=0)和已驳回(status=3)的成员
|
||||
if (!queryData.status) {
|
||||
// 使用params传递多状态查询
|
||||
queryData.params = { statusList: ['0', '3'] } as any;
|
||||
delete queryData.status; // 删除单个status字段
|
||||
}
|
||||
|
||||
console.log('PendingMembersTab: 最终查询参数:', queryData);
|
||||
|
||||
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);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('PendingMembersTab: 查询成员列表失败:', error);
|
||||
proxy?.$modal.msgError('查询成员列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value?.resetFields();
|
||||
queryParams.value = {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
relationId: props.teamId,
|
||||
status: undefined, // 重置为空,让getList方法处理多状态查询
|
||||
studentName: undefined,
|
||||
studentNo: undefined,
|
||||
phone: undefined
|
||||
};
|
||||
handleQuery();
|
||||
};
|
||||
|
||||
/** 多选框选中数据 */
|
||||
const handleSelectionChange = (selection: VolunteerMemberVO[]) => {
|
||||
ids.value = selection.map((item) => item.id);
|
||||
multiple.value = !selection.length;
|
||||
};
|
||||
|
||||
/** 查看按钮操作 */
|
||||
const handleView = async (row: VolunteerMemberVO) => {
|
||||
const res = await getVolunteerMember(row.id);
|
||||
Object.assign(viewForm, res.data);
|
||||
viewDialog.visible = true;
|
||||
viewDialog.title = '查看志愿者成员申请详情';
|
||||
};
|
||||
|
||||
/** 取消查看对话框 */
|
||||
const cancelView = () => {
|
||||
viewDialog.visible = false;
|
||||
};
|
||||
|
||||
/** 通过申请 */
|
||||
const handleApprove = async (row: VolunteerMemberVO) => {
|
||||
// 验证数据完整性
|
||||
if (!validateMemberData(row, '审核通过')) {
|
||||
return;
|
||||
}
|
||||
|
||||
await proxy?.$modal.confirm(`确认通过"${row.studentName}"的入队申请?`);
|
||||
|
||||
// 构建完整的更新数据,包含所有必要字段
|
||||
const updateData = {
|
||||
id: row.id,
|
||||
userId: row.userId, // 添加用户ID字段
|
||||
memberType: 2, // 志愿者成员类型(必需字段)
|
||||
relationId: row.relationId || props.teamId, // 优先使用行数据中的relationId,否则使用props中的teamId
|
||||
studentNo: row.studentNo,
|
||||
studentName: row.studentName,
|
||||
gender: row.gender,
|
||||
department: row.department,
|
||||
major: row.major,
|
||||
className: row.className,
|
||||
phone: row.phone,
|
||||
email: row.email,
|
||||
roleType: row.roleType || '队员', // 默认为队员
|
||||
status: '2', // 设置为已加入状态
|
||||
joinTime: new Date().toISOString().split('T')[0], // 设置加入时间为当前日期
|
||||
remark: row.remark
|
||||
};
|
||||
|
||||
console.log('审核通过 - 更新数据:', updateData);
|
||||
|
||||
await updateVolunteerMember(updateData as any);
|
||||
proxy?.$modal.msgSuccess('审核通过成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 拒绝申请 */
|
||||
const handleReject = async (row: VolunteerMemberVO) => {
|
||||
// 验证数据完整性
|
||||
if (!validateMemberData(row, '审核拒绝')) {
|
||||
return;
|
||||
}
|
||||
|
||||
await proxy?.$modal.confirm(`确认拒绝"${row.studentName}"的入队申请?`);
|
||||
|
||||
// 构建完整的更新数据,包含所有必要字段
|
||||
const updateData = {
|
||||
id: row.id,
|
||||
userId: row.userId, // 添加用户ID字段
|
||||
memberType: 2, // 志愿者成员类型(必需字段)
|
||||
relationId: row.relationId || props.teamId, // 优先使用行数据中的relationId,否则使用props中的teamId
|
||||
studentNo: row.studentNo,
|
||||
studentName: row.studentName,
|
||||
gender: row.gender,
|
||||
department: row.department,
|
||||
major: row.major,
|
||||
className: row.className,
|
||||
phone: row.phone,
|
||||
email: row.email,
|
||||
roleType: '', // 拒绝审核后没有角色
|
||||
status: '3', // 设置为已驳回状态
|
||||
remark: row.remark
|
||||
};
|
||||
|
||||
console.log('审核拒绝 - 更新数据:', updateData);
|
||||
|
||||
await updateVolunteerMember(updateData as any);
|
||||
proxy?.$modal.msgSuccess('审核拒绝成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 批量通过 */
|
||||
const handleBatchApprove = async () => {
|
||||
if (ids.value.length === 0) {
|
||||
proxy?.$modal.msgWarning('请选择要审核的成员');
|
||||
return;
|
||||
}
|
||||
|
||||
await proxy?.$modal.confirm(`确认批量通过所选成员的入队申请?`);
|
||||
|
||||
const promises = ids.value.map(id => {
|
||||
// 根据ID找到对应的成员记录,获取relationId
|
||||
const member = memberList.value.find(m => m.id === id);
|
||||
if (!member) {
|
||||
proxy?.$modal.msgError(`未找到ID为${id}的成员记录`);
|
||||
return Promise.reject(new Error(`未找到ID为${id}的成员记录`));
|
||||
}
|
||||
|
||||
// 验证数据完整性
|
||||
if (!validateMemberData(member, '批量审核通过')) {
|
||||
return Promise.reject(new Error(`成员${member.studentName}数据不完整`));
|
||||
}
|
||||
|
||||
// 构建完整的更新数据,包含所有必要字段
|
||||
const updateData = {
|
||||
id: id,
|
||||
userId: member.userId, // 添加用户ID字段
|
||||
memberType: 2, // 志愿者成员类型(必需字段)
|
||||
relationId: member.relationId || props.teamId, // 优先使用行数据中的relationId,否则使用props中的teamId
|
||||
studentNo: member.studentNo,
|
||||
studentName: member.studentName,
|
||||
gender: member.gender,
|
||||
department: member.department,
|
||||
major: member.major,
|
||||
className: member.className,
|
||||
phone: member.phone,
|
||||
email: member.email,
|
||||
roleType: member.roleType || '队员', // 默认为队员
|
||||
status: '2', // 设置为已加入状态
|
||||
joinTime: new Date().toISOString().split('T')[0],
|
||||
remark: member.remark
|
||||
};
|
||||
|
||||
console.log(`批量审核通过 - ID ${id} 更新数据:`, updateData);
|
||||
|
||||
return updateVolunteerMember(updateData as any);
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
proxy?.$modal.msgSuccess('批量审核通过成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
/** 批量拒绝 */
|
||||
const handleBatchReject = async () => {
|
||||
if (ids.value.length === 0) {
|
||||
proxy?.$modal.msgWarning('请选择要审核的成员');
|
||||
return;
|
||||
}
|
||||
|
||||
await proxy?.$modal.confirm(`确认批量拒绝所选成员的入队申请?`);
|
||||
|
||||
const promises = ids.value.map(id => {
|
||||
// 根据ID找到对应的成员记录,获取relationId
|
||||
const member = memberList.value.find(m => m.id === id);
|
||||
if (!member) {
|
||||
proxy?.$modal.msgError(`未找到ID为${id}的成员记录`);
|
||||
return Promise.reject(new Error(`未找到ID为${id}的成员记录`));
|
||||
}
|
||||
|
||||
// 验证数据完整性
|
||||
if (!validateMemberData(member, '批量审核拒绝')) {
|
||||
return Promise.reject(new Error(`成员${member.studentName}数据不完整`));
|
||||
}
|
||||
|
||||
// 构建完整的更新数据,包含所有必要字段
|
||||
const updateData = {
|
||||
id: id,
|
||||
userId: member.userId, // 添加用户ID字段
|
||||
memberType: 2, // 志愿者成员类型(必需字段)
|
||||
relationId: member.relationId || props.teamId, // 优先使用行数据中的relationId,否则使用props中的teamId
|
||||
studentNo: member.studentNo,
|
||||
studentName: member.studentName,
|
||||
gender: member.gender,
|
||||
department: member.department,
|
||||
major: member.major,
|
||||
className: member.className,
|
||||
phone: member.phone,
|
||||
email: member.email,
|
||||
roleType: '', // 拒绝审核后没有角色
|
||||
status: '3', // 设置为已驳回状态
|
||||
remark: member.remark
|
||||
};
|
||||
|
||||
console.log(`批量审核拒绝 - ID ${id} 更新数据:`, updateData);
|
||||
|
||||
return updateVolunteerMember(updateData as any);
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
proxy?.$modal.msgSuccess('批量审核拒绝成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
// 暴露给父组件的方法
|
||||
defineExpose({
|
||||
getList
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.view-description {
|
||||
padding: 12px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
min-height: 80px;
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* 表格固定列样式优化 */
|
||||
.pending-members-tab .el-table {
|
||||
/* 移除固定最小宽度,让表格自适应容器 */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pending-members-tab .el-table__fixed-right {
|
||||
/* 固定右侧列的样式 */
|
||||
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.pending-members-tab .el-table__fixed-right::before {
|
||||
/* 固定列左侧边框 */
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
background-color: #ebeef5;
|
||||
}
|
||||
|
||||
/* 操作按钮组样式 */
|
||||
.pending-members-tab .el-button + .el-button {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
/* 操作列按钮样式,与JoinedMembersTab保持一致 */
|
||||
.pending-members-tab .el-table .el-button {
|
||||
padding: 4px 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.pending-members-tab .el-table .el-button--link {
|
||||
height: 28px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 确保固定列在滚动时保持可见 */
|
||||
.pending-members-tab .el-table__body-wrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* 响应式表格布局 */
|
||||
.pending-members-tab .el-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pending-members-tab .el-table {
|
||||
/* 确保表格在卡片内正常显示 */
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* 在小屏幕下优化列宽 */
|
||||
@media (max-width: 1400px) {
|
||||
.pending-members-tab .el-table .el-table__column {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user