This commit is contained in:
zlwu committed 2025-08-26 00:42:25 +08:00
commit 94fdca480f
8 files changed
+1019 -59

No files matched your search

+313 -27
View File
@@ -84,19 +84,28 @@
<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 && !['2', '3', '4', '5'].includes(selectedActivity.activityStatus)">
<el-button icon="Edit" @click="handleUpdate()" v-hasPermi="['stgl:activity:edit']">修改</el-button>
</el-col>
<el-col :span="1.5" v-if="!single && selectedActivity && ['0', '7', '6'].includes(selectedActivity.activityStatus) && 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 && selectedActivity.activityStatus === '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 && selectedActivity.activityStatus === '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>
<el-col :span="1.5">
<el-button
icon="Delete"
:disabled="multiple || (selectedActivity && selectedActivity.activityStatus !== '1')"
:disabled="multiple || !['0', '7'].includes(selectedActivity?.activityStatus || '')"
@click="handleDelete()"
v-hasPermi="['stgl:activity:remove']"
:title="selectedActivity && selectedActivity.activityStatus !== '1' ? '只有筹备中的活动可以删除' : ''"
:title="selectedActivity && !['0', '7'].includes(selectedActivity.activityStatus) ? '只有草稿和已驳回状态的活动可以删除' : ''"
>删除</el-button
>
</el-col>
@@ -195,7 +204,7 @@
v-hasPermi="['stgl:activity:query']"
></el-button>
</el-tooltip>
<el-tooltip content="修改" placement="top" v-if="!['2', '3', '4', '5'].includes(scope.row.activityStatus)">
<el-tooltip content="修改" placement="top" v-if="['0', '7', '6'].includes(scope.row.activityStatus) && hasActivityOperationPermission(scope.row)">
<el-button
link
type="primary"
@@ -206,6 +215,39 @@
v-hasPermi="['stgl:activity:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="提交" placement="top" v-if="scope.row.activityStatus === '0' && hasActivityOperationPermission(scope.row)">
<el-button
link
type="success"
icon="Upload"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleSubmit(scope.row)"
v-hasPermi="['stgl:activity:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="审批" placement="top" v-if="scope.row.activityStatus === '2' && hasActivityOperationPermission(scope.row)">
<el-button
link
type="warning"
icon="Check"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleApprove(scope.row)"
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-button
link
type="danger"
icon="Close"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleCancel(scope.row)"
v-hasPermi="['stgl:activity:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="复制" placement="top">
<el-button
link
@@ -217,7 +259,7 @@
v-hasPermi="['stgl:activity:add']"
></el-button>
</el-tooltip>
<el-tooltip content="活动总结" placement="top" v-if="shouldShowSummaryButton(scope.row)">
<el-tooltip content="活动总结" placement="top" v-if="shouldShowSummaryButton(scope.row) && hasActivityOperationPermission(scope.row)">
<el-button
link
type="success"
@@ -239,7 +281,7 @@
v-hasPermi="['stgl:activity:query']"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top" v-if="scope.row.activityStatus === '1'">
<el-tooltip content="删除" placement="top" v-if="['0', '7'].includes(scope.row.activityStatus) && hasActivityOperationPermission(scope.row)">
<el-button
link
type="danger"
@@ -315,16 +357,9 @@
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="活动状态" prop="activityStatus">
<el-select v-model="form.activityStatus" placeholder="请选择活动状态" style="width: 100%">
<el-option v-for="dict in stgl_activity_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="关联社团" prop="relatedClubId">
<ClubSelector v-model="form.relatedClub" @change="handleRelatedClubChange" />
<ClubSelector v-model="form.relatedClub" @change="handleRelatedClubChange" :disabled="isRelatedClubDisabled" />
</el-form-item>
</el-col>
</el-row>
@@ -519,6 +554,37 @@
:activity-name="selectedActivity?.activityName"
ref="membersDialogRef"
/>
<!-- 审批对话框 -->
<el-dialog v-model="approveDialog.visible" title="审批活动" width="500px" append-to-body>
<el-form ref="approveFormRef" :model="approveForm" label-width="100px">
<el-form-item label="活动名称">
<el-input v-model="approveForm.activityName" readonly />
</el-form-item>
<el-form-item label="审批结果" prop="approvalResult">
<el-radio-group v-model="approveForm.approvalResult">
<el-radio label="approve">通过</el-radio>
<el-radio label="reject">驳回</el-radio>
</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-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="approveDialog.visible = false">取消</el-button>
<el-button type="primary" @click="confirmApprove" :loading="approveLoading">确认审批</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
@@ -545,10 +611,12 @@ import ActivityDetailView from './components/ActivityDetailView.vue';
import ActivityMembersDialog from './components/ActivityMembersDialog.vue';
import ActivityRegistrationView from './components/ActivityRegistrationView.vue'; // Added import
import { AdvisorVO } from '@/components/AdvisorManager/index.vue';
import { useUserStore } from '@/store/modules/user';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
const { stgl_activity_type, stgl_activity_status, stgl_registration_type, stgl_registration_status, sys_yes_no } = toRefs<any>(
proxy?.useDict('stgl_activity_type', 'stgl_activity_status', 'stgl_registration_type', 'stgl_registration_status', 'sys_yes_no')
);
@@ -570,6 +638,7 @@ const queryFormRef = ref<ElFormInstance>();
const activityFormRef = ref<ElFormInstance>();
const copyFormRef = ref<ElFormInstance>();
const summaryFormRef = ref<ElFormInstance>();
const approveFormRef = ref<ElFormInstance>();
const membersDialogRef = ref();
const dialog = reactive<DialogOption>({
@@ -597,8 +666,26 @@ const membersDialog = reactive<DialogOption>({
title: ''
});
// 审批对话框
const approveDialog = reactive<DialogOption>({
visible: false,
title: ''
});
// 审批表单
const approveForm = reactive({
id: undefined as string | number | undefined,
activityName: '',
approvalResult: 'approve' as 'approve' | 'reject',
rejectReason: ''
});
const copyLoading = ref(false);
const summaryLoading = ref(false);
const approveLoading = ref(false);
// 关联社团禁用状态控制
const isRelatedClubDisabled = ref(false);
const initFormData: StglActivityForm = {
id: undefined,
@@ -631,7 +718,7 @@ const initFormData: StglActivityForm = {
teamSizeMin: undefined,
teamSizeMax: undefined,
needApproval: 1,
activityStatus: '1',
activityStatus: '0',
isFinished: 0,
summary: undefined,
achievements: undefined,
@@ -668,8 +755,7 @@ const data = reactive<PageData<StglActivityForm, StglActivityQuery & { relatedCl
activityType: [{ required: true, message: '活动类型不能为空', trigger: 'change' }],
startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],
endTime: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }],
organizerName: [{ required: true, message: '主办方名称不能为空', trigger: 'blur' }],
activityStatus: [{ required: true, message: '活动状态不能为空', trigger: 'change' }]
organizerName: [{ required: true, message: '主办方名称不能为空', trigger: 'blur' }]
}
});
@@ -753,6 +839,11 @@ const getList = async () => {
queryParams.value.endTime = undefined;
}
// 如果用户是社长角色,只能查看自己创建的活动
if (proxy?.$auth.hasRole('stgl_president')) {
queryParams.value.createBy = userStore.userId;
}
try {
const res = await listStglActivity(queryParams.value);
activityList.value = res.rows;
@@ -781,6 +872,15 @@ const reset = () => {
form.value.instructorList = [];
form.value.instructors = undefined;
// 重置关联社团禁用状态
isRelatedClubDisabled.value = false;
// 重置审批表单
approveForm.id = undefined;
approveForm.activityName = '';
approveForm.approvalResult = 'approve';
approveForm.rejectReason = '';
// 更新formKey以强制重新渲染组件
formKey.value++;
@@ -839,6 +939,9 @@ const handleAdd = () => {
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,
@@ -851,6 +954,9 @@ const handleAdd = () => {
formRelatedClubId: form.value.relatedClubId,
formRelatedType: form.value.relatedType
});
} else {
// 没有关联组织时,允许编辑关联社团字段
isRelatedClubDisabled.value = false;
}
dialog.visible = true;
@@ -874,8 +980,8 @@ const handleUpdate = async (row?: StglActivityVO) => {
// 先获取活动数据检查状态
const res = await getStglActivity(_id);
// 检查活动状态是否允许修改(2:报名中、3:报名结束、4:进行中、5:已结束 不允许修改)
if (['2', '3', '4', '5'].includes(res.data.activityStatus)) {
// 检查活动状态是否允许修改(只有草稿0、已驳回7、已取消6状态允许修改)
if (!['0', '7', '6'].includes(res.data.activityStatus)) {
const statusDict = stgl_activity_status.value.find((dict: any) => dict.value === res.data.activityStatus);
const statusName = statusDict ? statusDict.label : '当前状态';
proxy?.$modal.msgWarning(`活动状态为"${statusName}",不允许修改`);
@@ -896,6 +1002,15 @@ 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;
} else {
isRelatedClubDisabled.value = false;
}
} else {
isRelatedClubDisabled.value = false;
}
// 处理承办方数据
@@ -945,6 +1060,11 @@ const submitForm = () => {
// 准备提交的数据
const submitData = { ...form.value } as any;
// 新增时自动设置活动状态为0(草稿)
if (!submitData.id) {
submitData.activityStatus = '0';
}
// 处理关联社团数据
if (submitData.relatedClub) {
submitData.relatedClubId = submitData.relatedClub.relatedClubId;
@@ -1032,23 +1152,23 @@ const handleDelete = async (row?: StglActivityVO) => {
const _ids = row?.id || ids.value;
let confirmMessage = '';
// 检查删除权限 - 只有筹备中(状态1)的活动可以删除
// 检查删除权限 - 只有草稿(状态0)和已驳回(状态7)的活动可以删除
if (row) {
if (row.activityStatus !== '1') {
if (!['0', '7'].includes(row.activityStatus)) {
const statusDict = stgl_activity_status.value.find((dict: any) => dict.value === row.activityStatus);
const statusName = statusDict ? statusDict.label : '当前状态';
proxy?.$modal.msgWarning(`活动状态为"${statusName}",只有筹备中的活动可以删除`);
proxy?.$modal.msgWarning(`活动状态为"${statusName}",只有草稿和已驳回状态的活动可以删除`);
return;
}
confirmMessage = `是否确认删除活动"${row.activityName}"?`;
} else {
const selectedItems = activityList.value.filter((item) => ids.value.includes(item.id));
// 检查选中的活动是否都是筹备中状态
const nonDeletableItems = selectedItems.filter((item) => item.activityStatus !== '1');
// 检查选中的活动是否都是草稿或已驳回状态
const nonDeletableItems = selectedItems.filter((item) => !['0', '7'].includes(item.activityStatus));
if (nonDeletableItems.length > 0) {
const names = nonDeletableItems.map((item) => item.activityName).join('、');
proxy?.$modal.msgWarning(`活动"${names}"不是筹备中状态,只有筹备中的活动可以删除`);
proxy?.$modal.msgWarning(`活动"${names}"不是草稿或已驳回状态,只有草稿和已驳回状态的活动可以删除`);
return;
}
@@ -1273,6 +1393,127 @@ const handleMembersReview = (row: StglActivityVO) => {
membersDialog.title = `${row.activityName}活动报名管理`;
};
/** 提交按钮操作 */
const handleSubmit = async (row?: StglActivityVO) => {
const activity = row || selectedActivity.value;
if (!activity) return;
try {
await proxy?.$modal.confirm(`是否确认提交活动"${activity.activityName}"?提交后将进入审批流程。`);
// 调用后端接口更新活动状态为2(待审批)
// 传递完整的活动数据,避免后端验证失败
await updateStglActivity({
...activity,
activityStatus: '2'
});
proxy?.$modal.msgSuccess('提交成功');
await getList();
} catch (error) {
if (error !== 'cancel') {
console.error('提交失败:', error);
proxy?.$modal.msgError('提交失败');
}
}
};
/** 审批按钮操作 */
const handleApprove = async (row?: StglActivityVO) => {
const activity = row || selectedActivity.value;
if (!activity) return;
// 显示审批对话框
approveDialog.visible = true;
approveDialog.title = `审批活动 - ${activity.activityName}`;
approveForm.id = activity.id;
approveForm.activityName = activity.activityName || '';
approveForm.approvalResult = 'approve';
approveForm.rejectReason = '';
};
/** 取消活动按钮操作 */
const handleCancel = async (row?: StglActivityVO) => {
const activity = row || selectedActivity.value;
if (!activity) return;
try {
await proxy?.$modal.confirm(`是否确认取消活动"${activity.activityName}"?取消后将无法恢复。`);
// 调用后端接口更新活动状态为6(已取消)
// 传递完整的活动数据,避免后端验证失败
await updateStglActivity({
...activity,
activityStatus: '6'
});
proxy?.$modal.msgSuccess('取消活动成功');
await getList();
} catch (error) {
if (error !== 'cancel') {
console.error('取消活动失败:', error);
proxy?.$modal.msgError('取消活动失败');
}
}
};
/** 确认审批 */
const confirmApprove = async () => {
if (!approveFormRef.value) return;
approveLoading.value = true;
try {
const activity = activityList.value.find(item => item.id === approveForm.id);
if (!activity) {
proxy?.$modal.msgError('活动信息不存在');
return;
}
let newStatus: string;
let successMessage: string;
if (approveForm.approvalResult === 'approve') {
// 审批通过,根据时间比较确定状态
const now = new Date();
const startTime = new Date(activity.startTime);
const endTime = new Date(activity.endTime);
if (now < startTime) {
newStatus = '1'; // 未开始
} else if (now >= startTime && now <= endTime) {
newStatus = '4'; // 进行中
} else {
newStatus = '5'; // 已结束
}
successMessage = '审批通过';
} else {
// 审批驳回
if (!approveForm.rejectReason.trim()) {
proxy?.$modal.msgWarning('请输入驳回原因');
return;
}
newStatus = '7'; // 已驳回
successMessage = '审批驳回';
}
// 调用后端接口更新活动状态
// 传递完整的活动数据,避免后端验证失败
await updateStglActivity({
...activity,
activityStatus: newStatus
});
proxy?.$modal.msgSuccess(successMessage);
approveDialog.visible = false;
await getList();
} catch (error) {
console.error('审批失败:', error);
proxy?.$modal.msgError('审批失败');
} finally {
approveLoading.value = false;
}
};
/** 格式化日期 */
const formatDate = (dateStr: string): string => {
if (!dateStr) return '';
@@ -1303,6 +1544,51 @@ const getAdvisorNames = (activity: StglActivityVO): string[] => {
return [];
};
// 检查是否拥有指导老师或管理员权限
const hasInstructorPermission = computed(() => {
return proxy?.$auth.hasRoleOr(['stgl_instructor', 'stgl_stgl_admin', 'superadmin']);
});
// 检查当前用户是否为指定活动的指导老师
const isCurrentUserAdvisor = (activity: StglActivityVO): boolean => {
if (!activity.instructors) return false;
try {
const advisors = JSON.parse(activity.instructors);
if (Array.isArray(advisors)) {
return advisors.some((advisor: any) => {
// 支持两种数据结构:直接包含userId或嵌套在teacher对象中
const userId = advisor.userId || advisor.teacher?.userId;
return userId && userId.toString() === userStore.userId?.toString();
});
}
} catch (error) {
console.warn('解析活动指导老师数据失败:', error);
}
return false;
};
// 检查当前用户是否有活动操作权限(指导老师只能操作自己指导的活动)
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;
}
return false;
};
onMounted(() => {
initUrlParams();
getList();
+26 -7
View File
@@ -26,6 +26,8 @@
</el-col>
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
@@ -619,6 +621,18 @@
</template>
<script setup name="StglClub" lang="ts">
/**
* 社团信息管理
*
* 权限控制说明:
* - stgl_president角色:可以操作自己担任社长的社团(无论是否为创建者)
* - stgl_instructor角色:指导老师,有部分字段的只读权限
* - 管理员角色:可以操作所有社团
*
* 业务逻辑:
* 管理员创建社团后选择社长,被选中的社长(stgl_president角色)
* 需要能够看到和操作自己担任社长的社团,不再受限于创建者身份
*/
import { useRouter } from 'vue-router';
import {
listStglClub,
@@ -646,9 +660,15 @@ const { stgl_club_type, sys_normal_disable, stgl_funding_source, sys_yes_no } =
// 检查是否拥有指导老师或管理员权限
const hasInstructorPermission = computed(() => {
return proxy?.$auth.hasRoleOr(['stgl_instructor', 'stgl_admin', 'superadmin']);
return proxy?.$auth.hasRoleOr(['stgl_instructor', 'stgl_stgl_admin', 'superadmin']);
});
// 社长权限控制 - 检查用户是否为各社团的社长(无论是否为创建者)
// 用于控制转让按钮显示和社长相关操作权限
const presidentRoles = ref<Record<string | number, boolean>>({});
const clubList = ref<StglClubVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
@@ -660,9 +680,6 @@ const total = ref(0);
const activeTab = ref('basic');
const charterDownloading = ref(false);
// 社长权限控制
const presidentRoles = ref<Record<string | number, boolean>>({});
const queryFormRef = ref<ElFormInstance>();
const clubFormRef = ref<ElFormInstance>();
@@ -1281,7 +1298,7 @@ const handleAdvisorsChange = (advisors: AdvisorVO[]) => {
});
};
/** 检查用户是否为指定社团的社长 */
/** 检查用户是否为指定社团的社长(无论是否为创建者) */
const checkIsPresident = async (clubId: string | number): Promise<boolean> => {
try {
console.log('🔍 checkIsPresident: 开始检查社长权限, clubId:', clubId);
@@ -1296,7 +1313,7 @@ const checkIsPresident = async (clubId: string | number): Promise<boolean> => {
}
};
/** 批量检查社长权限 */
/** 批量检查社长权限(检查用户是否为各社团的社长,无论是否为创建者) */
const checkBatchPresidentRoles = async (clubs: StglClubVO[]) => {
console.log('🔍 checkBatchPresidentRoles: 开始批量检查权限, clubs数量:', clubs.length);
const checks = clubs.map(async (club) => {
@@ -1334,7 +1351,7 @@ const shouldShowTransferButton = (club: StglClubVO): boolean => {
console.log('⏳ shouldShowTransferButton: 权限检查未完成,返回false避免闪现');
return false;
}
// 只有社长才能看到转让按钮
// 社长可以看到转让按钮(无论是否为创建者)
const result = isPresident === true;
console.log('🔍 shouldShowTransferButton: stgl_president角色最终结果:', result);
return result;
@@ -1689,6 +1706,8 @@ onMounted(() => {
}
}
/* 人员信息部分样式 */
.personnel-section {
margin-bottom: 24px;