wuzhen update

This commit is contained in:
wuzhen committed 2025-09-07 18:17:50 +08:00
1 parent edaf8dbacf
commit 48da578546
86 files changed
+2101 -1849

No files matched your search

@@ -38,9 +38,15 @@
<!-- 主办方信息区域 -->
<el-divider content-position="left">主办方信息</el-divider>
<el-descriptions :column="3" border class="organizer-info-section custom-width-organizer">
<el-descriptions-item label="主办方名称" :label-style="{ width: '13%' }" :content-style="{ width: '35%' }">{{ activity.organizerName || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="联系人" :label-style="{ width: '10%' }" :content-style="{ width: '15%' }">{{ activity.organizerContact || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="联系电话" :label-style="{ width: '10%' }" :content-style="{ width: '17%' }">{{ activity.organizerPhone || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="主办方名称" :label-style="{ width: '13%' }" :content-style="{ width: '35%' }">{{
activity.organizerName || '暂无'
}}</el-descriptions-item>
<el-descriptions-item label="联系人" :label-style="{ width: '10%' }" :content-style="{ width: '15%' }">{{
activity.organizerContact || '暂无'
}}</el-descriptions-item>
<el-descriptions-item label="联系电话" :label-style="{ width: '10%' }" :content-style="{ width: '17%' }">{{
activity.organizerPhone || '暂无'
}}</el-descriptions-item>
</el-descriptions>
<!-- 承办方信息区域 -->
@@ -48,9 +54,15 @@
<div v-if="coOrganizerList.length > 0">
<div v-for="(organizer, index) in coOrganizerList" :key="index" class="co-organizer-item custom-width-co-organizer">
<el-descriptions :column="3" border>
<el-descriptions-item label="承办方名称" :label-style="{ width: '12%' }" :content-style="{ width: '36%' }">{{ organizer.name || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="联系人" :label-style="{ width: '10%' }" :content-style="{ width: '16%' }">{{ organizer.contact || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="联系电话" :label-style="{ width: '10%' }" :content-style="{ width: '16%' }">{{ organizer.phone || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="承办方名称" :label-style="{ width: '12%' }" :content-style="{ width: '36%' }">{{
organizer.name || '暂无'
}}</el-descriptions-item>
<el-descriptions-item label="联系人" :label-style="{ width: '10%' }" :content-style="{ width: '16%' }">{{
organizer.contact || '暂无'
}}</el-descriptions-item>
<el-descriptions-item label="联系电话" :label-style="{ width: '10%' }" :content-style="{ width: '16%' }">{{
organizer.phone || '暂无'
}}</el-descriptions-item>
</el-descriptions>
</div>
</div>
@@ -61,10 +73,18 @@
<div v-if="instructorList.length > 0">
<div v-for="(instructor, index) in instructorList" :key="index" class="instructor-item custom-width-instructor">
<el-descriptions :column="4" border size="small">
<el-descriptions-item label="姓名" :label-style="{ width: '8%' }" :content-style="{ width: '15%' }">{{ instructor.name || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="部门" :label-style="{ width: '8%' }" :content-style="{ width: '17%' }">{{ instructor.dept || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="电话" :label-style="{ width: '8%' }" :content-style="{ width: '15%' }">{{ instructor.phone || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="邮箱" :label-style="{ width: '8%' }" :content-style="{ width: '17%' }">{{ instructor.email || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="姓名" :label-style="{ width: '8%' }" :content-style="{ width: '15%' }">{{
instructor.name || '暂无'
}}</el-descriptions-item>
<el-descriptions-item label="部门" :label-style="{ width: '8%' }" :content-style="{ width: '17%' }">{{
instructor.dept || '暂无'
}}</el-descriptions-item>
<el-descriptions-item label="电话" :label-style="{ width: '8%' }" :content-style="{ width: '15%' }">{{
instructor.phone || '暂无'
}}</el-descriptions-item>
<el-descriptions-item label="邮箱" :label-style="{ width: '8%' }" :content-style="{ width: '17%' }">{{
instructor.email || '暂无'
}}</el-descriptions-item>
</el-descriptions>
</div>
</div>
@@ -203,11 +223,16 @@ const props = defineProps<Props>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { stgl_activity_type, stgl_activity_status, stgl_registration_type, stgl_registration_status, stgl_activity_range, sys_yes_no } = toRefs<any>(
proxy?.useDict('stgl_activity_type', 'stgl_activity_status', 'stgl_registration_type', 'stgl_registration_status', 'stgl_activity_range', 'sys_yes_no')
proxy?.useDict(
'stgl_activity_type',
'stgl_activity_status',
'stgl_registration_type',
'stgl_registration_status',
'stgl_activity_range',
'sys_yes_no'
)
);
// 当前激活的Tab页签
const activeTab = ref('basic');
@@ -585,25 +610,25 @@ onMounted(() => {
.el-descriptions__header {
margin-bottom: 20px;
}
.el-descriptions__title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.el-descriptions__body {
.el-descriptions__table {
.el-descriptions__cell {
padding: 12px 16px;
}
.el-descriptions__label {
font-weight: 500;
color: #606266;
background-color: #fafafa;
}
.el-descriptions__content {
color: #303133;
}
@@ -624,7 +649,7 @@ onMounted(() => {
.activity-detail-view {
padding: 12px;
}
:deep(.el-descriptions) {
.el-descriptions__body {
.el-descriptions__table {
@@ -634,7 +659,7 @@ onMounted(() => {
}
}
}
.co-organizer-item,
.instructor-item {
padding: 12px;
@@ -3,24 +3,15 @@
<el-tabs v-model="activeTab" type="border-card" @tab-change="handleTabChange">
<!-- 已报名标签页 -->
<el-tab-pane label="已报名" name="registered">
<RegisteredMembersTab
:activity-id="activityId"
:activity-name="activityName"
ref="registeredTabRef"
/>
<RegisteredMembersTab :activity-id="activityId" :activity-name="activityName" ref="registeredTabRef" />
</el-tab-pane>
<!-- 待审核标签页 -->
<el-tab-pane label="待审核" name="pending">
<PendingMembersTab
:activity-id="activityId"
:activity-name="activityName"
ref="pendingTabRef"
@refresh-other-tabs="handleRefreshOtherTabs"
/>
<PendingMembersTab :activity-id="activityId" :activity-name="activityName" ref="pendingTabRef" @refresh-other-tabs="handleRefreshOtherTabs" />
</el-tab-pane>
</el-tabs>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">关 闭</el-button>
@@ -57,18 +48,21 @@ const handleVisibleChange = (value: boolean) => {
};
// 监听弹窗显示状态,当弹窗打开时刷新数据
watch(() => props.visible, (newVal) => {
if (newVal) {
// 弹窗打开时刷新当前标签页数据
nextTick(() => {
if (activeTab.value === 'registered' && registeredTabRef.value) {
registeredTabRef.value.getList();
} else if (activeTab.value === 'pending' && pendingTabRef.value) {
pendingTabRef.value.getList();
}
});
watch(
() => props.visible,
(newVal) => {
if (newVal) {
// 弹窗打开时刷新当前标签页数据
nextTick(() => {
if (activeTab.value === 'registered' && registeredTabRef.value) {
registeredTabRef.value.getList();
} else if (activeTab.value === 'pending' && pendingTabRef.value) {
pendingTabRef.value.getList();
}
});
}
}
});
);
// 标签页切换时刷新数据
const handleTabChange = (tabName: string) => {
@@ -127,4 +121,4 @@ defineExpose({
:deep(.el-tabs__content) {
padding: 20px 0;
}
</style>
</style>
@@ -6,7 +6,7 @@
<div v-else>
<!-- 移除统计信息部分 -->
<el-table :data="ratings" height="600" border stripe>
<!-- 基本信息 - 减少宽度 -->
<el-table-column label="评分人信息" width="160" fixed="left">
@@ -18,7 +18,7 @@
</div>
</template>
</el-table-column>
<!-- 所属信息 - 减少宽度 -->
<el-table-column label="所属信息" width="140">
<template #default="scope">
@@ -117,19 +117,23 @@ const averageRating = computed(() => {
// 计算最高评分
const maxRating = computed(() => {
if (ratings.value.length === 0) return 0;
return Math.max(...ratings.value.map((rating) => {
const score = typeof rating.ratingScore === 'string' ? parseFloat(rating.ratingScore) : rating.ratingScore;
return isNaN(score) ? 0 : score;
}));
return Math.max(
...ratings.value.map((rating) => {
const score = typeof rating.ratingScore === 'string' ? parseFloat(rating.ratingScore) : rating.ratingScore;
return isNaN(score) ? 0 : score;
})
);
});
// 计算最低评分
const minRating = computed(() => {
if (ratings.value.length === 0) return 0;
return Math.min(...ratings.value.map((rating) => {
const score = typeof rating.ratingScore === 'string' ? parseFloat(rating.ratingScore) : rating.ratingScore;
return isNaN(score) ? 0 : score;
}));
return Math.min(
...ratings.value.map((rating) => {
const score = typeof rating.ratingScore === 'string' ? parseFloat(rating.ratingScore) : rating.ratingScore;
return isNaN(score) ? 0 : score;
})
);
});
// 简化的日期时间格式化
@@ -171,12 +175,16 @@ const loadRatings = async () => {
};
// 监听 activityId 变化,自动重新加载评分数据
watch(() => props.activityId, (newId, oldId) => {
console.log('活动ID发生变化:', oldId, '->', newId);
if (newId && newId !== oldId) {
loadRatings();
}
}, { immediate: true }); // immediate: true 确保组件挂载时也会执行一次
watch(
() => props.activityId,
(newId, oldId) => {
console.log('活动ID发生变化:', oldId, '->', newId);
if (newId && newId !== oldId) {
loadRatings();
}
},
{ immediate: true }
); // immediate: true 确保组件挂载时也会执行一次
// 移除 onMounted,因为 watch 的 immediate: true 已经覆盖了这个功能
</script>
@@ -3,26 +3,26 @@
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="always" style="padding: 8px 16px;">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0;">
<el-card shadow="always" style="padding: 8px 16px">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0">
<el-row :gutter="16" align="middle">
<el-col :span="5">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0;">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0">
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0;">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="联系方式" prop="phone" style="margin-bottom: 0;">
<el-form-item label="联系方式" prop="phone" style="margin-bottom: 0">
<el-input v-model="queryParams.phone" placeholder="请输入联系方式" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -94,13 +94,7 @@
</el-table-column>
<el-table-column label="签到时间" align="center" width="120">
<template #default="scope">
<el-button
v-if="scope.row.latestSignInTime"
link
type="primary"
@click="showSignRecords(scope.row)"
class="time-button"
>
<el-button v-if="scope.row.latestSignInTime" link type="primary" @click="showSignRecords(scope.row)" class="time-button">
{{ formatSignTime(scope.row.latestSignInTime) }}
</el-button>
<span v-else class="text-gray-400">未签到</span>
@@ -108,13 +102,7 @@
</el-table-column>
<el-table-column label="签退时间" align="center" width="120">
<template #default="scope">
<el-button
v-if="scope.row.latestSignOutTime"
link
type="primary"
@click="showSignRecords(scope.row)"
class="time-button"
>
<el-button v-if="scope.row.latestSignOutTime" link type="primary" @click="showSignRecords(scope.row)" class="time-button">
{{ formatSignTime(scope.row.latestSignOutTime) }}
</el-button>
<span v-else class="text-gray-400">未签退</span>
@@ -309,7 +297,7 @@
是否更新已经存在的报名数据
</div>
<span>仅允许导入xls、xlsx格式文件。</span>
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
</div>
</template>
</el-upload>
@@ -493,15 +481,15 @@ const getList = async () => {
try {
// 构建查询参数,查询已审核通过的报名记录
const queryData = { ...queryParams.value };
// 如果状态字段为空或未选择,则查询已通过(approvalStatus=1)的报名记录
if (!queryData.approvalStatus) {
queryData.approvalStatus = 1;
}
console.log('ActivityRegistrationView: getList 开始查询,参数:', queryData);
console.log('ActivityRegistrationView: props.activityId:', props.activityId);
const res = await listActivityRegister(queryData);
console.log('ActivityRegistrationView: 查询结果完整对象:', res);
console.log('ActivityRegistrationView: res 的类型:', typeof res);
@@ -510,7 +498,7 @@ const getList = async () => {
console.log('ActivityRegistrationView: res.rows 的类型:', typeof res.rows);
console.log('ActivityRegistrationView: res.total:', res.total);
console.log('ActivityRegistrationView: res 的所有属性:', Object.keys(res || {}));
// 检查 res.rows 是否存在且为数组
if (!res || !res.rows) {
console.error('ActivityRegistrationView: API返回数据异常 - res.rows 不存在');
@@ -520,7 +508,7 @@ const getList = async () => {
total.value = 0;
return;
}
if (!Array.isArray(res.rows)) {
console.error('ActivityRegistrationView: API返回数据异常 - res.rows 不是数组');
console.error('ActivityRegistrationView: res.rows 实际值:', res.rows);
@@ -530,9 +518,9 @@ const getList = async () => {
total.value = 0;
return;
}
console.log('ActivityRegistrationView: res.rows 数组长度:', res.rows.length);
// 为每个成员添加签到信息
const membersWithSignInfo = await Promise.all(
res.rows.map(async (member) => {
@@ -546,17 +534,17 @@ const getList = async () => {
studentNo: member.studentNo
}
});
if (signRes.code === 200 && signRes.data && signRes.data.length > 0) {
// 获取最新的签到和签退时间
const latestSignIn = signRes.data
.filter((record: any) => record.signInTime)
.sort((a: any, b: any) => new Date(b.signInTime).getTime() - new Date(a.signInTime).getTime())[0];
const latestSignOut = signRes.data
.filter((record: any) => record.signOutTime)
.sort((a: any, b: any) => new Date(b.signOutTime).getTime() - new Date(a.signOutTime).getTime())[0];
return {
...member,
latestSignInTime: latestSignIn?.signInTime,
@@ -566,7 +554,7 @@ const getList = async () => {
} catch (error) {
console.warn('获取签到信息失败:', error);
}
return {
...member,
latestSignInTime: undefined,
@@ -574,7 +562,7 @@ const getList = async () => {
};
})
);
memberList.value = membersWithSignInfo;
total.value = res.total;
} catch (error: any) {
@@ -586,7 +574,7 @@ const getList = async () => {
request: error?.request,
config: error?.config
});
// 检查是否是网络错误
if (error?.code === 'NETWORK_ERROR' || error?.code === 'ECONNABORTED') {
proxy?.$modal.msgError('网络连接失败,请检查网络状态');
@@ -603,7 +591,7 @@ const getList = async () => {
// 其他错误(如代码逻辑错误)
proxy?.$modal.msgError(`查询成员列表失败: ${error?.message || '未知错误'}`);
}
// 确保在错误情况下清空数据
memberList.value = [];
total.value = 0;
@@ -617,7 +605,7 @@ const showSignRecords = async (member: ExtendedActivityRegisterVO) => {
signRecordsDialog.visible = true;
signRecordsDialog.title = `${member.studentName}(${member.studentNo})的签到记录`;
signRecordsLoading.value = true;
try {
const res = await request({
url: '/stgl/activityAttendance/getAllByActivityAndUser',
@@ -627,7 +615,7 @@ const showSignRecords = async (member: ExtendedActivityRegisterVO) => {
studentNo: member.studentNo
}
});
if (res.code === 200) {
signRecords.value = res.data || [];
} else {
@@ -708,7 +696,7 @@ const handleUpdate = async (row?: ExtendedActivityRegisterVO) => {
const _id = row?.id || ids.value[0];
const res = await getActivityRegister(_id);
Object.assign(form.value, res.data);
// 设置学生选择器数据
if (res.data.studentName) {
form.value.selectedStudent = {
@@ -722,7 +710,7 @@ const handleUpdate = async (row?: ExtendedActivityRegisterVO) => {
className: res.data.className || ''
} as StudentVO;
}
dialog.visible = true;
dialog.title = '修改活动报名';
};
@@ -732,14 +720,14 @@ const submitForm = () => {
memberFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
// 创建提交数据,排除selectedStudent字段
const { selectedStudent, ...submitData } = form.value;
// 确保包含必要字段
submitData.activityId = props.activityId;
submitData.approvalStatus = 1; // 新增的报名默认为已通过状态
try {
if (form.value.id) {
await updateActivityRegister(submitData as ActivityRegisterForm);
@@ -762,14 +750,14 @@ const submitForm = () => {
const handleDelete = async (row?: ExtendedActivityRegisterVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除选中的活动报名记录?');
try {
if (Array.isArray(_ids)) {
await delActivityRegister(_ids.join(','));
} else {
await delActivityRegister(_ids);
}
proxy?.$modal.msgSuccess('删除成功');
await getList();
} catch (error) {
@@ -911,4 +899,4 @@ onMounted(() => {
.text-gray-400 {
color: #909399;
}
</style>
</style>
@@ -3,21 +3,21 @@
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="always" style="padding: 8px 16px;">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0;">
<el-card shadow="always" style="padding: 8px 16px">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0">
<el-row :gutter="16" align="middle">
<el-col :span="8">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0;">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0">
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0;">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -33,10 +33,14 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="success" plain icon="Check" :disabled="single" @click="handleApprove()" v-hasPermi="['stgl:activityRegister:approve']">批量通过</el-button>
<el-button type="success" plain icon="Check" :disabled="single" @click="handleApprove()" v-hasPermi="['stgl:activityRegister:approve']"
>批量通过</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Close" :disabled="single" @click="handleReject()" v-hasPermi="['stgl:activityRegister:approve']">批量拒绝</el-button>
<el-button type="danger" plain icon="Close" :disabled="single" @click="handleReject()" v-hasPermi="['stgl:activityRegister:approve']"
>批量拒绝</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
@@ -86,9 +90,15 @@
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['stgl:activityRegister:query']"></el-button>
</el-tooltip>
<el-tooltip content="通过" placement="top">
<el-button link type="success" icon="Check" @click="handleApprove(scope.row)" v-hasPermi="['stgl:activityRegister:approve']"></el-button>
</el-tooltip>
<el-tooltip content="拒绝" placement="top">
<el-button
link
type="success"
icon="Check"
@click="handleApprove(scope.row)"
v-hasPermi="['stgl:activityRegister:approve']"
></el-button>
</el-tooltip>
<el-tooltip content="拒绝" placement="top">
<el-button link type="danger" icon="Close" @click="handleReject(scope.row)" v-hasPermi="['stgl:activityRegister:approve']"></el-button>
</el-tooltip>
</template>
@@ -123,7 +133,7 @@
<el-divider content-position="left">
<span class="text-lg font-medium">团队信息</span>
</el-divider>
<el-descriptions :column="2" border class="mb-4">
<el-descriptions-item label="团队名称">{{ teamInfo.teamName }}</el-descriptions-item>
<el-descriptions-item label="团队人数">{{ teamInfo.teamSize || 0 }}人</el-descriptions-item>
@@ -160,7 +170,7 @@
</el-table>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelView">关 闭</el-button>
@@ -178,9 +188,9 @@
</el-radio-group>
</el-form-item>
<el-form-item label="审核意见" prop="approvalRemark">
<el-input
v-model="approvalForm.approvalRemark"
type="textarea"
<el-input
v-model="approvalForm.approvalRemark"
type="textarea"
:rows="4"
:placeholder="approvalForm.approvalStatus === 1 ? '请输入通过意见(可选)' : '请输入拒绝原因(必填)'"
/>
@@ -299,9 +309,9 @@ const approvalForm = reactive({
const approvalRules = reactive({
approvalStatus: [{ required: true, message: '请选择审核结果', trigger: 'change' }],
approvalRemark: [
{
required: true,
message: '请输入拒绝原因',
{
required: true,
message: '请输入拒绝原因',
trigger: 'blur',
validator: (rule: any, value: any, callback: any) => {
if (approvalForm.approvalStatus === 2 && !value) {
@@ -332,15 +342,15 @@ const { queryParams } = toRefs(data);
/** 查询成员列表 */
const getList = async () => {
loading.value = true;
try {
const queryData = { ...queryParams.value };
// 确保查询待审核状态(approvalStatus='0')
queryData.approvalStatus = '0';
const res = await listActivityRegister(queryData);
memberList.value = res.rows;
total.value = res.total;
} catch (error) {
@@ -385,7 +395,7 @@ const handleApprove = (row?: ExtendedStglActivityRegistrationVo) => {
proxy?.$modal.msgError('请选择要审核的记录');
return;
}
approvalForm.ids = _ids;
approvalForm.approvalStatus = 1;
approvalForm.approvalRemark = '';
@@ -400,7 +410,7 @@ const handleReject = (row?: ExtendedStglActivityRegistrationVo) => {
proxy?.$modal.msgError('请选择要审核的记录');
return;
}
approvalForm.ids = _ids;
approvalForm.approvalStatus = 2;
approvalForm.approvalRemark = '';
@@ -413,29 +423,21 @@ const submitApproval = () => {
approvalFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
try {
if (approvalForm.ids.length === 1) {
// 单个审核
await approveActivityRegister(
approvalForm.ids[0],
approvalForm.approvalStatus,
approvalForm.approvalRemark
);
await approveActivityRegister(approvalForm.ids[0], approvalForm.approvalStatus, approvalForm.approvalRemark);
} else {
// 批量审核
await batchApproveActivityRegister(
approvalForm.ids,
approvalForm.approvalStatus,
approvalForm.approvalRemark
);
await batchApproveActivityRegister(approvalForm.ids, approvalForm.approvalStatus, approvalForm.approvalRemark);
}
const actionText = approvalForm.approvalStatus === 1 ? '通过' : '拒绝';
proxy?.$modal.msgSuccess(`批量${actionText}成功`);
approvalDialog.visible = false;
await getList();
// 通知父组件刷新其他标签页
notifyRefreshOtherTabs();
} catch (error) {
@@ -457,14 +459,14 @@ const cancelApproval = () => {
const handleDelete = async (row?: ExtendedStglActivityRegistrationVo) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除选中的活动报名记录?');
try {
if (Array.isArray(_ids)) {
await delActivityRegister(_ids.join(','));
} else {
await delActivityRegister(_ids);
}
proxy?.$modal.msgSuccess('删除成功');
await getList();
} catch (error) {
@@ -478,7 +480,7 @@ const handleView = async (row: ExtendedStglActivityRegistrationVo) => {
Object.assign(viewForm, res.data);
viewDialog.visible = true;
viewDialog.title = '查看活动报名详情';
// 如果是团队报名,获取团队信息
if (Number(viewForm.registrationType) === 2 && viewForm.teamId) {
await loadTeamInfo(viewForm.teamId);
@@ -494,39 +496,37 @@ const loadTeamInfo = async (teamId: string | number) => {
if (!teamId) {
return;
}
loadingTeamInfo.value = true;
try {
// 尝试调用团队信息API获取完整信息
try {
const response = await getTeamsByActivityId(viewForm.activityId);
console.log('PendingMembersTab: 团队信息API响应:', response);
if (response.data && Array.isArray(response.data)) {
// 从团队列表中查找当前团队
const teamData = response.data.find((team: any) => {
return team.id == teamId || team.id.toString() === teamId || team.id === parseInt(teamId as string);
});
console.log('PendingMembersTab: 找到的团队数据:', teamData);
if (teamData) {
// 优先从teamMembers中查找队长信息
let leaderPhone = '暂无联系方式';
let leaderDepartment = '暂无院系信息';
let leaderMajor = '';
let leaderClassName = '';
try {
if (teamData.teamMembers) {
const membersData = JSON.parse(teamData.teamMembers);
if (Array.isArray(membersData)) {
// 查找队长信息(isLeader: 1 或者 userId 匹配 leaderUserId)
const leaderMember = membersData.find((member: any) =>
member.isLeader === 1 || member.userId === teamData.leaderUserId
);
const leaderMember = membersData.find((member: any) => member.isLeader === 1 || member.userId === teamData.leaderUserId);
if (leaderMember) {
leaderPhone = leaderMember.phone || '暂无联系方式';
leaderDepartment = leaderMember.department || '暂无院系信息';
@@ -539,7 +539,7 @@ const loadTeamInfo = async (teamId: string | number) => {
} catch (parseError) {
console.error('PendingMembersTab: 解析团队成员JSON失败:', parseError);
}
// 如果从teamMembers中没有找到队长信息,尝试通过leaderUserId获取
if (leaderPhone === '暂无联系方式' || leaderDepartment === '暂无院系信息') {
try {
@@ -547,7 +547,7 @@ const loadTeamInfo = async (teamId: string | number) => {
console.log('PendingMembersTab: 通过leaderUserId获取队长详细信息:', teamData.leaderUserId);
const studentResponse = await getStudent(teamData.leaderUserId);
console.log('PendingMembersTab: getStudent API响应:', studentResponse);
if (studentResponse.data) {
const studentData = studentResponse.data;
console.log('PendingMembersTab: 学生数据详情:', studentData);
@@ -557,7 +557,7 @@ const loadTeamInfo = async (teamId: string | number) => {
majorName: studentData.majorName,
className: studentData.className
});
// 只有在teamMembers中没有找到时才使用API获取的信息
if (leaderPhone === '暂无联系方式') {
leaderPhone = studentData.phone || '暂无联系方式';
@@ -580,7 +580,7 @@ const loadTeamInfo = async (teamId: string | number) => {
console.log('PendingMembersTab: 获取队长详细信息失败:', studentError);
}
}
// 如果仍然没有获取到队长信息,尝试从报名详情中获取
if (leaderPhone === '暂无联系方式' && viewForm.phone) {
leaderPhone = viewForm.phone;
@@ -590,17 +590,15 @@ const loadTeamInfo = async (teamId: string | number) => {
leaderDepartment = viewForm.department;
console.log('PendingMembersTab: 使用报名详情中的院系信息:', leaderDepartment);
}
// 最后的备用方案:如果还是没有获取到,尝试从teamMembers中查找匹配leaderUserId的成员
if ((leaderPhone === '暂无联系方式' || leaderDepartment === '暂无院系信息') && teamData.teamMembers) {
try {
const membersData = JSON.parse(teamData.teamMembers);
if (Array.isArray(membersData)) {
// 查找与leaderUserId匹配的成员
const leaderMember = membersData.find((member: any) =>
member.userId === teamData.leaderUserId
);
const leaderMember = membersData.find((member: any) => member.userId === teamData.leaderUserId);
if (leaderMember) {
if (leaderPhone === '暂无联系方式') {
leaderPhone = leaderMember.phone || '暂无联系方式';
@@ -621,7 +619,7 @@ const loadTeamInfo = async (teamId: string | number) => {
console.error('PendingMembersTab: 备用方案解析团队成员JSON失败:', parseError);
}
}
// 更新团队信息
teamInfo.value = {
id: teamData.id,
@@ -636,9 +634,9 @@ const loadTeamInfo = async (teamId: string | number) => {
description: teamData.description || '',
teamMembers: teamData.teamMembers || '[]'
};
console.log('PendingMembersTab: 最终设置的团队信息:', teamInfo.value);
// 解析团队成员信息
if (teamData.teamMembers) {
try {
@@ -674,13 +672,13 @@ const loadTeamInfo = async (teamId: string | number) => {
}
} catch (apiError) {
console.log('PendingMembersTab: 团队信息API调用失败,使用报名详情中的信息:', apiError);
// 如果API调用失败,使用报名详情中的信息
if (viewForm.teamName) {
// 尝试获取队长的详细信息
let leaderPhone = '暂无联系方式';
let leaderDepartment = '暂无院系信息';
try {
if (viewForm.userId) {
const studentResponse = await getStudent(viewForm.userId);
@@ -701,7 +699,7 @@ const loadTeamInfo = async (teamId: string | number) => {
leaderDepartment = viewForm.department;
}
}
// 构造团队信息对象,优先使用获取到的详细信息
teamInfo.value = {
id: viewForm.teamId,
@@ -716,10 +714,10 @@ const loadTeamInfo = async (teamId: string | number) => {
description: viewForm.remark || '暂无团队简介',
teamMembers: '[]' // JSON格式的字符串
};
// 团队成员信息暂时为空,因为报名详情中没有这个字段
teamMembers.value = [] as TeamMemberInfo[];
console.log('PendingMembersTab: 使用报名详情构造的团队信息:', teamInfo.value);
} else {
teamInfo.value = null;
@@ -744,8 +742,6 @@ const cancelView = () => {
teamMembers.value = [] as TeamMemberInfo[];
};
// 暴露给父组件的方法
defineExpose({
getList
@@ -753,7 +749,7 @@ defineExpose({
// 定义事件
const emit = defineEmits<{
refreshOtherTabs: []
refreshOtherTabs: [];
}>();
// 通知父组件刷新其他标签页
@@ -803,4 +799,4 @@ onMounted(() => {
.mb-4 {
margin-bottom: 16px;
}
</style>
</style>
@@ -3,21 +3,21 @@
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="always" style="padding: 8px 16px;">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0;">
<el-card shadow="always" style="padding: 8px 16px">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0">
<el-row :gutter="16" align="middle">
<el-col :span="5">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0;">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0">
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0;">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -36,10 +36,14 @@
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['stgl:activityRegister:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['stgl:activityRegister:edit']">修改</el-button>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['stgl:activityRegister:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['stgl:activityRegister:remove']">删除</el-button>
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['stgl:activityRegister:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Download" @click="handleExport" v-hasPermi="['stgl:activityRegister:export']">导出数据</el-button>
@@ -98,7 +102,7 @@
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['stgl:activityRegister:edit']"></el-button>
</el-tooltip>
<!-- 审核通过状态显示成绩录入按钮 -->
<template v-if="Number(scope.row.approvalStatus) === 1">
<el-tooltip content="录入成绩" placement="top">
@@ -111,7 +115,7 @@
></el-button>
</el-tooltip>
</template>
<el-tooltip content="删除" placement="top">
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['stgl:activityRegister:remove']"></el-button>
</el-tooltip>
@@ -216,7 +220,7 @@
<el-divider content-position="left">
<span class="text-lg font-medium">团队信息</span>
</el-divider>
<el-descriptions :column="2" border class="mb-4">
<el-descriptions-item label="团队名称">{{ teamInfo.teamName }}</el-descriptions-item>
<el-descriptions-item label="团队人数">{{ teamInfo.teamSize || 0 }}人</el-descriptions-item>
@@ -253,7 +257,7 @@
</el-table>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelView">关 闭</el-button>
@@ -306,7 +310,7 @@
是否更新已经存在的报名数据
</div>
<span>仅允许导入xls、xlsx格式文件。</span>
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
</div>
</template>
</el-upload>
@@ -508,18 +512,22 @@ const data = reactive<PageData<ExtendedActivityRegisterForm, StglActivityRegistr
const { queryParams, form, rules } = toRefs(data);
// 监听activityId变化,同步更新查询参数
watch(() => props.activityId, (newActivityId) => {
if (newActivityId) {
console.log('RegisteredMembersTab: activityId发生变化,从', queryParams.value.activityId, '更新为', newActivityId);
queryParams.value.activityId = newActivityId;
// 重置分页到第一页
queryParams.value.pageNum = 1;
// 如果组件已经挂载,则立即重新查询
if (memberList.value.length > 0 || loading.value === false) {
getList();
watch(
() => props.activityId,
(newActivityId) => {
if (newActivityId) {
console.log('RegisteredMembersTab: activityId发生变化,从', queryParams.value.activityId, '更新为', newActivityId);
queryParams.value.activityId = newActivityId;
// 重置分页到第一页
queryParams.value.pageNum = 1;
// 如果组件已经挂载,则立即重新查询
if (memberList.value.length > 0 || loading.value === false) {
getList();
}
}
}
}, { immediate: true });
},
{ immediate: true }
);
// 上传相关
const uploadRef = ref<UploadInstance>();
@@ -544,23 +552,23 @@ const getList = async () => {
loading.value = true;
try {
// 构建查询参数,确保使用最新的activityId
const queryData = {
const queryData = {
...queryParams.value,
activityId: props.activityId // 强制使用最新的props值
activityId: props.activityId // 强制使用最新的props值
};
// 如果状态字段为空或未选择,则查询已通过(approvalStatus=1)的报名记录
if (!queryData.approvalStatus) {
queryData.approvalStatus = '1';
}
console.log('RegisteredMembersTab: getList 开始查询,参数:', queryData);
console.log('RegisteredMembersTab: props.activityId:', props.activityId);
console.log('RegisteredMembersTab: queryParams.activityId:', queryParams.value.activityId);
const res = await listActivityRegister(queryData);
console.log('RegisteredMembersTab: 查询结果:', res);
memberList.value = res.rows;
total.value = res.total;
} catch (error) {
@@ -579,9 +587,9 @@ const cancel = () => {
/** 表单重置 */
const reset = () => {
form.value = {
...initFormData,
activityId: props.activityId // 确保使用最新的activityId
form.value = {
...initFormData,
activityId: props.activityId // 确保使用最新的activityId
};
memberFormRef.value?.resetFields();
};
@@ -626,7 +634,7 @@ const handleUpdate = async (row?: ExtendedStglActivityRegistrationVo) => {
const _id = row?.id || ids.value[0];
const res = await getActivityRegister(_id);
Object.assign(form.value, res.data);
// 设置学生选择器数据
if (res.data.studentName) {
// 使用扩展类型来访问额外字段
@@ -642,7 +650,7 @@ const handleUpdate = async (row?: ExtendedStglActivityRegistrationVo) => {
className: extendedData.className || ''
} as StudentVO;
}
dialog.visible = true;
dialog.title = '修改活动报名';
};
@@ -652,14 +660,14 @@ const submitForm = () => {
memberFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
// 创建提交数据,排除selectedStudent字段
const { selectedStudent, ...submitData } = form.value;
// 确保包含必要字段
submitData.activityId = props.activityId; // 使用最新的activityId
submitData.activityId = props.activityId; // 使用最新的activityId
submitData.approvalStatus = 1; // 新增的报名默认为已通过状态
try {
if (form.value.id) {
await updateActivityRegister(submitData as StglActivityRegistrationForm);
@@ -682,14 +690,14 @@ const submitForm = () => {
const handleDelete = async (row?: ExtendedStglActivityRegistrationVo) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除选中的活动报名记录?');
try {
if (Array.isArray(_ids)) {
await delActivityRegister(_ids.join(','));
} else {
await delActivityRegister(_ids);
}
proxy?.$modal.msgSuccess('删除成功');
await getList();
} catch (error) {
@@ -703,7 +711,7 @@ const handleView = async (row: ExtendedStglActivityRegistrationVo) => {
Object.assign(viewForm, res.data);
viewDialog.visible = true;
viewDialog.title = '查看活动报名详情';
// 如果是团队报名,获取团队信息
if (Number(viewForm.registrationType) === 2 && viewForm.teamId) {
await loadTeamInfo(viewForm.teamId);
@@ -719,39 +727,37 @@ const loadTeamInfo = async (teamId: string | number) => {
if (!teamId) {
return;
}
loadingTeamInfo.value = true;
try {
// 尝试调用团队信息API获取完整信息
try {
const response = await getTeamsByActivityId(viewForm.activityId);
console.log('RegisteredMembersTab: 团队信息API响应:', response);
if (response.data && Array.isArray(response.data)) {
// 从团队列表中查找当前团队
const teamData = response.data.find((team: any) => {
return team.id == teamId || team.id.toString() === teamId || team.id === parseInt(teamId as string);
});
console.log('RegisteredMembersTab: 找到的团队数据:', teamData);
if (teamData) {
// 优先从teamMembers中查找队长信息
let leaderPhone = '暂无联系方式';
let leaderDepartment = '暂无院系信息';
let leaderMajor = '';
let leaderClassName = '';
try {
if (teamData.teamMembers) {
const membersData = JSON.parse(teamData.teamMembers);
if (Array.isArray(membersData)) {
// 查找队长信息(isLeader: 1 或者 userId 匹配 leaderUserId)
const leaderMember = membersData.find((member: any) =>
member.isLeader === 1 || member.userId === teamData.leaderUserId
);
const leaderMember = membersData.find((member: any) => member.isLeader === 1 || member.userId === teamData.leaderUserId);
if (leaderMember) {
leaderPhone = leaderMember.phone || '暂无联系方式';
leaderDepartment = leaderMember.department || '暂无院系信息';
@@ -764,7 +770,7 @@ const loadTeamInfo = async (teamId: string | number) => {
} catch (parseError) {
console.error('RegisteredMembersTab: 解析团队成员JSON失败:', parseError);
}
// 如果从teamMembers中没有找到队长信息,尝试通过leaderUserId获取
if (leaderPhone === '暂无联系方式' || leaderDepartment === '暂无院系信息') {
try {
@@ -772,7 +778,7 @@ const loadTeamInfo = async (teamId: string | number) => {
console.log('RegisteredMembersTab: 通过leaderUserId获取队长详细信息:', teamData.leaderUserId);
const studentResponse = await getStudent(teamData.leaderUserId);
console.log('RegisteredMembersTab: getStudent API响应:', studentResponse);
if (studentResponse.data) {
const studentData = studentResponse.data;
console.log('RegisteredMembersTab: 学生数据详情:', studentData);
@@ -782,7 +788,7 @@ const loadTeamInfo = async (teamId: string | number) => {
majorName: studentData.majorName,
className: studentData.className
});
// 只有在teamMembers中没有找到时才使用API获取的信息
if (leaderPhone === '暂无联系方式') {
leaderPhone = studentData.phone || '暂无联系方式';
@@ -805,7 +811,7 @@ const loadTeamInfo = async (teamId: string | number) => {
console.log('RegisteredMembersTab: 获取队长详细信息失败:', studentError);
}
}
// 如果仍然没有获取到队长信息,尝试从报名详情中获取
if (leaderPhone === '暂无联系方式' && viewForm.phone) {
leaderPhone = viewForm.phone;
@@ -815,17 +821,15 @@ const loadTeamInfo = async (teamId: string | number) => {
leaderDepartment = viewForm.department;
console.log('RegisteredMembersTab: 使用报名详情中的院系信息:', leaderDepartment);
}
// 最后的备用方案:如果还是没有获取到,尝试从teamMembers中查找匹配leaderUserId的成员
if ((leaderPhone === '暂无联系方式' || leaderDepartment === '暂无院系信息') && teamData.teamMembers) {
try {
const membersData = JSON.parse(teamData.teamMembers);
if (Array.isArray(membersData)) {
// 查找与leaderUserId匹配的成员
const leaderMember = membersData.find((member: any) =>
member.userId === teamData.leaderUserId
);
const leaderMember = membersData.find((member: any) => member.userId === teamData.leaderUserId);
if (leaderMember) {
if (leaderPhone === '暂无联系方式') {
leaderPhone = leaderMember.phone || '暂无联系方式';
@@ -846,7 +850,7 @@ const loadTeamInfo = async (teamId: string | number) => {
console.error('RegisteredMembersTab: 备用方案解析团队成员JSON失败:', parseError);
}
}
// 更新团队信息
teamInfo.value = {
id: teamData.id,
@@ -861,9 +865,9 @@ const loadTeamInfo = async (teamId: string | number) => {
description: teamData.description || '',
teamMembers: teamData.teamMembers || '[]'
};
console.log('RegisteredMembersTab: 最终设置的团队信息:', teamInfo.value);
// 解析团队成员信息
if (teamData.teamMembers) {
try {
@@ -899,13 +903,13 @@ const loadTeamInfo = async (teamId: string | number) => {
}
} catch (apiError) {
console.log('RegisteredMembersTab: 团队信息API调用失败,使用报名详情中的信息:', apiError);
// 如果API调用失败,使用报名详情中的信息
if (viewForm.teamName) {
// 尝试获取队长的详细信息
let leaderPhone = '暂无联系方式';
let leaderDepartment = '暂无院系信息';
try {
if (viewForm.userId) {
const studentResponse = await getStudent(viewForm.userId);
@@ -926,7 +930,7 @@ const loadTeamInfo = async (teamId: string | number) => {
leaderDepartment = viewForm.department;
}
}
// 构造团队信息对象,优先使用获取到的详细信息
teamInfo.value = {
id: viewForm.teamId,
@@ -941,10 +945,10 @@ const loadTeamInfo = async (teamId: string | number) => {
description: viewForm.remark || '暂无团队简介',
teamMembers: '[]' // JSON格式的字符串
};
// 团队成员信息暂时为空,因为报名详情中没有这个字段
teamMembers.value = [] as TeamMemberInfo[];
console.log('RegisteredMembersTab: 使用报名详情构造的团队信息:', teamInfo.value);
} else {
teamInfo.value = null;
@@ -1124,4 +1128,4 @@ onMounted(() => {
.mb-4 {
margin-bottom: 16px;
}
</style>
</style>
+5 -5
View File
@@ -147,7 +147,7 @@
<!-- 查看活动详细信息对话框 -->
<el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="1200px" append-to-body>
<ActivityDetailView :activity="viewForm" />
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="handleRegister(viewForm)" v-if="canRegister(viewForm)">立即报名</el-button>
@@ -484,7 +484,7 @@ const checkUserRegistrationStatus = async () => {
console.warn('活动数据不完整,跳过报名状态检查:', activity);
return;
}
const registered = await checkRegistration(activity.id as number);
userRegistrationStatus.value[activity.id as number] = registered.data;
} catch (error) {
@@ -591,7 +591,7 @@ const handleRegistrationTypeChange = async (value: number) => {
} else if (value === 2) {
// 团队报名,自动填充当前用户信息作为团队负责人
await loadUserInfoToForm();
// 设置团队负责人为学生对象(用于StudentSelector组件)
// 使用类型断言避免类型错误,因为我们只需要这些字段用于显示
registerForm.presidentStudent = {
@@ -747,14 +747,14 @@ const submitRegister = () => {
// 个人报名时不发送任何团队相关字段
await registerActivity(submitData);
// 根据审核设置显示不同的成功消息
if (selectedActivity.value.needApproval === 0) {
proxy?.$modal.msgSuccess('报名成功!您的报名已自动通过审核');
} else {
proxy?.$modal.msgSuccess('报名成功,请等待审核');
}
registerDialog.visible = false;
// 更新报名状态
+140 -102
View File
@@ -84,18 +84,41 @@
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['stgl:activity:add']">新增</el-button>
</el-col>
<el-col :span="1.5" v-if="!single && selectedActivity && ['0', '7', '6'].includes(getOriginalActivityStatus(selectedActivity)) && hasActivityOperationPermission(selectedActivity)">
<el-button icon="Edit" @click="handleUpdate()" v-hasPermi="['stgl:activity:edit']">修改</el-button>
</el-col>
<el-col :span="1.5" v-if="!single && selectedActivity && getOriginalActivityStatus(selectedActivity) === '0' && hasActivityOperationPermission(selectedActivity)">
<el-button type="success" icon="Upload" @click="handleSubmit()" v-hasPermi="['stgl:activity:edit']">提交</el-button>
</el-col>
<el-col :span="1.5" v-if="!single && selectedActivity && getOriginalActivityStatus(selectedActivity) === '2' && hasActivityOperationPermission(selectedActivity)">
<el-button type="warning" icon="Check" @click="handleApprove()" v-hasPermi="['stgl:activity:approve']">审批</el-button>
</el-col>
<el-col :span="1.5" v-if="!single && selectedActivity && ['3', '4'].includes(selectedActivity.activityStatus) && hasActivityOperationPermission(selectedActivity)">
<el-button type="danger" icon="Close" @click="handleCancel()" v-hasPermi="['stgl:activity:edit']">取消活动</el-button>
</el-col>
<el-col
:span="1.5"
v-if="
!single &&
selectedActivity &&
['0', '7', '6'].includes(getOriginalActivityStatus(selectedActivity)) &&
hasActivityOperationPermission(selectedActivity)
"
>
<el-button icon="Edit" @click="handleUpdate()" v-hasPermi="['stgl:activity:edit']">修改</el-button>
</el-col>
<el-col
:span="1.5"
v-if="
!single && selectedActivity && getOriginalActivityStatus(selectedActivity) === '0' && hasActivityOperationPermission(selectedActivity)
"
>
<el-button type="success" icon="Upload" @click="handleSubmit()" v-hasPermi="['stgl:activity:edit']">提交</el-button>
</el-col>
<el-col
:span="1.5"
v-if="
!single && selectedActivity && getOriginalActivityStatus(selectedActivity) === '2' && hasActivityOperationPermission(selectedActivity)
"
>
<el-button type="warning" icon="Check" @click="handleApprove()" v-hasPermi="['stgl:activity:approve']">审批</el-button>
</el-col>
<el-col
:span="1.5"
v-if="
!single && selectedActivity && ['3', '4'].includes(selectedActivity.activityStatus) && hasActivityOperationPermission(selectedActivity)
"
>
<el-button type="danger" icon="Close" @click="handleCancel()" v-hasPermi="['stgl:activity:edit']">取消活动</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="CopyDocument" :disabled="single" @click="handleCopy()" v-hasPermi="['stgl:activity:add']">复制</el-button>
</el-col>
@@ -105,7 +128,9 @@
:disabled="multiple || !['0', '7'].includes(getOriginalActivityStatus(selectedActivity) || '')"
@click="handleDelete()"
v-hasPermi="['stgl:activity:remove']"
:title="selectedActivity && !['0', '7'].includes(getOriginalActivityStatus(selectedActivity)) ? '只有草稿和已驳回状态的活动可以删除' : ''"
:title="
selectedActivity && !['0', '7'].includes(getOriginalActivityStatus(selectedActivity)) ? '只有草稿和已驳回状态的活动可以删除' : ''
"
>删除</el-button
>
</el-col>
@@ -209,7 +234,11 @@
v-hasPermi="['stgl:activity:query']"
></el-button>
</el-tooltip>
<el-tooltip content="修改" placement="top" v-if="['0', '7', '6'].includes(getOriginalActivityStatus(scope.row)) && hasActivityOperationPermission(scope.row)">
<el-tooltip
content="修改"
placement="top"
v-if="['0', '7', '6'].includes(getOriginalActivityStatus(scope.row)) && hasActivityOperationPermission(scope.row)"
>
<el-button
link
type="primary"
@@ -220,7 +249,11 @@
v-hasPermi="['stgl:activity:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="提交" placement="top" v-if="getOriginalActivityStatus(scope.row) === '0' && hasActivityOperationPermission(scope.row)">
<el-tooltip
content="提交"
placement="top"
v-if="getOriginalActivityStatus(scope.row) === '0' && hasActivityOperationPermission(scope.row)"
>
<el-button
link
type="success"
@@ -231,7 +264,11 @@
v-hasPermi="['stgl:activity:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="审批" placement="top" v-if="getOriginalActivityStatus(scope.row) === '2' && hasActivityOperationPermission(scope.row)">
<el-tooltip
content="审批"
placement="top"
v-if="getOriginalActivityStatus(scope.row) === '2' && hasActivityOperationPermission(scope.row)"
>
<el-button
link
type="warning"
@@ -242,7 +279,11 @@
v-hasPermi="['stgl:activity:approve']"
></el-button>
</el-tooltip>
<el-tooltip content="取消活动" placement="top" v-if="['3', '4'].includes(scope.row.activityStatus) && hasActivityOperationPermission(scope.row)">
<el-tooltip
content="取消活动"
placement="top"
v-if="['3', '4'].includes(scope.row.activityStatus) && hasActivityOperationPermission(scope.row)"
>
<el-button
link
type="danger"
@@ -286,7 +327,11 @@
v-hasPermi="['stgl:activity:query']"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top" v-if="['0', '7'].includes(getOriginalActivityStatus(scope.row)) && hasActivityOperationPermission(scope.row)">
<el-tooltip
content="删除"
placement="top"
v-if="['0', '7'].includes(getOriginalActivityStatus(scope.row)) && hasActivityOperationPermission(scope.row)"
>
<el-button
link
type="danger"
@@ -409,9 +454,9 @@
<div class="section-divider">
<h4 class="section-title">指导老师信息</h4>
</div>
<AdvisorManager
<AdvisorManager
:key="`advisor-manager-${form.id || 'new'}-${formKey}`"
v-model="form.advisors"
v-model="form.advisors"
@change="handleAdvisorsChange"
:initial-advisors="form.advisors"
/>
@@ -495,10 +540,6 @@
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</el-form>
<template #footer>
@@ -565,7 +606,7 @@
</el-dialog>
<!-- 成员审核弹窗 -->
<ActivityMembersDialog
<ActivityMembersDialog
v-model:visible="membersDialog.visible"
:activity-id="selectedActivity?.id"
:activity-name="selectedActivity?.activityName"
@@ -585,14 +626,7 @@
</el-radio-group>
</el-form-item>
<el-form-item label="驳回原因" prop="rejectReason" v-if="approveForm.approvalResult === 'reject'">
<el-input
v-model="approveForm.rejectReason"
type="textarea"
placeholder="请输入驳回原因"
:rows="3"
:maxlength="200"
show-word-limit
/>
<el-input v-model="approveForm.rejectReason" type="textarea" placeholder="请输入驳回原因" :rows="3" :maxlength="200" show-word-limit />
</el-form-item>
</el-form>
<template #footer>
@@ -636,7 +670,14 @@ const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const { stgl_activity_type, stgl_activity_status, stgl_registration_type, stgl_registration_status, stgl_activity_range, sys_yes_no } = toRefs<any>(
proxy?.useDict('stgl_activity_type', 'stgl_activity_status', 'stgl_registration_type', 'stgl_registration_status', 'stgl_activity_range', 'sys_yes_no')
proxy?.useDict(
'stgl_activity_type',
'stgl_activity_status',
'stgl_registration_type',
'stgl_registration_status',
'stgl_activity_range',
'sys_yes_no'
)
);
const activityList = ref<StglActivityVO[]>([]);
@@ -790,8 +831,6 @@ const { queryParams, form, rules } = toRefs(data);
const viewForm = ref<StglActivityVO>({} as StglActivityVO);
/** 初始化URL参数 */
const initUrlParams = () => {
const { relatedClubId, relatedClubName, relatedTeamId, relatedTeamName, from } = route.query;
@@ -897,27 +936,27 @@ const reset = () => {
form.value.advisors = [];
form.value.instructorList = [];
form.value.instructors = '[]';
// 重置关联社团禁用状态
isRelatedClubDisabled.value = false;
// 重置审批表单
approveForm.id = undefined;
approveForm.activityName = '';
approveForm.approvalResult = 'approve';
approveForm.rejectReason = '';
// 更新formKey以强制重新渲染组件
formKey.value++;
activeTab.value = 'basic';
console.log('表单重置后的状态:', {
advisors: form.value.advisors,
instructorList: form.value.instructorList,
instructors: form.value.instructors
});
// 延迟重置表单字段,避免递归更新
setTimeout(() => {
activityFormRef.value?.resetFields();
@@ -950,37 +989,37 @@ const handleAdd = () => {
// 先重置表单
form.value = { ...initFormData };
activeTab.value = 'basic';
// 确保指导老师字段被正确初始化
form.value.advisors = [];
form.value.instructorList = [];
form.value.instructors = '[]';
// 更新formKey以强制重新渲染组件
formKey.value++;
console.log('新增活动 - 初始化表单数据:', {
advisors: form.value.advisors,
instructorList: form.value.instructorList,
instructors: form.value.instructors,
formKey: formKey.value
});
// 如果有查询条件中的关联组织(社团或志愿者队伍),自动填充关联信息
if (queryParams.value.relatedClub) {
form.value.relatedClub = queryParams.value.relatedClub;
form.value.relatedClubId = queryParams.value.relatedClub.relatedClubId;
form.value.relatedType = queryParams.value.relatedClub.relatedType;
// 设置关联社团字段为禁用状态(不允许修改)
isRelatedClubDisabled.value = true;
console.log('新增活动 - 自动填充关联组织信息:', {
relatedType: queryParams.value.relatedClub.relatedType,
relatedClubId: queryParams.value.relatedClub.relatedClubId,
relatedClubName: queryParams.value.relatedClub.relatedClubName
});
// 确保表单数据正确设置
console.log('新增活动 - 表单数据设置后:', {
formRelatedClub: form.value.relatedClub,
@@ -994,7 +1033,7 @@ const handleAdd = () => {
dialog.visible = true;
dialog.title = '添加活动信息';
// 确保对话框完全打开后再检查表单状态
nextTick(() => {
console.log('新增活动 - 对话框打开后的表单状态:', {
@@ -1036,7 +1075,7 @@ const handleUpdate = async (row?: StglActivityVO) => {
relatedType: res.data.relatedType,
uniqueKey: `${res.data.relatedType}-${res.data.relatedClubId}`
};
// 修改时,如果是从URL参数进入的,则禁用关联社团字段
if (queryParams.value.relatedClub && queryParams.value.relatedClub.relatedClubId === res.data.relatedClubId) {
isRelatedClubDisabled.value = true;
@@ -1074,7 +1113,7 @@ const handleUpdate = async (row?: StglActivityVO) => {
try {
const instructorsData = JSON.parse(res.data.instructors);
console.log('解析的指导老师数据:', instructorsData);
// 确保数据格式正确
if (Array.isArray(instructorsData)) {
(res.data as any).advisors = instructorsData.map((advisor: any) => ({
@@ -1098,12 +1137,12 @@ const handleUpdate = async (row?: StglActivityVO) => {
}
Object.assign(form.value, res.data);
// 确保数据类型正确
if (form.value.registrationType !== undefined) {
form.value.registrationType = String(form.value.registrationType);
}
// 确保指导老师数据正确设置
console.log('修改活动 - 设置表单数据后的状态:', {
advisors: form.value.advisors,
@@ -1111,10 +1150,10 @@ const handleUpdate = async (row?: StglActivityVO) => {
instructors: form.value.instructors,
registrationType: form.value.registrationType
});
// 更新formKey以强制重新渲染组件
formKey.value++;
dialog.visible = true;
dialog.title = '修改活动信息';
};
@@ -1122,7 +1161,7 @@ const handleUpdate = async (row?: StglActivityVO) => {
/** 显示验证错误提示 */
const showValidationErrors = (invalidFields: any) => {
if (!invalidFields) return;
const errorMessages: string[] = [];
const tabMapping = {
// 基本信息标签页字段
@@ -1135,20 +1174,23 @@ const showValidationErrors = (invalidFields: any) => {
'registrationType': { tab: 'registration', label: '报名方式' },
'activityRange': { tab: 'registration', label: '报名范围' }
};
let needSwitchTab = '';
let currentTabErrors: string[] = [];
let otherTabErrors: string[] = [];
// 分类错误信息
Object.keys(invalidFields).forEach(fieldName => {
Object.keys(invalidFields).forEach((fieldName) => {
const fieldInfo = tabMapping[fieldName];
if (fieldInfo) {
const errorMsg = invalidFields[fieldName][0]?.message || '验证失败';
// 去掉重复的"不能为空"等文字,只保留核心提示
const cleanMessage = errorMsg.replace(/不能为空$/, '').replace(/^请选择/, '').replace(/^请输入/, '');
const cleanMessage = errorMsg
.replace(/不能为空$/, '')
.replace(/^请选择/, '')
.replace(/^请输入/, '');
const errorMessage = cleanMessage ? `${fieldInfo.label}:${cleanMessage}` : `${fieldInfo.label}为必填项`;
if (fieldInfo.tab === activeTab.value) {
currentTabErrors.push(errorMessage);
} else {
@@ -1159,17 +1201,17 @@ const showValidationErrors = (invalidFields: any) => {
}
}
});
// 构建提示消息
if (currentTabErrors.length > 0) {
errorMessages.push(`【当前页面】${currentTabErrors.join(';')}`);
}
if (otherTabErrors.length > 0) {
const tabName = needSwitchTab === 'registration' ? '报名设置' : '基本信息';
errorMessages.push(`【${tabName}页面】${otherTabErrors.join(';')}`);
}
if (errorMessages.length > 0) {
// 使用Notification在浏览器顶部显示提示
ElNotification({
@@ -1180,11 +1222,11 @@ const showValidationErrors = (invalidFields: any) => {
position: 'top-right',
showClose: true
});
// 如果其他标签页有错误,自动切换到第一个有错误的标签页
if (needSwitchTab && needSwitchTab !== activeTab.value) {
activeTab.value = needSwitchTab;
// 切换标签页后延迟一下再次验证,确保用户能看到错误字段
nextTick(() => {
activityFormRef.value?.validate(() => {});
@@ -1201,7 +1243,7 @@ const submitForm = () => {
// 准备提交的数据
const submitData = { ...form.value } as any;
console.log('提交前 - 原始表单数据:', {
advisors: submitData.advisors,
instructorList: submitData.instructorList,
@@ -1234,7 +1276,7 @@ const submitForm = () => {
if (submitData.coOrganizerList && submitData.coOrganizerList.length > 0) {
submitData.coOrganizers = JSON.stringify(submitData.coOrganizerList);
} else {
submitData.coOrganizers = '[]'; // 修复:使用空JSON数组而不是undefined
submitData.coOrganizers = '[]'; // 修复:使用空JSON数组而不是undefined
}
// 处理指导老师数据
@@ -1245,24 +1287,24 @@ const submitForm = () => {
submitData.advisors = [];
}
try {
if (form.value.id) {
await updateStglActivity(submitData);
} else {
await addStglActivity(submitData);
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
// 提交成功后重置表单
reset();
await getList();
} catch (error) {
console.error('提交失败:', error);
} finally {
buttonLoading.value = false;
try {
if (form.value.id) {
await updateStglActivity(submitData);
} else {
await addStglActivity(submitData);
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
// 提交成功后重置表单
reset();
await getList();
} catch (error) {
console.error('提交失败:', error);
} finally {
buttonLoading.value = false;
}
} else {
// 验证失败,显示必填项提示
showValidationErrors(invalidFields);
@@ -1459,7 +1501,7 @@ const handleRelatedClubChange = (club: ClubOption | null) => {
form.value.relatedClub = club;
form.value.relatedClubId = club?.relatedClubId;
form.value.relatedType = club?.relatedType; // 修复:同时设置关联类型
console.log('活动表单 - 更新后的表单数据:', {
relatedClub: form.value.relatedClub,
relatedClubId: form.value.relatedClubId,
@@ -1473,8 +1515,6 @@ const handleQueryRelatedClubChange = (club: ClubOption | null) => {
queryParams.value.relatedClubId = club?.relatedClubId;
};
/** 承办方变化处理 */
const handleCoOrganizersChange = (coOrganizers: CoOrganizerInfo[]) => {
form.value.coOrganizerList = coOrganizers;
@@ -1525,14 +1565,14 @@ const handleSubmit = async (row?: StglActivityVO) => {
try {
await proxy?.$modal.confirm(`是否确认提交活动"${activity.activityName}"?提交后将进入审批流程。`);
// 调用后端接口更新活动状态为2(待审批)
// 传递完整的活动数据,避免后端验证失败
await updateStglActivity({
...activity,
activityStatus: '2'
});
proxy?.$modal.msgSuccess('提交成功');
await getList();
} catch (error) {
@@ -1564,14 +1604,14 @@ const handleCancel = async (row?: StglActivityVO) => {
try {
await proxy?.$modal.confirm(`是否确认取消活动"${activity.activityName}"?取消后将无法恢复。`);
// 调用后端接口更新活动状态为6(已取消)
// 传递完整的活动数据,避免后端验证失败
await updateStglActivity({
...activity,
activityStatus: '6'
});
proxy?.$modal.msgSuccess('取消活动成功');
await getList();
} catch (error) {
@@ -1588,7 +1628,7 @@ const confirmApprove = async () => {
approveLoading.value = true;
try {
const activity = activityList.value.find(item => item.id === approveForm.id);
const activity = activityList.value.find((item) => item.id === approveForm.id);
if (!activity) {
proxy?.$modal.msgError('活动信息不存在');
return;
@@ -1677,7 +1717,7 @@ const hasInstructorPermission = computed(() => {
// 检查当前用户是否为指定活动的指导老师
const isCurrentUserAdvisor = (activity: StglActivityVO): boolean => {
if (!activity.instructors) return false;
try {
const advisors = JSON.parse(activity.instructors);
if (Array.isArray(advisors)) {
@@ -1690,7 +1730,7 @@ const isCurrentUserAdvisor = (activity: StglActivityVO): boolean => {
} catch (error) {
console.warn('解析活动指导老师数据失败:', error);
}
return false;
};
@@ -1700,22 +1740,22 @@ const hasActivityOperationPermission = (activity: StglActivityVO): boolean => {
if (proxy?.$auth.hasRole('admin') || proxy?.$auth.hasRole('common')) {
return true;
}
// 指导老师只能操作自己指导的活动
if (proxy?.$auth.hasRole('stgl_instructor')) {
return isCurrentUserAdvisor(activity);
}
// 社长只能操作自己创建的活动
if (proxy?.$auth.hasRole('stgl_president')) {
return activity.createBy === userStore.userId;
}
// 志愿者队长只能操作自己创建的活动
if (proxy?.$auth.hasRole('stgl_volunteer_captain')) {
return activity.createBy === userStore.userId;
}
return false;
};
@@ -1844,6 +1884,4 @@ onMounted(() => {
border: 1px solid #dcdfe6;
}
}
</style>
@@ -43,7 +43,6 @@
新增
</el-button>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['stgl:clubmember:edit']"
@@ -421,7 +420,7 @@ const { queryParams, form, rules } = toRefs(data);
const isAddButtonDisabled = computed(() => {
// 使用本地成员数量,确保实时更新
const currentCount = memberList.value.length;
if (!props.memberLimit || props.memberLimit <= 0) {
return false; // 没有设置上限,允许新增
}
@@ -431,7 +430,7 @@ const isAddButtonDisabled = computed(() => {
// 计算属性:新增按钮的提示信息
const getAddButtonTooltip = computed(() => {
const currentCount = memberList.value.length;
if (!props.memberLimit || props.memberLimit <= 0) {
return '该社团未设置成员上限';
}
@@ -441,7 +440,7 @@ const getAddButtonTooltip = computed(() => {
// 计算属性:成员数量标签的类型
const getMemberCountTagType = computed(() => {
const currentCount = memberList.value.length;
if (!props.memberLimit || props.memberLimit <= 0) {
return 'info'; // 无上限时显示信息标签
}
@@ -812,7 +811,7 @@ defineExpose({
// 定义事件,用于通知父组件数据变化
const emit = defineEmits<{
memberCountChanged: [newCount: number]
memberCountChanged: [newCount: number];
}>();
// 通知父组件成员数量变化
@@ -838,6 +837,4 @@ const notifyMemberCountChange = () => {
white-space: pre-wrap;
word-break: break-word;
}
</style>
@@ -3,27 +3,22 @@
<el-tabs v-model="activeTab" type="border-card" :key="clubId" @tab-change="handleTabChange">
<!-- 已加入标签页 -->
<el-tab-pane label="已加入" name="joined">
<JoinedMembersTab
<JoinedMembersTab
ref="joinedTabRef"
:club-id="clubId"
:club-name="clubName"
:club-id="clubId"
:club-name="clubName"
:member-limit="memberLimit"
:current-member-count="currentMemberCount"
:key="`joined-${clubId}`"
:key="`joined-${clubId}`"
/>
</el-tab-pane>
<!-- 待审核标签页 -->
<el-tab-pane label="待审核" name="pending">
<PendingMembersTab
ref="pendingTabRef"
:club-id="clubId"
:club-name="clubName"
:key="`pending-${clubId}`"
/>
<PendingMembersTab ref="pendingTabRef" :club-id="clubId" :club-name="clubName" :key="`pending-${clubId}`" />
</el-tab-pane>
</el-tabs>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">关 闭</el-button>
@@ -58,27 +53,33 @@ const joinedTabRef = ref();
const pendingTabRef = ref();
// 监听visible变化
watch(() => props.visible, (newVal) => {
if (newVal) {
activeTab.value = 'joined';
// 当弹窗打开时,触发当前标签页的数据加载
nextTick(() => {
if (activeTab.value === 'joined' && joinedTabRef.value) {
joinedTabRef.value.getList();
} else if (activeTab.value === 'pending' && pendingTabRef.value) {
pendingTabRef.value.getList();
}
});
watch(
() => props.visible,
(newVal) => {
if (newVal) {
activeTab.value = 'joined';
// 当弹窗打开时,触发当前标签页的数据加载
nextTick(() => {
if (activeTab.value === 'joined' && joinedTabRef.value) {
joinedTabRef.value.getList();
} else if (activeTab.value === 'pending' && pendingTabRef.value) {
pendingTabRef.value.getList();
}
});
}
}
});
);
// 监听clubId变化,重置标签页
watch(() => props.clubId, (newClubId) => {
console.log('MemberManagementDialog: clubId 变化:', newClubId);
if (newClubId) {
activeTab.value = 'joined';
watch(
() => props.clubId,
(newClubId) => {
console.log('MemberManagementDialog: clubId 变化:', newClubId);
if (newClubId) {
activeTab.value = 'joined';
}
}
});
);
const handleClose = () => {
emit('update:visible', false);
@@ -91,7 +92,7 @@ const handleVisibleChange = (value: boolean) => {
/** 标签页切换处理 */
const handleTabChange = (tabName: string) => {
console.log('标签页切换到:', tabName);
// 根据当前激活的标签页,调用对应组件的刷新方法
if (tabName === 'joined' && joinedTabRef.value) {
// 调用已加入成员Tab的刷新方法
@@ -111,4 +112,4 @@ const handleTabChange = (tabName: string) => {
.dialog-footer {
text-align: right;
}
</style>
</style>
@@ -3,26 +3,26 @@
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="always" style="padding: 8px 16px;">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0;">
<el-card shadow="always" style="padding: 8px 16px">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0">
<el-row :gutter="16" align="middle">
<el-col :span="5">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0;">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0">
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0;">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="联系方式" prop="params.phone" style="margin-bottom: 0;">
<el-form-item label="联系方式" prop="params.phone" style="margin-bottom: 0">
<el-input v-model="queryParams.params.phone" placeholder="请输入联系方式" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="8" class="search-btn-container">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -38,13 +38,7 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="info"
plain
icon="Check"
:disabled="multiple"
@click="handleBatchApprove()"
v-hasPermi="['stgl:clubmember:approve']"
<el-button type="info" plain icon="Check" :disabled="multiple" @click="handleBatchApprove()" v-hasPermi="['stgl:clubmember:approve']"
>批量审核</el-button
>
</el-col>
@@ -280,9 +274,9 @@ const getList = async () => {
try {
// 构建查询参数,支持多状态查询
const queryData = { ...queryParams.value };
console.log('PendingMembersTab: getList 开始查询,参数:', queryData);
// 待审核页面:查询待审核(status=0)和已驳回(status=3)的成员
// 字典配置:0-待审核,1-审核中,2-已加入,3-已驳回,4-已退出
if (!queryData.status) {
@@ -290,19 +284,19 @@ const getList = async () => {
queryData.params = { ...queryData.params, statusList: ['0', '3'] } as any;
delete queryData.status; // 删除单个status字段
}
// 确保phone参数正确传递到params中
if (queryData.params.phone) {
// 保持phone参数在params中,后端会从params中读取
console.log('PendingMembersTab: phone参数:', queryData.params.phone);
}
console.log('PendingMembersTab: 最终查询参数:', queryData);
console.log('PendingMembersTab: phone参数值:', queryData.params?.phone);
const res = await listStglMember(queryData);
console.log('PendingMembersTab: 查询结果:', res);
memberList.value = res.rows;
total.value = res.total;
} catch (error) {
@@ -363,25 +357,25 @@ const validateMemberData = (member: StglMemberVO, operation: string): boolean =>
console.error(`${operation}失败 - 用户ID缺失:`, member);
return false;
}
if (!member.relationId && !props.clubId) {
proxy?.$modal.msgError(`${operation}失败:关联ID(社团ID)不能为空`);
console.error(`${operation}失败 - 关联ID缺失:`, member);
return false;
}
if (!member.memberType) {
proxy?.$modal.msgError(`${operation}失败:成员类型不能为空`);
console.error(`${operation}失败 - 成员类型缺失:`, member);
return false;
}
if (!member.studentNo || !member.studentName) {
proxy?.$modal.msgError(`${operation}失败:学号和姓名不能为空`);
console.error(`${operation}失败 - 基础信息缺失:`, member);
return false;
}
return true;
};
@@ -391,7 +385,7 @@ const handleApprove = (row: StglMemberVO) => {
if (!validateMemberData(row, '审核')) {
return;
}
approvalForm.id = row.id;
approvalForm.approvalStatus = undefined;
approvalForm.approvalRemark = '';
@@ -408,11 +402,11 @@ const handleBatchApprove = () => {
// 验证所有选中成员的数据完整性
const invalidMembers = memberList.value
.filter(member => ids.value.includes(member.id))
.filter(member => !validateMemberData(member, '批量审核'));
.filter((member) => ids.value.includes(member.id))
.filter((member) => !validateMemberData(member, '批量审核'));
if (invalidMembers.length > 0) {
const invalidNames = invalidMembers.map(m => m.studentName || '未知成员').join('、');
const invalidNames = invalidMembers.map((m) => m.studentName || '未知成员').join('、');
proxy.$modal.msgError(`以下成员数据不完整,无法审核:${invalidNames}`);
return;
}
@@ -432,41 +426,43 @@ const submitApproval = () => {
if (valid) {
if (approvalForm.id) {
// 单个审核 - 再次验证数据完整性
const member = memberList.value.find(m => m.id === approvalForm.id);
const member = memberList.value.find((m) => m.id === approvalForm.id);
if (member && !validateMemberData(member, '审核')) {
return;
}
approveMember(approvalForm.id, approvalForm.approvalStatus === 2).then(() => {
proxy.$modal.msgSuccess('审核成功');
approvalOpen.value = false;
getList();
}).catch((error) => {
console.error('审核失败:', error);
proxy.$modal.msgError('审核失败,请检查数据完整性');
});
approveMember(approvalForm.id, approvalForm.approvalStatus === 2)
.then(() => {
proxy.$modal.msgSuccess('审核成功');
approvalOpen.value = false;
getList();
})
.catch((error) => {
console.error('审核失败:', error);
proxy.$modal.msgError('审核失败,请检查数据完整性');
});
} else {
// 批量审核 - 再次验证数据完整性
const selectedMembers = memberList.value.filter(m => ids.value.includes(m.id));
const invalidMembers = selectedMembers.filter(member => !validateMemberData(member, '批量审核'));
const selectedMembers = memberList.value.filter((m) => ids.value.includes(m.id));
const invalidMembers = selectedMembers.filter((member) => !validateMemberData(member, '批量审核'));
if (invalidMembers.length > 0) {
const invalidNames = invalidMembers.map(m => m.studentName || '未知成员').join('、');
const invalidNames = invalidMembers.map((m) => m.studentName || '未知成员').join('、');
proxy.$modal.msgError(`以下成员数据不完整,无法审核:${invalidNames}`);
return;
}
const promises = ids.value.map(id =>
approveMember(id, approvalForm.approvalStatus === 2)
);
Promise.all(promises).then(() => {
proxy.$modal.msgSuccess('批量审核成功');
approvalOpen.value = false;
getList();
}).catch((error) => {
console.error('批量审核失败:', error);
proxy.$modal.msgError('批量审核失败,请检查数据完整性');
});
const promises = ids.value.map((id) => approveMember(id, approvalForm.approvalStatus === 2));
Promise.all(promises)
.then(() => {
proxy.$modal.msgSuccess('批量审核成功');
approvalOpen.value = false;
getList();
})
.catch((error) => {
console.error('批量审核失败:', error);
proxy.$modal.msgError('批量审核失败,请检查数据完整性');
});
}
}
});
@@ -519,4 +515,4 @@ defineExpose({
white-space: pre-wrap;
word-break: break-word;
}
</style>
</style>
+12 -25
View File
@@ -26,8 +26,6 @@
</el-col>
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
@@ -238,7 +236,14 @@
</el-col>
<el-col :span="12">
<el-form-item label="成员规模上限" prop="memberLimit" required>
<el-input-number v-model="form.memberLimit" :min="1" :max="9999" placeholder="请输入成员规模上限" style="width: 100%" :disabled="!hasInstructorPermission" />
<el-input-number
v-model="form.memberLimit"
:min="1"
:max="9999"
placeholder="请输入成员规模上限"
style="width: 100%"
:disabled="!hasInstructorPermission"
/>
</el-form-item>
</el-col>
</el-row>
@@ -307,8 +312,6 @@
</el-row>
</div>
<!-- 指导老师信息 -->
<div class="personnel-section">
<h4 class="section-title">指导老师信息</h4>
@@ -439,8 +442,6 @@
</div>
</div>
<!-- 指导老师信息 -->
<div class="position-group">
<div class="position-title">指导老师:</div>
@@ -623,12 +624,12 @@
<script setup name="StglClub" lang="ts">
/**
* 社团信息管理
*
*
* 权限控制说明:
* - stgl_president角色:可以操作自己担任社长的社团(无论是否为创建者)
* - stgl_instructor角色:指导老师,有部分字段的只读权限
* - 管理员角色:可以操作所有社团
*
*
* 业务逻辑:
* 管理员创建社团后选择社长,被选中的社长(stgl_president角色)
* 需要能够看到和操作自己担任社长的社团,不再受限于创建者身份
@@ -663,8 +664,6 @@ const hasInstructorPermission = computed(() => {
return proxy?.$auth.hasRoleOr(['stgl_instructor', 'stgl_stgl_admin', 'superadmin']);
});
// 社长权限控制 - 检查用户是否为各社团的社长(无论是否为创建者)
// 用于控制转让按钮显示和社长相关操作权限
const presidentRoles = ref<Record<string | number, boolean>>({});
@@ -974,8 +973,6 @@ const handleUpdate = async (row?: StglClubVO) => {
vicePresidentPhone: res.data.vicePresidentPhone
});
// 处理指导老师信息:优先从JSON字段读取,如果没有则使用单一字段
// 🔥 优先从JSON字段读取指导老师数据
if (res.data.advisorsJson) {
@@ -1040,8 +1037,6 @@ const submitForm = () => {
});
}
// 🔥 新增:同时保存JSON字段和兼容性字段
submitData.advisorsJson = JSON.stringify(submitData.advisors || []);
@@ -1109,8 +1104,6 @@ const handleView = async (row: StglClubVO) => {
(viewData as any).sighOpen = String(viewData.sighOpen);
}
// 🔥 处理指导老师信息:优先从JSON字段读取,如果没有则使用单一字段
console.log('handleView: 指导老师原始数据:', {
advisorsJson: viewData.advisorsJson,
@@ -1263,8 +1256,6 @@ const handlePresidentChange = (student: StudentVO | null) => {
});
};
// 防止递归更新的锁
const syncLock = ref(false);
@@ -1486,9 +1477,9 @@ const handleMemberCountChanged = (newCount: number) => {
if (currentClub.value) {
// 更新当前社团的成员数量
currentClub.value.memberCount = newCount;
// 同时更新列表中的对应社团数据
const clubIndex = clubList.value.findIndex(club => club.id === currentClub.value?.id);
const clubIndex = clubList.value.findIndex((club) => club.id === currentClub.value?.id);
if (clubIndex !== -1) {
clubList.value[clubIndex].memberCount = newCount;
console.log('handleMemberCountChanged: 已更新列表中的成员数量:', clubList.value[clubIndex]);
@@ -1552,8 +1543,6 @@ onMounted(() => {
text-align: center;
}
.member-count {
font-weight: bold;
color: #409eff;
@@ -1706,8 +1695,6 @@ onMounted(() => {
}
}
/* 人员信息部分样式 */
.personnel-section {
margin-bottom: 24px;
+63 -115
View File
@@ -12,19 +12,9 @@
</el-col>
<el-col :span="6">
<el-form-item label="活动类型" prop="activityType">
<el-select
v-model="queryParams.activityType"
placeholder="请选择活动类型"
clearable
style="width: 100%"
>
<el-select v-model="queryParams.activityType" placeholder="请选择活动类型" clearable style="width: 100%">
<el-option label="全部" value="" />
<el-option
v-for="dict in stgl_activity_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
<el-option v-for="dict in stgl_activity_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
@@ -44,7 +34,7 @@
</el-form-item>
</el-col> -->
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
@@ -106,7 +96,7 @@
</el-table-column>
<el-table-column label="报名状态" align="center" prop="myRegistrationStatus" width="100">
<template #default="scope">
<el-tag
<el-tag
:type="scope.row.myRegistrationStatus === '1' ? 'success' : scope.row.myRegistrationStatus === '2' ? 'danger' : 'warning'"
size="small"
>
@@ -217,7 +207,9 @@
<span class="rating-text">{{ detailForm.myRatingScore }}</span>
</div>
</el-descriptions-item>
<el-descriptions-item label="评分时间" v-if="detailForm.myRatingTime">{{ parseTime(detailForm.myRatingTime, '{y}-{m}-{d} {h}:{i}') }}</el-descriptions-item>
<el-descriptions-item label="评分时间" v-if="detailForm.myRatingTime">{{
parseTime(detailForm.myRatingTime, '{y}-{m}-{d} {h}:{i}')
}}</el-descriptions-item>
<el-descriptions-item label="活动平均评分" v-if="detailForm.averageRating">
<div class="rating-cell">
<el-rate :model-value="detailForm.averageRating" disabled size="small" />
@@ -305,10 +297,7 @@
</el-table-column>
<el-table-column label="身份" align="center" width="80">
<template #default="{ row }">
<el-tag
:type="row.isLeader === 1 ? 'success' : 'info'"
size="small"
>
<el-tag :type="row.isLeader === 1 ? 'success' : 'info'" size="small">
{{ row.isLeader === 1 ? '队长' : '队员' }}
</el-tag>
</template>
@@ -321,14 +310,12 @@
</div>
<div v-else class="no-team-info">
<el-empty description="未找到队伍信息" :image-size="60">
<el-button type="primary" size="small" @click="getTeamInfo(detailForm.id)">
重新获取
</el-button>
<el-button type="primary" size="small" @click="getTeamInfo(detailForm.id)"> 重新获取 </el-button>
</el-empty>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="detailOpen = false">关 闭</el-button>
@@ -382,10 +369,10 @@
</el-dialog>
<!-- 签到对话框 -->
<el-dialog
title="活动签到"
v-model="attendanceOpen"
width="560px"
<el-dialog
title="活动签到"
v-model="attendanceOpen"
width="560px"
append-to-body
:close-on-click-modal="false"
:close-on-press-escape="true"
@@ -393,25 +380,11 @@
>
<div class="attendance-content">
<div class="attendance-actions">
<el-button
type="primary"
size="large"
:loading="signInLoading"
:disabled="!canSignIn"
@click="handleSignIn"
class="action-btn"
>
<el-button type="primary" size="large" :loading="signInLoading" :disabled="!canSignIn" @click="handleSignIn" class="action-btn">
{{ signInButtonText }}
</el-button>
<el-button
type="warning"
size="large"
:loading="signOutLoading"
:disabled="!canSignOut"
@click="handleSignOut"
class="action-btn"
>
<el-button type="warning" size="large" :loading="signOutLoading" :disabled="!canSignOut" @click="handleSignOut" class="action-btn">
签 退
</el-button>
</div>
@@ -420,24 +393,10 @@
<div class="attendance-records" v-if="attendanceRecords.length > 0">
<div class="records-header">
<span class="records-title">签到记录</span>
<el-button
type="text"
size="small"
@click="refreshRecords"
:loading="recordsLoading"
>
刷新
</el-button>
<el-button type="text" size="small" @click="refreshRecords" :loading="recordsLoading"> 刷新 </el-button>
</div>
<el-table
:data="attendanceRecords"
stripe
size="small"
class="records-table"
v-loading="recordsLoading"
:max-height="getTableMaxHeight()"
>
<el-table :data="attendanceRecords" stripe size="small" class="records-table" v-loading="recordsLoading" :max-height="getTableMaxHeight()">
<el-table-column prop="signInTime" label="签到时间" width="200">
<template #default="{ row }">
{{ formatDateTime(row.signInTime) }}
@@ -451,10 +410,7 @@
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag
:type="row.signOutTime ? 'success' : 'warning'"
size="small"
>
<el-tag :type="row.signOutTime ? 'success' : 'warning'" size="small">
{{ row.signOutTime ? '已完成' : '进行中' }}
</el-tag>
</template>
@@ -549,23 +505,23 @@ const getList = async () => {
const params = Object.fromEntries(
Object.entries(queryParams.value).filter(([_, value]) => value !== '' && value !== undefined && value !== null)
);
// 直接使用getMyActivities接口,后端已经处理了所有情况(包括用户作为队员参与的活动)
const res = await getMyActivities(params);
const activities = res.data || [];
// 按报名时间排序
activities.sort((a, b) => {
const timeA = a.myRegistrationTime ? new Date(a.myRegistrationTime).getTime() : 0;
const timeB = b.myRegistrationTime ? new Date(b.myRegistrationTime).getTime() : 0;
return timeB - timeA; // 降序排列
});
activityList.value = activities;
console.log('我的活动列表数据:', {
totalCount: activities.length,
activities: activities.map(activity => ({
activities: activities.map((activity) => ({
id: activity.id,
activityName: activity.activityName,
registrationType: activity.registrationType,
@@ -580,8 +536,6 @@ const getList = async () => {
}
};
/** 搜索按钮操作 */
const handleQuery = () => {
getList();
@@ -597,7 +551,7 @@ const resetQuery = () => {
const handleView = async (row: MyActivityVO) => {
detailForm.value = { ...row };
detailOpen.value = true;
// 如果是团队报名,获取队伍信息
if (row.registrationType === '2') {
await getTeamInfo(row.id);
@@ -609,7 +563,7 @@ const handleView = async (row: MyActivityVO) => {
/** 获取队伍信息 */
const getTeamInfo = async (activityId: string | number) => {
if (!activityId) return;
teamInfoLoading.value = true;
try {
const res = await getTeamsByActivityId(activityId);
@@ -621,7 +575,7 @@ const getTeamInfo = async (activityId: string | number) => {
teamInfo.value = null;
return;
}
const currentUserTeam = res.data.find((team: StglActivityTeamInfoVo) => {
// 检查是否为队长
if (team.leaderStudentNo === userInfo.userName) {
@@ -639,9 +593,9 @@ const getTeamInfo = async (activityId: string | number) => {
}
return false;
});
teamInfo.value = currentUserTeam || null;
if (currentUserTeam) {
console.log('获取到队伍信息:', {
teamName: currentUserTeam.teamName,
@@ -681,14 +635,12 @@ const parseTeamMembers = (teamMembersStr: string): any[] => {
}
};
/** 活动评分按钮操作 */
const handleRate = async (row: MyActivityVO) => {
try {
// 获取当前用户详细信息
const userInfo = await getCurrentUserDetailInfo();
ratingForm.value = {
activityId: row.id,
activityName: row.activityName,
@@ -699,7 +651,7 @@ const handleRate = async (row: MyActivityVO) => {
experienceScore: 0,
ratingComment: ''
};
console.log('评分表单数据:', ratingForm.value);
ratingOpen.value = true;
} catch (error) {
@@ -711,7 +663,7 @@ const handleRate = async (row: MyActivityVO) => {
/** 提交评分 */
const submitRating = async () => {
if (!ratingFormRef.value) return;
await ratingFormRef.value.validate(async (valid: boolean) => {
if (valid) {
ratingLoading.value = true;
@@ -821,8 +773,8 @@ const attendanceHistory = ref<any[]>([]);
const currentAttendance = ref<any>(null);
// 签到记录相关数据
const attendanceRecords = ref<any[]>([])
const recordsLoading = ref(false)
const attendanceRecords = ref<any[]>([]);
const recordsLoading = ref(false);
// 计算属性 - 简化状态判断
const canSignIn = computed(() => {
@@ -830,37 +782,35 @@ const canSignIn = computed(() => {
if (!currentAttendance.value) {
return true; // 没有记录时可以签到
}
// 如果最新记录已签到已签退,则可以重新签到
if (currentAttendance.value.signInStatus === 1 && currentAttendance.value.signOutStatus === 1) {
return true;
}
// 如果最新记录已签到未签退,则不能签到
if (currentAttendance.value.signInStatus === 1 && currentAttendance.value.signOutStatus === 0) {
return false;
}
// 其他情况允许签到
return true;
});
const canSignOut = computed(() => {
// 如果有记录且已签到未签退,则可以签退
return currentAttendance.value &&
currentAttendance.value.signInStatus === 1 &&
currentAttendance.value.signOutStatus === 0;
return currentAttendance.value && currentAttendance.value.signInStatus === 1 && currentAttendance.value.signOutStatus === 0;
});
const signInButtonText = computed(() => {
if (!currentAttendance.value) {
return '签到';
}
if (currentAttendance.value.signInStatus === 1 && currentAttendance.value.signOutStatus === 0) {
return '已签到';
}
return '签到';
});
@@ -868,7 +818,7 @@ const signInButtonText = computed(() => {
const handleAttendance = async (row: MyActivityVO) => {
try {
const userInfo = await getCurrentUserDetailInfo();
// 填充活动信息
attendanceForm.value = {
activityId: row.id,
@@ -929,13 +879,13 @@ const handleSignIn = async () => {
};
await createSignInRecord(signInData);
// 签到成功消息
proxy?.$modal.msgSuccess('签到成功');
// 刷新签到状态和历史记录
await refreshAttendanceData();
// 强制更新计算属性
nextTick(() => {
console.log('签到后状态更新:', {
@@ -980,7 +930,7 @@ const handleSignOut = async () => {
// 刷新签到状态和历史记录
await refreshAttendanceData();
// 强制更新计算属性
nextTick(() => {
console.log('签退后状态更新:', {
@@ -1003,19 +953,17 @@ const refreshAttendanceData = async () => {
try {
// 获取最新的签到记录
const recordRes = await getAllAttendanceRecords(attendanceForm.value.activityId, attendanceForm.value.studentNo);
if (recordRes.data && recordRes.data.length > 0) {
// 按创建时间排序,取最新的一条作为当前记录
const sortedRecords = recordRes.data.sort((a: any, b: any) =>
new Date(b.createTime).getTime() - new Date(a.createTime).getTime()
);
const sortedRecords = recordRes.data.sort((a: any, b: any) => new Date(b.createTime).getTime() - new Date(a.createTime).getTime());
currentAttendance.value = sortedRecords[0];
attendanceHistory.value = sortedRecords;
// 同时更新签到记录表格数据
attendanceRecords.value = sortedRecords;
console.log('获取到签到记录:', {
total: sortedRecords.length,
current: currentAttendance.value,
@@ -1030,7 +978,7 @@ const refreshAttendanceData = async () => {
// 强制触发计算属性重新计算
await nextTick();
console.log('刷新签到数据完成:', {
history: attendanceHistory.value,
current: currentAttendance.value,
@@ -1050,7 +998,7 @@ const refreshAttendanceData = async () => {
/** 刷新签到记录 */
const refreshRecords = async () => {
if (!attendanceForm.value.activityId || !attendanceForm.value.studentNo) return;
recordsLoading.value = true;
try {
await refreshAttendanceData();
@@ -1093,14 +1041,14 @@ const getTableMaxHeight = () => {
const calculateDuration = (signInTime: string, signOutTime: string): string => {
if (!signInTime) return '-';
if (!signOutTime) return '进行中';
const start = new Date(signInTime);
const end = new Date(signOutTime);
const diffMs = end.getTime() - start.getTime();
const hours = Math.floor(diffMs / (1000 * 60 * 60));
const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
if (hours > 0) {
return `${hours}小时${minutes}分钟`;
} else {
@@ -1111,9 +1059,9 @@ const calculateDuration = (signInTime: string, signOutTime: string): string => {
/** 提取指导老师姓名 */
const getInstructorNames = (instructors: string | any[]): string => {
if (!instructors) return '暂无';
let instructorArray: any[] = [];
// 如果是字符串,尝试解析JSON
if (typeof instructors === 'string') {
try {
@@ -1127,12 +1075,12 @@ const getInstructorNames = (instructors: string | any[]): string => {
} else {
return '暂无';
}
if (!instructorArray || instructorArray.length === 0) return '暂无';
return instructorArray
.map(instructor => instructor.name || instructor.nickName || '未知')
.filter(name => name && name !== '未知')
.map((instructor) => instructor.name || instructor.nickName || '未知')
.filter((name) => name && name !== '未知')
.join('、');
};
@@ -1423,4 +1371,4 @@ onMounted(() => {
display: flex;
justify-content: center;
}
</style>
</style>
+1 -10
View File
@@ -17,16 +17,9 @@
</el-col>
<el-col :span="6">
<el-form-item label="名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名称"
clearable
style="width: 100%"
@keyup.enter="handleQuery"
/>
<el-input v-model="queryParams.name" placeholder="请输入名称" clearable style="width: 100%" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
</el-row>
<el-row>
@@ -228,8 +221,6 @@ const getRegistrationTypeTag = (type: string) => {
return tagMap[type as keyof typeof tagMap] || ('info' as const);
};
/** 获取当前状态标签样式 */
const getCurrentStatusTag = (status: string) => {
const tagMap = {
+4 -10
View File
@@ -202,9 +202,7 @@ import {
import { StglClassQuery, StglClassVO, StglClassDetailVO, ClubDetailVO, VolunteerTeamDetailVO, ActivityDetailVO } from '@/api/stgl/stclass/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_user_sex, stgl_club_type, stgl_activity_type } = toRefs<any>(
proxy?.useDict('sys_user_sex', 'stgl_club_type', 'stgl_activity_type')
);
const { sys_user_sex, stgl_club_type, stgl_activity_type } = toRefs<any>(proxy?.useDict('sys_user_sex', 'stgl_club_type', 'stgl_activity_type'));
const loading = ref(false);
const showSearch = ref(true);
@@ -308,7 +306,7 @@ const handleClassChange = () => {
/** 查看按钮操作 */
const handleView = (row?: StglClassVO) => {
const _row = row || studentList.value.find(item => item.id === ids.value[0]);
const _row = row || studentList.value.find((item) => item.id === ids.value[0]);
if (!_row) {
proxy?.$modal.msgError('请选择要查看的学生');
return;
@@ -364,11 +362,7 @@ const handleViewActivities = async (row: StglClassVO) => {
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'stgl/stclass/export',
queryParams.value,
`班级学生参与情况_${new Date().getTime()}.xlsx`
);
proxy?.download('stgl/stclass/export', queryParams.value, `班级学生参与情况_${new Date().getTime()}.xlsx`);
};
onMounted(() => {
@@ -452,4 +446,4 @@ onMounted(() => {
.activity-item:last-child {
margin-bottom: 0;
}
</style>
</style>
@@ -3,26 +3,26 @@
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="always" style="padding: 8px 16px;">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0;">
<el-card shadow="always" style="padding: 8px 16px">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0">
<el-row :gutter="16" align="middle">
<el-col :span="5">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0;">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0">
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0;">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="联系方式" prop="phone" style="margin-bottom: 0;">
<el-form-item label="联系方式" prop="phone" style="margin-bottom: 0">
<el-input v-model="queryParams.phone" placeholder="请输入联系方式" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -39,16 +39,27 @@
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-tooltip :content="getAddButtonTooltip" placement="top" :disabled="!isAddButtonDisabled">
<el-button type="primary" plain icon="Plus" @click="handleAdd" :disabled="isAddButtonDisabled" v-hasPermi="['stgl:volunteermember:add']">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
:disabled="isAddButtonDisabled"
v-hasPermi="['stgl:volunteermember:add']"
>
新增
</el-button>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['stgl:volunteermember:edit']">修改</el-button>
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['stgl:volunteermember:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['stgl:volunteermember:remove']">删除</el-button>
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['stgl:volunteermember:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-dropdown class="mt-[1px]">
@@ -259,7 +270,7 @@
是否更新已经存在的志愿者成员数据
</div>
<span>仅允许导入xls、xlsx格式文件。</span>
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
</div>
</template>
</el-upload>
@@ -308,7 +319,9 @@ interface ExtendedVolunteerMemberForm extends VolunteerMemberForm {
const props = defineProps<Props>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_user_sex, sys_normal_disable, stgl_member_status } = toRefs<any>(proxy?.useDict('sys_user_sex', 'sys_normal_disable', 'stgl_member_status'));
const { sys_user_sex, sys_normal_disable, stgl_member_status } = toRefs<any>(
proxy?.useDict('sys_user_sex', 'sys_normal_disable', 'stgl_member_status')
);
const memberList = ref<VolunteerMemberVO[]>([]);
const buttonLoading = ref(false);
@@ -401,7 +414,7 @@ const { queryParams, form, rules } = toRefs(data);
const isAddButtonDisabled = computed(() => {
// 使用本地成员数量,确保实时更新
const currentCount = memberList.value.length;
if (!props.memberLimit || props.memberLimit <= 0) {
return false; // 没有设置上限,允许新增
}
@@ -411,7 +424,7 @@ const isAddButtonDisabled = computed(() => {
// 计算属性:新增按钮的提示信息
const getAddButtonTooltip = computed(() => {
const currentCount = memberList.value.length;
if (!props.memberLimit || props.memberLimit <= 0) {
return '该团队未设置成员上限';
}
@@ -442,30 +455,30 @@ const getList = async () => {
try {
// 构建查询参数,支持多状态查询
const queryData = { ...queryParams.value };
console.log('JoinedMembersTab: getList 开始查询,参数:', queryData);
// 构建params对象,包含所有需要在params中的查询条件
queryData.params = { ...queryData.params } as any;
// 将phone等查询条件放入params中(后端期望params.phone)
if (queryData.phone) {
(queryData.params as any).phone = queryData.phone;
delete queryData.phone; // 删除顶级phone字段
}
// 如果状态字段为空或未选择,则查询已加入(status=2)和已退出(status=4)的成员
if (!queryData.status) {
// 使用params传递多状态查询,传递字符串格式
(queryData.params as any).statusList = '2,4';
delete queryData.status; // 删除单个status字段
}
console.log('JoinedMembersTab: 最终查询参数:', queryData);
const res = await listVolunteerMember(queryData);
console.log('JoinedMembersTab: 查询结果:', res);
memberList.value = res.rows;
total.value = res.total;
} catch (error) {
@@ -520,7 +533,7 @@ const handleSelectionChange = (selection: VolunteerMemberVO[]) => {
const handleAdd = () => {
// 检查人员上限 - 使用本地成员数量,确保实时更新
const currentCount = memberList.value.length;
if (props.memberLimit && props.memberLimit > 0) {
if (currentCount >= props.memberLimit) {
proxy?.$modal.msgError(`该团队成员已满,当前成员数量:${currentCount},成员上限:${props.memberLimit},无法再新增成员`);
@@ -569,20 +582,20 @@ const submitForm = () => {
return;
}
}
buttonLoading.value = true;
// 创建提交数据,排除selectedStudent字段,确保包含userId和memberType
const { selectedStudent, ...submitData } = form.value;
// 确保memberType为2(志愿者成员)
submitData.memberType = 2;
// 确保status为2(已加入状态)
if (!submitData.status) {
submitData.status = '2';
}
if (form.value.id) {
await updateVolunteerMember(submitData as VolunteerMemberForm).finally(() => (buttonLoading.value = false));
} else {
@@ -601,14 +614,14 @@ const submitForm = () => {
const handleDelete = async (row?: VolunteerMemberVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除选中的志愿者成员?');
// 处理单个删除或批量删除
if (Array.isArray(_ids)) {
await delVolunteerMember(_ids.join(','));
} else {
await delVolunteerMember(_ids);
}
proxy?.$modal.msgSuccess('删除成功');
await getList();
// 通知父组件成员数量变化
@@ -693,7 +706,7 @@ const handleFileSuccess = (response: any) => {
getList();
// 通知父组件成员数量变化
notifyMemberCountChange();
// 检查导入后是否超过成员上限
setTimeout(() => {
const currentCount = memberList.value.length;
@@ -711,7 +724,7 @@ defineExpose({
// 定义事件,用于通知父组件数据变化
const emit = defineEmits<{
memberCountChanged: [newCount: number]
memberCountChanged: [newCount: number];
}>();
// 通知父组件成员数量变化
@@ -741,4 +754,4 @@ onMounted(() => {
white-space: pre-wrap;
word-break: break-word;
}
</style>
</style>
@@ -3,28 +3,23 @@
<el-tabs v-model="activeTab" type="border-card" :key="teamId" @tab-change="handleTabChange">
<!-- 已加入标签页 -->
<el-tab-pane label="已加入" name="joined">
<JoinedMembersTab
<JoinedMembersTab
ref="joinedTabRef"
:team-id="teamId"
:team-name="teamName"
:team-id="teamId"
:team-name="teamName"
:member-limit="memberLimit"
:current-member-count="currentMemberCount"
:key="`joined-${teamId}`"
:key="`joined-${teamId}`"
@member-count-changed="handleMemberCountChanged"
/>
</el-tab-pane>
<!-- 待审核标签页 -->
<el-tab-pane label="待审核" name="pending">
<PendingMembersTab
ref="pendingTabRef"
:team-id="teamId"
:team-name="teamName"
:key="`pending-${teamId}`"
/>
<PendingMembersTab ref="pendingTabRef" :team-id="teamId" :team-name="teamName" :key="`pending-${teamId}`" />
</el-tab-pane>
</el-tabs>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleClose">关 闭</el-button>
@@ -59,27 +54,33 @@ const joinedTabRef = ref();
const pendingTabRef = ref();
// 监听visible变化
watch(() => props.visible, (newVal) => {
if (newVal) {
activeTab.value = 'joined';
// 当弹窗打开时,触发当前标签页的数据加载
nextTick(() => {
if (activeTab.value === 'joined' && joinedTabRef.value) {
joinedTabRef.value.getList();
} else if (activeTab.value === 'pending' && pendingTabRef.value) {
pendingTabRef.value.getList();
}
});
watch(
() => props.visible,
(newVal) => {
if (newVal) {
activeTab.value = 'joined';
// 当弹窗打开时,触发当前标签页的数据加载
nextTick(() => {
if (activeTab.value === 'joined' && joinedTabRef.value) {
joinedTabRef.value.getList();
} else if (activeTab.value === 'pending' && pendingTabRef.value) {
pendingTabRef.value.getList();
}
});
}
}
});
);
// 监听teamId变化,重置标签页
watch(() => props.teamId, (newTeamId) => {
console.log('MemberManagementDialog: teamId 变化:', newTeamId);
if (newTeamId) {
activeTab.value = 'joined';
watch(
() => props.teamId,
(newTeamId) => {
console.log('MemberManagementDialog: teamId 变化:', newTeamId);
if (newTeamId) {
activeTab.value = 'joined';
}
}
});
);
const handleClose = () => {
emit('update:visible', false);
@@ -98,7 +99,7 @@ const handleMemberCountChanged = (newCount: number) => {
/** 标签页切换处理 */
const handleTabChange = (tabName: string) => {
console.log('标签页切换到:', tabName);
// 根据当前激活的标签页,调用对应组件的刷新方法
if (tabName === 'joined' && joinedTabRef.value) {
// 调用已加入成员Tab的刷新方法
@@ -118,4 +119,4 @@ const handleTabChange = (tabName: string) => {
.dialog-footer {
text-align: right;
}
</style>
</style>
@@ -3,26 +3,26 @@
<!-- 搜索区域 -->
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="always" style="padding: 8px 16px;">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0;">
<el-card shadow="always" style="padding: 8px 16px">
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0">
<el-row :gutter="16" align="middle">
<el-col :span="5">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0;">
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0">
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0;">
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0">
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="联系方式" prop="phone" style="margin-bottom: 0;">
<el-form-item label="联系方式" prop="phone" style="margin-bottom: 0">
<el-input v-model="queryParams.phone" placeholder="请输入联系方式" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item style="margin-bottom: 0;">
<el-form-item style="margin-bottom: 0">
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
@@ -38,10 +38,14 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="success" plain icon="Select" :disabled="multiple" @click="handleBatchApprove" v-hasPermi="['stgl:volunteermember:edit']">批量通过</el-button>
<el-button type="success" plain icon="Select" :disabled="multiple" @click="handleBatchApprove" v-hasPermi="['stgl:volunteermember:edit']"
>批量通过</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Close" :disabled="multiple" @click="handleBatchReject" v-hasPermi="['stgl:volunteermember:edit']">批量拒绝</el-button>
<el-button type="danger" plain icon="Close" :disabled="multiple" @click="handleBatchReject" v-hasPermi="['stgl:volunteermember:edit']"
>批量拒绝</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@@ -130,12 +134,7 @@
<script setup lang="ts">
import { getCurrentInstance, onMounted, reactive, ref, toRefs } from 'vue';
import {
listVolunteerMember,
getVolunteerMember,
updateVolunteerMember,
approveVolunteerMember
} from '@/api/stgl/volunteermember';
import { listVolunteerMember, getVolunteerMember, updateVolunteerMember, approveVolunteerMember } from '@/api/stgl/volunteermember';
import { VolunteerMemberVO, VolunteerMemberQuery } from '@/api/stgl/volunteermember/types';
import { parseTime } from '@/utils/ruoyi';
@@ -147,7 +146,9 @@ interface Props {
const props = defineProps<Props>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_user_sex, sys_normal_disable, stgl_member_status } = toRefs<any>(proxy?.useDict('sys_user_sex', 'sys_normal_disable', 'stgl_member_status'));
const { sys_user_sex, sys_normal_disable, stgl_member_status } = toRefs<any>(
proxy?.useDict('sys_user_sex', 'sys_normal_disable', 'stgl_member_status')
);
const memberList = ref<VolunteerMemberVO[]>([]);
const loading = ref(true);
@@ -204,19 +205,19 @@ const validateMemberData = (member: VolunteerMemberVO, operation: string): boole
console.error(`${operation}失败 - 用户ID缺失:`, member);
return false;
}
if (!member.relationId && !props.teamId) {
proxy?.$modal.msgError(`${operation}失败:关联ID(志愿者队伍ID)不能为空`);
console.error(`${operation}失败 - 关联ID缺失:`, member);
return false;
}
if (!member.studentNo || !member.studentName) {
proxy?.$modal.msgError(`${operation}失败:学号和姓名不能为空`);
console.error(`${operation}失败 - 基础信息缺失:`, member);
return false;
}
return true;
};
@@ -226,31 +227,31 @@ const getList = async () => {
try {
// 构建查询参数,支持多状态查询
const queryData = { ...queryParams.value };
console.log('PendingMembersTab: getList 开始查询,参数:', queryData);
// 构建params对象,包含所有需要在params中的查询条件
queryData.params = { ...queryData.params } as any;
// 将phone等查询条件放入params中(后端期望params.phone)
if (queryData.phone) {
(queryData.params as any).phone = queryData.phone;
console.log('PendingMembersTab: phone参数放入params:', queryData.phone);
delete queryData.phone; // 删除顶级phone字段
}
// 如果状态字段为空或未选择,则查询待审核(status=0)和已驳回(status=3)的成员
if (!queryData.status) {
// 使用params传递多状态查询,保留现有params内容,传递字符串格式
(queryData.params as any).statusList = '0,3';
delete queryData.status; // 删除单个status字段
}
console.log('PendingMembersTab: 最终查询参数:', queryData);
const res = await listVolunteerMember(queryData);
console.log('PendingMembersTab: 查询结果:', res);
// 防御性编程:检查响应数据结构
if (!res || !res.rows) {
console.warn('PendingMembersTab: API返回数据格式异常:', res);
@@ -259,7 +260,7 @@ const getList = async () => {
} else {
memberList.value = res.rows;
total.value = res.total;
// 调试:检查数据完整性
if (memberList.value.length > 0) {
console.log('PendingMembersTab: 第一条成员数据:', memberList.value[0]);
@@ -322,9 +323,9 @@ const handleApprove = async (row: VolunteerMemberVO) => {
if (!validateMemberData(row, '审核通过')) {
return;
}
await proxy?.$modal.confirm(`确认通过"${row.studentName}"的入队申请?`);
await approveVolunteerMember(row.id, true);
proxy?.$modal.msgSuccess('审核通过成功');
await getList();
@@ -336,9 +337,9 @@ const handleReject = async (row: VolunteerMemberVO) => {
if (!validateMemberData(row, '审核拒绝')) {
return;
}
await proxy?.$modal.confirm(`确认拒绝"${row.studentName}"的入队申请?`);
await approveVolunteerMember(row.id, false);
proxy?.$modal.msgSuccess('审核拒绝成功');
await getList();
@@ -350,25 +351,25 @@ const handleBatchApprove = async () => {
proxy?.$modal.msgWarning('请选择要审核的成员');
return;
}
await proxy?.$modal.confirm(`确认批量通过所选成员的入队申请?`);
const promises = ids.value.map(id => {
const promises = ids.value.map((id) => {
// 根据ID找到对应的成员记录,进行数据验证
const member = memberList.value.find(m => m.id === id);
const member = memberList.value.find((m) => m.id === id);
if (!member) {
proxy?.$modal.msgError(`未找到ID为${id}的成员记录`);
return Promise.reject(new Error(`未找到ID为${id}的成员记录`));
}
// 验证数据完整性
if (!validateMemberData(member, '批量审核通过')) {
return Promise.reject(new Error(`成员${member.studentName}数据不完整`));
}
return approveVolunteerMember(id, true);
});
await Promise.all(promises);
proxy?.$modal.msgSuccess('批量审核通过成功');
await getList();
@@ -380,25 +381,25 @@ const handleBatchReject = async () => {
proxy?.$modal.msgWarning('请选择要审核的成员');
return;
}
await proxy?.$modal.confirm(`确认批量拒绝所选成员的入队申请?`);
const promises = ids.value.map(id => {
const promises = ids.value.map((id) => {
// 根据ID找到对应的成员记录,进行数据验证
const member = memberList.value.find(m => m.id === id);
const member = memberList.value.find((m) => m.id === id);
if (!member) {
proxy?.$modal.msgError(`未找到ID为${id}的成员记录`);
return Promise.reject(new Error(`未找到ID为${id}的成员记录`));
}
// 验证数据完整性
if (!validateMemberData(member, '批量审核拒绝')) {
return Promise.reject(new Error(`成员${member.studentName}数据不完整`));
}
return approveVolunteerMember(id, false);
});
await Promise.all(promises);
proxy?.$modal.msgSuccess('批量审核拒绝成功');
await getList();
@@ -486,4 +487,4 @@ onMounted(() => {
min-width: auto;
}
}
</style>
</style>
+1 -1
View File
@@ -1199,7 +1199,7 @@ const handleActivityManagement = (row) => {
relatedTeamName: row.teamName,
from: 'team'
});
router.push({
path: '/stgl/activity/info',
query: {