Files
hzdyjs/web/src/views/stgl/activity/index.vue
T
2025-08-27 22:30:18 +08:00

1840 lines
66 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="p-2 page-container">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" label-width="100px">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="活动名称" prop="activityName">
<el-input v-model="queryParams.activityName" placeholder="请输入活动名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="活动类型" prop="activityType">
<el-select v-model="queryParams.activityType" placeholder="请选择活动类型" clearable style="width: 100%">
<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>
<el-col :span="6">
<el-form-item label="活动状态" prop="activityStatus">
<el-select v-model="queryParams.activityStatus" placeholder="请选择活动状态" clearable 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="6">
<el-form-item label="报名状态" prop="registrationStatus">
<el-select v-model="queryParams.registrationStatus" placeholder="请选择报名状态" clearable style="width: 100%">
<el-option v-for="dict in stgl_registration_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="主办方" prop="organizerName">
<el-input v-model="queryParams.organizerName" placeholder="请输入主办方名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="是否已办" prop="isFinished">
<el-select v-model="queryParams.isFinished" placeholder="请选择是否已办" clearable style="width: 100%">
<el-option v-for="dict in sys_yes_no" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="关联社团/队伍" prop="relatedClubId">
<ClubSelector v-model="queryParams.relatedClub" @change="handleQueryRelatedClubChange" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="活动时间">
<el-date-picker
v-model="dateRange"
type="datetimerange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never" class="button-card table-card">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="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(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 || !['0', '7'].includes(selectedActivity?.activityStatus || '')"
@click="handleDelete()"
v-hasPermi="['stgl:activity:remove']"
:title="selectedActivity && !['0', '7'].includes(selectedActivity.activityStatus) ? '只有草稿和已驳回状态的活动可以删除' : ''"
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="success" icon="Download" @click="handleExport" v-hasPermi="['stgl:activity:export']">导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" border :data="activityList" @selection-change="handleSelectionChange" @row-dblclick="handleView">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="活动名称" align="center" prop="activityName" min-width="150" show-overflow-tooltip />
<el-table-column label="活动类型" align="center" prop="activityType" width="100">
<template #default="scope">
<dict-tag :options="stgl_activity_type" :value="scope.row.activityType" />
</template>
</el-table-column>
<el-table-column label="主办方" align="center" prop="organizerName" width="120" show-overflow-tooltip />
<el-table-column label="关联社团/队伍" align="center" width="120" show-overflow-tooltip>
<template #default="scope">
<span v-if="scope.row.relatedClubName">{{ scope.row.relatedClubName }}</span>
<span v-else class="text-gray-400">未关联</span>
</template>
</el-table-column>
<el-table-column label="指导老师" align="center" width="180" :show-overflow-tooltip="false">
<template #default="scope">
<div class="advisor-cell">
<span v-if="getAdvisorNames(scope.row).length === 0" class="no-advisor">暂无</span>
<div v-else class="advisor-list">
<el-tag v-for="(name, index) in getAdvisorNames(scope.row)" :key="index" type="info" size="small" class="advisor-tag" :title="name">
{{ name }}
</el-tag>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="活动时间" align="center" width="160">
<template #default="scope">
<div v-if="scope.row.startTime && scope.row.endTime">
<div>{{ formatDate(scope.row.startTime) }}</div>
<div class="text-sm text-gray-500">{{ formatDate(scope.row.endTime) }}</div>
</div>
<span v-else class="text-gray-400">未设置</span>
</template>
</el-table-column>
<el-table-column label="报名时间" align="center" width="160">
<template #default="scope">
<div v-if="scope.row.registrationStart && scope.row.registrationEnd">
<div>{{ formatDate(scope.row.registrationStart) }}</div>
<div class="text-sm text-gray-500">{{ formatDate(scope.row.registrationEnd) }}</div>
</div>
<span v-else class="text-gray-400">未设置</span>
</template>
</el-table-column>
<el-table-column label="活动状态" align="center" prop="activityStatus" width="100">
<template #default="scope">
<dict-tag :options="stgl_activity_status" :value="scope.row.activityStatus" />
</template>
</el-table-column>
<el-table-column label="报名范围" align="center" prop="activityRange" width="100">
<template #default="scope">
<dict-tag :options="stgl_activity_range" :value="scope.row.activityRange" />
</template>
</el-table-column>
<el-table-column label="报名状态" align="center" prop="registrationStatus" width="100">
<template #default="scope">
<dict-tag :options="stgl_registration_status" :value="scope.row.registrationStatus" />
</template>
</el-table-column>
<el-table-column label="报名人数" align="center" width="100">
<template #default="scope">
<el-tag type="info" size="small">{{ scope.row.registrationCount || 0 }}</el-tag>
</template>
</el-table-column>
<el-table-column label="平均评分" align="center" width="100">
<template #default="scope">
<div v-if="scope.row.averageRating" class="rating-cell">
<el-rate :model-value="scope.row.averageRating" disabled size="small" />
<span class="rating-text">{{ scope.row.averageRating }}</span>
</div>
<span v-else class="text-gray-400">暂无评分</span>
</template>
</el-table-column>
<el-table-column label="是否已办" align="center" prop="isFinished" width="100">
<template #default="scope">
<dict-tag :options="sys_yes_no" :value="scope.row.isFinished" />
</template>
</el-table-column>
<el-table-column label="创建者" align="center" prop="createByName" width="100" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="320" fixed="right">
<template #default="scope">
<el-tooltip content="查看" placement="top">
<el-button
link
type="primary"
icon="View"
size="default"
style="font-size: 16px; margin-right: 8px; color: blue"
@click="handleView(scope.row)"
v-hasPermi="['stgl:activity:query']"
></el-button>
</el-tooltip>
<el-tooltip content="修改" placement="top" v-if="['0', '7', '6'].includes(scope.row.activityStatus) && hasActivityOperationPermission(scope.row)">
<el-button
link
type="primary"
icon="Edit"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleUpdate(scope.row)"
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
type="warning"
icon="CopyDocument"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleCopy(scope.row)"
v-hasPermi="['stgl:activity:add']"
></el-button>
</el-tooltip>
<el-tooltip content="活动总结" placement="top" v-if="shouldShowSummaryButton(scope.row) && hasActivityOperationPermission(scope.row)">
<el-button
link
type="success"
icon="Document"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleSummary(scope.row)"
v-hasPermi="['stgl:activity:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="报名管理" placement="top">
<el-button
link
type="info"
icon="User"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleMembersReview(scope.row)"
v-hasPermi="['stgl:activity:query']"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top" v-if="['0', '7'].includes(scope.row.activityStatus) && hasActivityOperationPermission(scope.row)">
<el-button
link
type="danger"
icon="Delete"
size="default"
style="font-size: 16px"
@click="handleDelete(scope.row)"
v-hasPermi="['stgl:activity:remove']"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</div>
</el-card>
<!-- 添加或修改活动信息对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="1400px" append-to-body>
<el-form ref="activityFormRef" :model="form" :rules="rules" label-width="120px">
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane label="基本信息" name="basic">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="活动名称" prop="activityName" required>
<el-input v-model="form.activityName" placeholder="请输入活动名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="活动类型" prop="activityType" required>
<el-select v-model="form.activityType" placeholder="请选择活动类型" style="width: 100%">
<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>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="活动主题" prop="activityTheme">
<el-input v-model="form.activityTheme" placeholder="请输入活动主题" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="活动地点" prop="location">
<el-input v-model="form.location" placeholder="请输入活动地点" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="开始时间" prop="startTime" required>
<el-date-picker
v-model="form.startTime"
type="datetime"
placeholder="请选择开始时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="结束时间" prop="endTime" required>
<el-date-picker
v-model="form.endTime"
type="datetime"
placeholder="请选择结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="关联社团/队伍" prop="relatedClubId">
<ClubSelector v-model="form.relatedClub" @change="handleRelatedClubChange" :disabled="isRelatedClubDisabled" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="活动海报" prop="posterUrl">
<image-upload v-model="form.posterUrl" />
</el-form-item>
<el-form-item label="活动简介" prop="description">
<el-input v-model="form.description" type="textarea" placeholder="请输入活动简介" resize="none" :rows="4" />
</el-form-item>
<!-- 主办方信息 -->
<div class="section-divider">
<h4 class="section-title">主办方信息</h4>
</div>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="主办方名称" prop="organizerName" required>
<el-input v-model="form.organizerName" placeholder="请输入主办方名称" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="联系人" prop="organizerContact">
<el-input v-model="form.organizerContact" placeholder="请输入联系人姓名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话" prop="organizerPhone">
<el-input v-model="form.organizerPhone" placeholder="请输入联系电话" />
</el-form-item>
</el-col>
</el-row>
<!-- 承办方信息 -->
<div class="section-divider">
<h4 class="section-title">承办方信息</h4>
</div>
<CoOrganizerManager v-model="form.coOrganizerList" @change="handleCoOrganizersChange" />
<!-- 指导老师信息 -->
<div class="section-divider">
<h4 class="section-title">指导老师信息</h4>
</div>
<AdvisorManager
:key="`advisor-manager-${form.id || 'new'}-${formKey}`"
v-model="form.advisors"
@change="handleAdvisorsChange"
:initial-advisors="form.advisors"
/>
</el-tab-pane>
<el-tab-pane label="报名设置" name="registration">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="报名方式" prop="registrationType" required>
<el-select v-model="form.registrationType" placeholder="请选择报名方式" style="width: 100%" @change="handleRegistrationTypeChange">
<el-option v-for="dict in stgl_registration_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否需要审核" prop="needApproval">
<el-select v-model="form.needApproval" placeholder="请选择是否需要审核" style="width: 100%">
<el-option label="否" :value="0" />
<el-option label="是" :value="1" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="报名范围" prop="activityRange" required>
<el-radio-group v-model="form.activityRange" style="width: 100%">
<el-radio v-for="dict in stgl_activity_range" :key="dict.value" :value="Number(dict.value)">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="报名开始时间" prop="registrationStart">
<el-date-picker
v-model="form.registrationStart"
type="datetime"
placeholder="请选择报名开始时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="报名结束时间" prop="registrationEnd">
<el-date-picker
v-model="form.registrationEnd"
type="datetime"
placeholder="请选择报名结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" v-if="form.registrationType === '1' || form.registrationType === '3'">
<el-col :span="12">
<el-form-item label="总人数上限" prop="maxParticipants">
<el-input-number v-model="form.maxParticipants" :min="1" :max="9999" placeholder="请输入总人数上限" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20" v-if="form.registrationType === '2' || form.registrationType === '3'">
<el-col :span="8">
<el-form-item label="团队数量上限" prop="maxTeams">
<el-input-number v-model="form.maxTeams" :min="1" :max="9999" placeholder="请输入团队数量上限" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="每队最小人数" prop="teamSizeMin">
<el-input-number v-model="form.teamSizeMin" :min="1" :max="99" placeholder="请输入每队最小人数" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="每队最大人数" prop="teamSizeMax">
<el-input-number v-model="form.teamSizeMax" :min="1" :max="99" placeholder="请输入每队最大人数" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" plain type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
<!-- 查看活动详细信息对话框 -->
<el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="1200px" append-to-body>
<ActivityDetailView :activity="viewForm" />
<template #footer>
<div class="dialog-footer">
<el-button @click="viewDialog.visible = false">关 闭</el-button>
</div>
</template>
</el-dialog>
<!-- 活动复制对话框 -->
<el-dialog v-model="copyDialog.visible" title="复制活动" width="500px" append-to-body>
<el-form ref="copyFormRef" :model="copyForm" :rules="copyRules" label-width="120px">
<el-form-item label="源活动名称">
<el-input v-model="copyForm.sourceActivityName" readonly />
</el-form-item>
<el-form-item label="新活动名称" prop="newActivityName">
<el-input v-model="copyForm.newActivityName" placeholder="请输入新活动名称" maxlength="100" show-word-limit />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="copyDialog.visible = false">取消</el-button>
<el-button type="primary" @click="confirmCopy" :loading="copyLoading">确认复制</el-button>
</div>
</template>
</el-dialog>
<!-- 活动总结对话框 -->
<el-dialog v-model="summaryDialog.visible" title="活动总结" width="900px" append-to-body>
<el-form ref="summaryFormRef" :model="summaryForm" label-width="120px">
<el-form-item label="活动名称">
<el-input v-model="summaryForm.activityName" readonly />
</el-form-item>
<el-form-item label="活动总结报告" prop="summary">
<el-input v-model="summaryForm.summary" type="textarea" placeholder="请输入活动总结报告" resize="none" :rows="6" />
</el-form-item>
<el-form-item label="总结报告附件" prop="summaryAttachments">
<file-upload v-model="summaryForm.summaryAttachments" />
</el-form-item>
<el-form-item label="活动成果" prop="achievements">
<el-input v-model="summaryForm.achievements" type="textarea" placeholder="请输入活动成果" resize="none" :rows="6" />
</el-form-item>
<el-form-item label="成果附件" prop="achievementsAttachments">
<file-upload v-model="summaryForm.achievementsAttachments" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="summaryDialog.visible = false">取消</el-button>
<el-button type="primary" @click="confirmSummary" :loading="summaryLoading">确认总结</el-button>
</div>
</template>
</el-dialog>
<!-- 成员审核弹窗 -->
<ActivityMembersDialog
v-model:visible="membersDialog.visible"
:activity-id="selectedActivity?.id"
: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>
<script setup name="StglActivity" lang="ts">
import { nextTick } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { ElNotification } from 'element-plus';
import {
listStglActivity,
getStglActivity,
delStglActivity,
addStglActivity,
updateStglActivity,
exportStglActivity,
copyStglActivity,
summarizeActivity
} from '@/api/stgl/activity';
import { StglActivityVO, StglActivityQuery, StglActivityForm, ClubOption, CoOrganizerInfo, InstructorInfo } from '@/api/stgl/activity/types';
import ClubSelector from '@/components/ClubSelector/index.vue';
import CoOrganizerManager from '@/components/CoOrganizerManager/index.vue';
import InstructorManager from '@/components/InstructorManager/index.vue';
import AdvisorManager from '@/components/AdvisorManager/index.vue';
import ActivityRatingView from './components/ActivityRatingView.vue';
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, 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')
);
const activityList = ref<StglActivityVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const activeTab = ref('basic');
const dateRange = ref<[string, string]>();
const selectedActivity = ref<StglActivityVO | null>(null);
const formKey = ref(0);
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>({
visible: false,
title: ''
});
const viewDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const copyDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const summaryDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const membersDialog = reactive<DialogOption>({
visible: false,
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,
activityName: undefined,
activityType: undefined,
activityTheme: undefined,
startTime: undefined,
endTime: undefined,
location: undefined,
description: undefined,
posterUrl: undefined,
relatedClubId: undefined,
relatedType: undefined,
relatedClub: null,
organizerName: undefined,
organizerType: undefined,
organizerContact: undefined,
organizerPhone: undefined,
coOrganizerList: [],
coOrganizers: undefined,
instructorList: [],
instructors: '[]',
advisors: [],
registrationType: undefined,
registrationStart: undefined,
registrationEnd: undefined,
registrationStatus: undefined,
maxParticipants: undefined,
maxTeams: undefined,
teamSizeMin: undefined,
teamSizeMax: undefined,
needApproval: 1,
activityRange: 1, // 默认为1-本组织成员
activityStatus: '0',
isFinished: 0,
summary: undefined,
achievements: undefined,
summaryAttachments: undefined,
achievementsAttachments: undefined,
importSource: undefined,
remark: undefined
};
const data = reactive<PageData<StglActivityForm, StglActivityQuery & { relatedClub?: ClubOption | null }>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
activityName: undefined,
activityType: undefined,
activityTheme: undefined,
startTime: undefined,
endTime: undefined,
location: undefined,
relatedClubId: undefined,
relatedClub: null,
organizerName: undefined,
registrationType: undefined,
registrationStart: undefined,
registrationEnd: undefined,
registrationStatus: undefined,
activityStatus: undefined,
isFinished: undefined,
params: {}
},
rules: {
activityName: [
{ required: true, message: '活动名称不能为空', trigger: 'blur' },
{ min: 2, max: 100, message: '活动名称长度在2到100个字符', trigger: 'blur' }
],
activityType: [{ required: true, message: '请选择活动类型', trigger: 'change' }],
startTime: [{ required: true, message: '请选择活动开始时间', trigger: 'change' }],
endTime: [{ required: true, message: '请选择活动结束时间', trigger: 'change' }],
organizerName: [
{ required: true, message: '主办方名称不能为空', trigger: 'blur' },
{ min: 2, max: 50, message: '主办方名称长度在2到50个字符', trigger: 'blur' }
],
registrationType: [{ required: true, message: '请选择报名方式', trigger: 'change' }],
activityRange: [{ required: true, message: '请选择报名范围', trigger: 'change' }]
}
});
const { queryParams, form, rules } = toRefs(data);
const viewForm = ref<StglActivityVO>({} as StglActivityVO);
/** 初始化URL参数 */
const initUrlParams = () => {
const { relatedClubId, relatedClubName, relatedTeamId, relatedTeamName, from } = route.query;
if (relatedClubId && relatedClubName && from === 'club') {
// 设置社团查询参数
queryParams.value.relatedClubId = relatedClubId as string | number;
queryParams.value.relatedClub = {
relatedClubId: relatedClubId as string | number,
relatedClubName: relatedClubName as string,
relatedType: 1, // 社团类型
uniqueKey: `1-${relatedClubId}`
};
console.log('初始化URL参数 - 社团过滤:', {
relatedClubId,
relatedClubName,
queryParams: queryParams.value.relatedClub
});
} else if (relatedTeamId && relatedTeamName && from === 'team') {
// 设置志愿者队伍查询参数
queryParams.value.relatedClubId = relatedTeamId as string | number;
queryParams.value.relatedClub = {
relatedClubId: relatedTeamId as string | number,
relatedClubName: relatedTeamName as string,
relatedType: 2, // 志愿者队伍类型
uniqueKey: `2-${relatedTeamId}`
};
console.log('初始化URL参数 - 志愿者队伍过滤:', {
relatedTeamId,
relatedTeamName,
queryParams: queryParams.value.relatedClub
});
}
};
// 复制表单
const copyForm = reactive({
sourceId: undefined as string | number | undefined,
sourceActivityName: '',
newActivityName: ''
});
const copyRules = {
newActivityName: [
{ required: true, message: '请输入新活动名称', trigger: 'blur' },
{ min: 1, max: 100, message: '活动名称长度在1到100个字符', trigger: 'blur' }
]
};
// 总结表单
const summaryForm = reactive({
id: undefined as string | number | undefined,
activityName: '',
summary: '',
achievements: '',
summaryAttachments: '',
achievementsAttachments: ''
});
/** 查询活动信息列表 */
const getList = async () => {
loading.value = true;
// 处理日期范围
if (dateRange.value && dateRange.value.length === 2) {
queryParams.value.startTime = dateRange.value[0];
queryParams.value.endTime = dateRange.value[1];
} else {
queryParams.value.startTime = undefined;
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;
total.value = res.total;
} catch (error) {
console.error('查询活动列表失败:', error);
proxy?.$modal.msgError('查询活动列表失败');
} finally {
loading.value = false;
}
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
// 直接重置,避免使用nextTick导致的递归更新
form.value = { ...initFormData };
// 确保指导老师字段被正确初始化
form.value.advisors = [];
form.value.instructorList = [];
form.value.instructors = '[]';
// 重置关联社团禁用状态
isRelatedClubDisabled.value = false;
// 重置审批表单
approveForm.id = undefined;
approveForm.activityName = '';
approveForm.approvalResult = 'approve';
approveForm.rejectReason = '';
// 更新formKey以强制重新渲染组件
formKey.value++;
activeTab.value = 'basic';
console.log('表单重置后的状态:', {
advisors: form.value.advisors,
instructorList: form.value.instructorList,
instructors: form.value.instructors
});
// 延迟重置表单字段,避免递归更新
setTimeout(() => {
activityFormRef.value?.resetFields();
}, 0);
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
dateRange.value = undefined;
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: StglActivityVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
selectedActivity.value = selection.length === 1 ? selection[0] : null;
};
/** 新增按钮操作 */
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,
formRelatedClubId: form.value.relatedClubId,
formRelatedType: form.value.relatedType
});
} else {
// 没有关联组织时,允许编辑关联社团字段
isRelatedClubDisabled.value = false;
}
dialog.visible = true;
dialog.title = '添加活动信息';
// 确保对话框完全打开后再检查表单状态
nextTick(() => {
console.log('新增活动 - 对话框打开后的表单状态:', {
advisors: form.value.advisors,
instructorList: form.value.instructorList,
instructors: form.value.instructors
});
});
};
/** 修改按钮操作 */
const handleUpdate = async (row?: StglActivityVO) => {
reset();
const _id = row?.id || ids.value[0];
// 先获取活动数据检查状态
const res = await getStglActivity(_id);
// 检查活动状态是否允许修改(只有草稿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}",不允许修改`);
return;
}
console.log('handleUpdate - 获取的活动数据:', res.data);
console.log('handleUpdate - 附件字段:', {
summaryAttachments: res.data.summaryAttachments,
achievementsAttachments: res.data.achievementsAttachments
});
// 处理关联社团数据
if (res.data.relatedClubId && res.data.relatedClubName) {
(res.data as any).relatedClub = {
relatedClubId: res.data.relatedClubId,
relatedClubName: res.data.relatedClubName,
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;
}
// 处理承办方数据
if (res.data.coOrganizers) {
try {
(res.data as any).coOrganizerList = JSON.parse(res.data.coOrganizers);
} catch (error) {
(res.data as any).coOrganizerList = [];
}
} else {
(res.data as any).coOrganizerList = [];
}
// 处理指导老师数据
if (res.data.instructors) {
try {
(res.data as any).instructorList = JSON.parse(res.data.instructors);
} catch (error) {
(res.data as any).instructorList = [];
}
} else {
(res.data as any).instructorList = [];
}
// 处理AdvisorManager组件的指导老师数据
if (res.data.instructors) {
try {
const instructorsData = JSON.parse(res.data.instructors);
console.log('解析的指导老师数据:', instructorsData);
// 确保数据格式正确
if (Array.isArray(instructorsData)) {
(res.data as any).advisors = instructorsData.map((advisor: any) => ({
...advisor,
// 确保必要字段存在
name: advisor.name || advisor.teacher?.nickName || '',
dept: advisor.dept || advisor.teacher?.deptName || '',
phone: advisor.phone || advisor.teacher?.phonenumber || '',
email: advisor.email || advisor.teacher?.email || '',
userId: advisor.userId || advisor.teacher?.userId || ''
}));
} else {
(res.data as any).advisors = [];
}
} catch (error) {
console.warn('解析指导老师JSON数据失败:', error);
(res.data as any).advisors = [];
}
} else {
(res.data as any).advisors = [];
}
Object.assign(form.value, res.data);
// 确保数据类型正确
if (form.value.registrationType !== undefined) {
form.value.registrationType = String(form.value.registrationType);
}
// 确保指导老师数据正确设置
console.log('修改活动 - 设置表单数据后的状态:', {
advisors: form.value.advisors,
instructorList: form.value.instructorList,
instructors: form.value.instructors,
registrationType: form.value.registrationType
});
// 更新formKey以强制重新渲染组件
formKey.value++;
dialog.visible = true;
dialog.title = '修改活动信息';
};
/** 显示验证错误提示 */
const showValidationErrors = (invalidFields: any) => {
if (!invalidFields) return;
const errorMessages: string[] = [];
const tabMapping = {
// 基本信息标签页字段
'activityName': { tab: 'basic', label: '活动名称' },
'activityType': { tab: 'basic', label: '活动类型' },
'startTime': { tab: 'basic', label: '开始时间' },
'endTime': { tab: 'basic', label: '结束时间' },
'organizerName': { tab: 'basic', label: '主办方名称' },
// 报名设置标签页字段
'registrationType': { tab: 'registration', label: '报名方式' },
'activityRange': { tab: 'registration', label: '报名范围' }
};
let needSwitchTab = '';
let currentTabErrors: string[] = [];
let otherTabErrors: string[] = [];
// 分类错误信息
Object.keys(invalidFields).forEach(fieldName => {
const fieldInfo = tabMapping[fieldName];
if (fieldInfo) {
const errorMsg = invalidFields[fieldName][0]?.message || '验证失败';
// 去掉重复的"不能为空"等文字,只保留核心提示
const cleanMessage = errorMsg.replace(/不能为空$/, '').replace(/^请选择/, '').replace(/^请输入/, '');
const errorMessage = cleanMessage ? `${fieldInfo.label}:${cleanMessage}` : `${fieldInfo.label}为必填项`;
if (fieldInfo.tab === activeTab.value) {
currentTabErrors.push(errorMessage);
} else {
otherTabErrors.push(errorMessage);
if (!needSwitchTab) {
needSwitchTab = fieldInfo.tab;
}
}
}
});
// 构建提示消息
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({
title: '表单验证失败',
message: errorMessages.join('\n'),
type: 'warning',
duration: 10000,
position: 'top-right',
showClose: true
});
// 如果其他标签页有错误,自动切换到第一个有错误的标签页
if (needSwitchTab && needSwitchTab !== activeTab.value) {
activeTab.value = needSwitchTab;
// 切换标签页后延迟一下再次验证,确保用户能看到错误字段
nextTick(() => {
activityFormRef.value?.validate(() => {});
});
}
}
};
/** 提交按钮 */
const submitForm = () => {
activityFormRef.value?.validate(async (valid: boolean, invalidFields) => {
if (valid) {
buttonLoading.value = true;
// 准备提交的数据
const submitData = { ...form.value } as any;
console.log('提交前 - 原始表单数据:', {
advisors: submitData.advisors,
instructorList: submitData.instructorList,
instructors: submitData.instructors
});
// 新增时自动设置活动状态为0(草稿)
if (!submitData.id) {
submitData.activityStatus = '0';
}
// 处理关联社团数据
if (submitData.relatedClub) {
submitData.relatedClubId = submitData.relatedClub.relatedClubId;
submitData.relatedType = submitData.relatedClub.relatedType;
} else {
// 清空关联社团数据
submitData.relatedClubId = null;
submitData.relatedType = null;
}
// 调试信息
console.log('提交的关联社团数据:', {
relatedClub: submitData.relatedClub,
relatedClubId: submitData.relatedClubId,
relatedType: submitData.relatedType
});
// 处理承办方数据
if (submitData.coOrganizerList && submitData.coOrganizerList.length > 0) {
submitData.coOrganizers = JSON.stringify(submitData.coOrganizerList);
} else {
submitData.coOrganizers = '[]'; // 修复:使用空JSON数组而不是undefined
}
// 处理指导老师数据
if (submitData.advisors && submitData.advisors.length > 0) {
submitData.instructors = JSON.stringify(submitData.advisors);
} else {
submitData.instructors = '[]';
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;
}
} else {
// 验证失败,显示必填项提示
showValidationErrors(invalidFields);
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: StglActivityVO) => {
const _ids = row?.id || ids.value;
let confirmMessage = '';
// 检查删除权限 - 只有草稿(状态0)和已驳回(状态7)的活动可以删除
if (row) {
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}",只有草稿和已驳回状态的活动可以删除`);
return;
}
confirmMessage = `是否确认删除活动"${row.activityName}"?`;
} else {
const selectedItems = activityList.value.filter((item) => ids.value.includes(item.id));
// 检查选中的活动是否都是草稿或已驳回状态
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}"不是草稿或已驳回状态,只有草稿和已驳回状态的活动可以删除`);
return;
}
if (selectedItems.length === 1) {
confirmMessage = `是否确认删除活动"${selectedItems[0].activityName}"?`;
} else {
const names = selectedItems.map((item) => item.activityName).join('、');
confirmMessage = `是否确认删除活动"${names}"?`;
}
}
await proxy?.$modal.confirm(confirmMessage);
await delStglActivity(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 查看按钮操作 */
const handleView = async (row: StglActivityVO) => {
const res = await getStglActivity(row.id);
console.log('handleView - 获取的活动数据:', res.data);
console.log('handleView - 附件字段:', {
summaryAttachments: res.data.summaryAttachments,
achievementsAttachments: res.data.achievementsAttachments
});
viewForm.value = res.data;
viewDialog.visible = true;
viewDialog.title = '查看活动信息';
};
/** 复制按钮操作 */
const handleCopy = (row?: StglActivityVO) => {
const activity = row || activityList.value.find((item) => ids.value.includes(item.id));
if (!activity) return;
copyForm.sourceId = activity.id;
copyForm.sourceActivityName = activity.activityName || '';
copyForm.newActivityName = '';
copyDialog.visible = true;
};
/** 确认复制 */
const confirmCopy = async () => {
if (!copyFormRef.value) return;
await copyFormRef.value.validate(async (valid: boolean) => {
if (valid) {
copyLoading.value = true;
try {
await copyStglActivity({
sourceId: copyForm.sourceId!,
newActivityName: copyForm.newActivityName,
activityStatus: '0', // 复制后的活动状态设置为0(草稿)
registrationStatus: '0' // 复制后的报名状态设置为0(未开始)
});
proxy?.$modal.msgSuccess('复制成功');
copyDialog.visible = false;
await getList();
} catch (error) {
console.error('复制失败:', error);
} finally {
copyLoading.value = false;
}
}
});
};
/** 活动总结按钮操作 */
const handleSummary = async (row?: StglActivityVO) => {
const activity = row || activityList.value.find((item) => ids.value.includes(item.id));
if (!activity) return;
// 先重置总结表单
resetSummaryForm();
// 获取完整的活动数据,确保包含所有附件信息
const res = await getStglActivity(activity.id);
const fullActivity = res.data;
console.log('handleSummary - 完整活动数据:', fullActivity);
console.log('handleSummary - 附件字段:', {
summaryAttachments: fullActivity.summaryAttachments,
achievementsAttachments: fullActivity.achievementsAttachments
});
summaryForm.id = fullActivity.id;
summaryForm.activityName = fullActivity.activityName || '';
summaryForm.summary = fullActivity.summary || '';
summaryForm.achievements = fullActivity.achievements || '';
summaryForm.summaryAttachments = fullActivity.summaryAttachments || '';
summaryForm.achievementsAttachments = fullActivity.achievementsAttachments || '';
console.log('handleSummary - 设置到表单的数据:', {
summaryAttachments: summaryForm.summaryAttachments,
achievementsAttachments: summaryForm.achievementsAttachments
});
summaryDialog.visible = true;
};
/** 重置总结表单 */
const resetSummaryForm = () => {
summaryForm.id = undefined;
summaryForm.activityName = '';
summaryForm.summary = '';
summaryForm.achievements = '';
summaryForm.summaryAttachments = '';
summaryForm.achievementsAttachments = '';
};
/** 确认总结 */
const confirmSummary = async () => {
summaryLoading.value = true;
try {
console.log('confirmSummary - 提交的数据:', {
id: summaryForm.id,
summary: summaryForm.summary,
achievements: summaryForm.achievements,
summaryAttachments: summaryForm.summaryAttachments,
achievementsAttachments: summaryForm.achievementsAttachments
});
await summarizeActivity({
id: summaryForm.id!,
summary: summaryForm.summary,
achievements: summaryForm.achievements,
summaryAttachments: summaryForm.summaryAttachments,
achievementsAttachments: summaryForm.achievementsAttachments
});
proxy?.$modal.msgSuccess('活动总结成功');
summaryDialog.visible = false;
await getList();
} catch (error) {
console.error('活动总结失败:', error);
} finally {
summaryLoading.value = false;
}
};
/** 总结报告附件变化处理 */
const handleSummaryAttachmentsChange = (attachments: string) => {
summaryForm.summaryAttachments = attachments;
};
/** 活动成果附件变化处理 */
const handleAchievementsAttachmentsChange = (attachments: string) => {
summaryForm.achievementsAttachments = attachments;
};
/** 导出按钮操作 */
const handleExport = async () => {
try {
const res = await exportStglActivity(queryParams.value);
proxy?.$download.excel(res, '活动信息.xlsx');
} catch (error) {
console.error('导出失败:', error);
proxy?.$modal.msgError('导出失败');
}
};
/** 关联社团变化处理 */
const handleRelatedClubChange = (club: ClubOption | null) => {
console.log('活动表单 - handleRelatedClubChange:', club);
form.value.relatedClub = club;
form.value.relatedClubId = club?.relatedClubId;
form.value.relatedType = club?.relatedType; // 修复:同时设置关联类型
console.log('活动表单 - 更新后的表单数据:', {
relatedClub: form.value.relatedClub,
relatedClubId: form.value.relatedClubId,
relatedType: form.value.relatedType
});
};
/** 查询关联社团变化处理 */
const handleQueryRelatedClubChange = (club: ClubOption | null) => {
queryParams.value.relatedClub = club;
queryParams.value.relatedClubId = club?.relatedClubId;
};
/** 承办方变化处理 */
const handleCoOrganizersChange = (coOrganizers: CoOrganizerInfo[]) => {
form.value.coOrganizerList = coOrganizers;
};
/** 指导老师变化处理 */
const handleInstructorsChange = (instructors: InstructorInfo[]) => {
form.value.instructorList = instructors;
};
/** 指导老师变化处理 */
const handleAdvisorsChange = (advisors: AdvisorVO[]) => {
form.value.advisors = advisors;
};
/** 报名方式变化处理 */
const handleRegistrationTypeChange = (value: string) => {
// 根据报名方式重置相关字段
if (value === '1') {
// 个人报名
form.value.maxTeams = undefined;
form.value.teamSizeMin = undefined;
form.value.teamSizeMax = undefined;
} else if (value === '2') {
// 团队报名
form.value.maxParticipants = undefined;
}
};
/** 判断是否显示总结按钮 */
const shouldShowSummaryButton = (row: StglActivityVO): boolean => {
// 只有进行中(4)和已结束(5)状态的活动才显示总结按钮
return row.activityStatus === '4' || row.activityStatus === '5';
};
/** 成员审核按钮操作 */
const handleMembersReview = (row: StglActivityVO) => {
selectedActivity.value = row;
membersDialog.visible = true;
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 = '3'; // 已通过(活动还未开始)
} 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 '';
const date = new Date(dateStr);
return date.toLocaleString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
};
/** 获取指导老师姓名列表 */
const getAdvisorNames = (activity: StglActivityVO): string[] => {
// 优先从JSON字段读取指导老师数据
if (activity.instructors) {
try {
const instructors = JSON.parse(activity.instructors);
if (Array.isArray(instructors)) {
return instructors.map((instructor: any) => instructor.name || instructor.teacher?.nickName || '').filter((name) => name);
}
} catch (error) {
console.warn('解析指导老师JSON数据失败:', error);
}
}
console.log('getAdvisorNames: 没有找到指导老师数据,activity:', activity);
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;
}
// 志愿者队长只能操作自己创建的活动
if (proxy?.$auth.hasRole('stgl_volunteer_captain')) {
return activity.createBy === userStore.userId;
}
return false;
};
onMounted(() => {
initUrlParams();
getList();
});
</script>
<style scoped>
/* 页面容器样式 */
.page-container {
padding: 8px;
background-color: #f5f5f5;
}
/* 表格卡片布局 */
.table-card {
margin-top: 0;
border-radius: 12px;
border: 1px solid #e4e7ed;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.table-card :deep(.el-card__body) {
padding: 0px 20px 20px 20px !important;
}
/* 移除按钮栏底部边框 */
.button-card :deep(.el-card__header) {
border-bottom: none !important;
background: linear-gradient(135deg, #fafbff 0%, #f8fafc 100%);
padding: 16px 20px;
}
/* 分页容器样式 */
.pagination-container {
display: flex;
justify-content: flex-end;
padding: 16px 20px 0 0 !important;
background-color: #fff;
margin-top: 0px !important;
}
/* 搜索按钮容器样式 */
.search-btn-container {
display: flex;
justify-content: flex-end;
margin-top: 10px;
.el-form-item {
margin-bottom: 0 !important;
}
}
/* 章节分隔样式 */
.section-divider {
margin: 24px 0 16px 0;
padding-top: 16px;
border-top: 1px solid #e4e7ed;
}
.section-title {
margin: 0 0 16px 0;
color: #303133;
font-size: 16px;
font-weight: 600;
border-bottom: 2px solid #409eff;
padding-bottom: 8px;
}
/* 评分单元格样式 */
.rating-cell {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.rating-text {
font-size: 12px;
color: #606266;
font-weight: 500;
}
/* 指导老师单元格样式 */
.advisor-cell {
padding: 4px 8px;
width: 100%;
.no-advisor {
color: #909399;
font-size: 12px;
}
.advisor-list {
display: flex;
flex-wrap: wrap;
gap: 4px 6px;
align-items: center;
justify-content: center;
width: 100%;
line-height: 1.5;
}
.advisor-tag {
margin: 0;
font-size: 12px;
line-height: 1.3;
padding: 2px 6px;
border-radius: 2px;
white-space: nowrap;
overflow: visible;
text-overflow: unset;
flex-shrink: 0;
max-width: none;
background-color: #f0f2f5;
color: #606266;
border: 1px solid #dcdfe6;
}
}
</style>