903 lines
33 KiB
Vue
903 lines
33 KiB
Vue
<template>
|
||
<div class="activity-registration-view">
|
||
<!-- 搜索区域 -->
|
||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
||
<div v-show="showSearch" class="mb-[10px]">
|
||
<el-card shadow="always" style="padding: 8px 16px">
|
||
<el-form ref="queryFormRef" :model="queryParams" label-width="80px" style="padding: 0">
|
||
<el-row :gutter="16" align="middle">
|
||
<el-col :span="5">
|
||
<el-form-item label="姓名" prop="studentName" style="margin-bottom: 0">
|
||
<el-input v-model="queryParams.studentName" placeholder="请输入姓名" clearable size="small" @keyup.enter="handleQuery" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="5">
|
||
<el-form-item label="学号" prop="studentNo" style="margin-bottom: 0">
|
||
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable size="small" @keyup.enter="handleQuery" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="6">
|
||
<el-form-item label="联系方式" prop="phone" style="margin-bottom: 0">
|
||
<el-input v-model="queryParams.phone" placeholder="请输入联系方式" clearable size="small" @keyup.enter="handleQuery" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="8">
|
||
<el-form-item style="margin-bottom: 0">
|
||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
</el-card>
|
||
</div>
|
||
</transition>
|
||
|
||
<!-- 表格区域 -->
|
||
<el-card shadow="never">
|
||
<template #header>
|
||
<el-row :gutter="10" class="mb8">
|
||
<el-col :span="1.5" v-if="!isViewMode">
|
||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['stgl:activityRegister:add']">新增</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5" v-if="!isViewMode">
|
||
<el-button icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['stgl:activityRegister:edit']">修改</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5" v-if="!isViewMode">
|
||
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['stgl:activityRegister:remove']">删除</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5" v-if="!isViewMode">
|
||
<el-button type="success" icon="Download" @click="handleExport" v-hasPermi="['stgl:activityRegister:export']">导出</el-button>
|
||
</el-col>
|
||
<el-col :span="1.5" v-if="!isViewMode">
|
||
<el-button type="warning" icon="Upload" @click="handleImport" v-hasPermi="['stgl:activityRegister:import']">导入</el-button>
|
||
</el-col>
|
||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||
</el-row>
|
||
</template>
|
||
|
||
<el-table v-loading="loading" border :data="memberList" @selection-change="handleSelectionChange">
|
||
<el-table-column type="selection" width="55" align="center" v-if="!isViewMode" />
|
||
<el-table-column label="序号" type="index" width="55" align="center">
|
||
<template #default="scope">
|
||
<span>{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="状态" align="center" prop="approvalStatus" width="80">
|
||
<template #default="scope">
|
||
<dict-tag :options="stgl_approval_status" :value="scope.row.approvalStatus" v-if="scope.row.approvalStatus !== undefined" />
|
||
<span v-else>-</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="学号" align="center" prop="studentNo" width="120" />
|
||
<el-table-column label="姓名" align="center" prop="studentName" width="80" />
|
||
<el-table-column label="性别" align="center" prop="gender" width="60">
|
||
<template #default="scope">
|
||
<dict-tag :options="sys_user_sex" :value="scope.row.gender" v-if="scope.row.gender" />
|
||
<span v-else>-</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="联系方式" align="center" prop="phone" width="120" />
|
||
<el-table-column label="院系" align="center" prop="department" width="120" />
|
||
<el-table-column label="专业" align="center" prop="major" width="180" />
|
||
<el-table-column label="班级" align="center" prop="className" width="120" />
|
||
<el-table-column label="报名类型" align="center" prop="registrationType" width="100">
|
||
<template #default="scope">
|
||
<dict-tag :options="stgl_registration_type" :value="scope.row.registrationType" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="团队名称" align="center" prop="teamName" width="150" show-overflow-tooltip />
|
||
<el-table-column label="报名时间" align="center" prop="registrationTime" width="100">
|
||
<template #default="scope">
|
||
<span>{{ parseTime(scope.row.registrationTime, '{y}-{m}-{d}') }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="签到时间" align="center" width="120">
|
||
<template #default="scope">
|
||
<el-button v-if="scope.row.latestSignInTime" link type="primary" @click="showSignRecords(scope.row)" class="time-button">
|
||
{{ formatSignTime(scope.row.latestSignInTime) }}
|
||
</el-button>
|
||
<span v-else class="text-gray-400">未签到</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="签退时间" align="center" width="120">
|
||
<template #default="scope">
|
||
<el-button v-if="scope.row.latestSignOutTime" link type="primary" @click="showSignRecords(scope.row)" class="time-button">
|
||
{{ formatSignTime(scope.row.latestSignOutTime) }}
|
||
</el-button>
|
||
<span v-else class="text-gray-400">未签退</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="成绩" align="center" prop="score" width="80">
|
||
<template #default="scope">
|
||
<span>{{ scope.row.score || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width" fixed="right">
|
||
<template #default="scope">
|
||
<el-tooltip content="查看" placement="top">
|
||
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['stgl:activityRegister:query']"></el-button>
|
||
</el-tooltip>
|
||
<el-tooltip content="修改" placement="top" v-if="!isViewMode">
|
||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['stgl:activityRegister:edit']"></el-button>
|
||
</el-tooltip>
|
||
<el-tooltip content="删除" placement="top" v-if="!isViewMode">
|
||
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['stgl:activityRegister:remove']"></el-button>
|
||
</el-tooltip>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||
</el-card>
|
||
|
||
<!-- 添加或修改活动报名对话框 -->
|
||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body v-if="!isViewMode">
|
||
<el-form ref="memberFormRef" :model="form" :rules="rules" label-width="100px">
|
||
<el-row>
|
||
<el-col :span="12">
|
||
<el-form-item label="姓名" prop="selectedStudent">
|
||
<StudentSelector v-model="form.selectedStudent" placeholder="请选择学生" :showStudentNo="false" @change="handleStudentChange" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="学号" prop="studentNo">
|
||
<el-input v-model="form.studentNo" placeholder="学号" readonly />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="性别" prop="gender">
|
||
<el-select v-model="form.gender" placeholder="性别" disabled>
|
||
<el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value"> </el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="院系" prop="department">
|
||
<el-input v-model="form.department" placeholder="院系" readonly />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="专业" prop="major">
|
||
<el-input v-model="form.major" placeholder="专业" readonly />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="班级" prop="className">
|
||
<el-input v-model="form.className" placeholder="班级" readonly />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="联系方式" prop="phone">
|
||
<el-input v-model="form.phone" placeholder="联系方式" readonly />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="报名类型" prop="registrationType">
|
||
<el-select v-model="form.registrationType" placeholder="请选择报名类型" style="width: 100%">
|
||
<el-option v-for="dict in stgl_registration_type" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12">
|
||
<el-form-item label="团队名称" prop="teamName" v-if="form.registrationType === '2' || form.registrationType === '3'">
|
||
<el-input v-model="form.teamName" placeholder="请输入团队名称" />
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="24">
|
||
<el-form-item label="申请理由" prop="remark">
|
||
<el-input v-model="form.remark" type="textarea" placeholder="请输入申请理由" />
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
</el-form>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||
<el-button @click="cancel">取 消</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 查看成员详情对话框 -->
|
||
<el-dialog :title="viewDialog.title" v-model="viewDialog.visible" width="800px" append-to-body>
|
||
<el-descriptions :column="2" border>
|
||
<el-descriptions-item label="姓名">{{ viewForm.studentName }}</el-descriptions-item>
|
||
<el-descriptions-item label="学号">{{ viewForm.studentNo }}</el-descriptions-item>
|
||
<el-descriptions-item label="性别">
|
||
<dict-tag :options="sys_user_sex" :value="viewForm.gender" v-if="viewForm.gender" />
|
||
<span v-else>-</span>
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="联系方式">{{ viewForm.phone || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="院系">{{ viewForm.department || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="专业">{{ viewForm.major || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="班级">{{ viewForm.className || '-' }}</el-descriptions-item>
|
||
<el-descriptions-item label="报名类型">
|
||
<dict-tag :options="stgl_registration_type" :value="viewForm.registrationType" />
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="团队名称" v-if="viewForm.registrationType === '2' || viewForm.registrationType === '3'">
|
||
{{ viewForm.teamName || '-' }}
|
||
</el-descriptions-item>
|
||
<el-descriptions-item label="报名时间">{{ parseTime(viewForm.registrationTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
|
||
<el-descriptions-item label="申请理由" :span="2">
|
||
<div class="view-description">{{ viewForm.remark || '无' }}</div>
|
||
</el-descriptions-item>
|
||
</el-descriptions>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<el-button @click="viewDialog.visible = false">关 闭</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 签到记录弹窗 -->
|
||
<el-dialog v-model="signRecordsDialog.visible" :title="signRecordsDialog.title" width="800px" append-to-body>
|
||
<div v-loading="signRecordsLoading">
|
||
<el-table :data="signRecords" border stripe>
|
||
<el-table-column label="序号" type="index" width="60" align="center" />
|
||
<el-table-column label="签到时间" align="center" width="160">
|
||
<template #default="scope">
|
||
<span v-if="scope.row.signInTime">{{ formatSignTime(scope.row.signInTime) }}</span>
|
||
<span v-else class="text-gray-400">未签到</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="签到地点" align="center" prop="signInLocation" width="150" show-overflow-tooltip>
|
||
<template #default="scope">
|
||
<span>{{ scope.row.signInLocation || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="签退时间" align="center" width="160">
|
||
<template #default="scope">
|
||
<span v-if="scope.row.signOutTime">{{ formatSignTime(scope.row.signOutTime) }}</span>
|
||
<span v-else class="text-gray-400">未签退</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="签退地点" align="center" prop="signOutLocation" width="150" show-overflow-tooltip>
|
||
<template #default="scope">
|
||
<span>{{ scope.row.signOutLocation || '-' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="状态" align="center" width="100">
|
||
<template #default="scope">
|
||
<el-tag v-if="scope.row.overallStatus === 0" type="info">未签到</el-tag>
|
||
<el-tag v-else-if="scope.row.overallStatus === 1" type="success">已签到</el-tag>
|
||
<el-tag v-else-if="scope.row.overallStatus === 2" type="warning">已签退</el-tag>
|
||
<span v-else>-</span>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<el-button @click="signRecordsDialog.visible = false">关 闭</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 导入对话框 -->
|
||
<el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body v-if="!isViewMode">
|
||
<el-upload
|
||
ref="uploadRef"
|
||
:limit="1"
|
||
accept=".xlsx, .xls"
|
||
:headers="upload.headers"
|
||
:action="upload.url"
|
||
:disabled="upload.isUploading"
|
||
:on-progress="handleFileUploadProgress"
|
||
:on-success="handleFileSuccess"
|
||
:auto-upload="false"
|
||
drag
|
||
>
|
||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||
<template #tip>
|
||
<div class="el-upload__tip text-center">
|
||
<div class="el-upload__tip">
|
||
<el-checkbox v-model="upload.updateSupport" />
|
||
是否更新已经存在的报名数据
|
||
</div>
|
||
<span>仅允许导入xls、xlsx格式文件。</span>
|
||
<el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板</el-link>
|
||
</div>
|
||
</template>
|
||
</el-upload>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
||
<el-button @click="upload.open = false">取 消</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { getCurrentInstance, onMounted, reactive, ref, toRefs, watch } from 'vue';
|
||
import {
|
||
listActivityRegister,
|
||
getActivityRegister,
|
||
delActivityRegister,
|
||
addActivityRegister,
|
||
updateActivityRegister,
|
||
exportActivityRegister
|
||
} from '@/api/stgl/activityregister';
|
||
import { ActivityRegisterVO, ActivityRegisterForm, ActivityRegisterQuery } from '@/api/stgl/activityregister/types';
|
||
import { StudentVO } from '@/api/xg/student/types';
|
||
import StudentSelector from '@/components/StudentSelector/index.vue';
|
||
import { parseTime } from '@/utils/ruoyi';
|
||
import { globalHeaders } from '@/utils/request';
|
||
import { ArrowDown, UploadFilled } from '@element-plus/icons-vue';
|
||
import type { UploadInstance } from 'element-plus';
|
||
import request from '@/utils/request';
|
||
|
||
interface Props {
|
||
activityId: string | number;
|
||
isViewMode?: boolean;
|
||
}
|
||
|
||
// 扩展ActivityRegisterForm以包含选择的学生对象
|
||
interface ExtendedActivityRegisterForm extends ActivityRegisterForm {
|
||
selectedStudent?: StudentVO | null;
|
||
}
|
||
|
||
// 扩展ActivityRegisterVO以包含签到信息
|
||
interface ExtendedActivityRegisterVO extends ActivityRegisterVO {
|
||
latestSignInTime?: string;
|
||
latestSignOutTime?: string;
|
||
}
|
||
|
||
// 签到记录类型
|
||
interface SignRecord {
|
||
id: string | number;
|
||
signInTime?: string;
|
||
signOutTime?: string;
|
||
signInLocation?: string;
|
||
signOutLocation?: string;
|
||
overallStatus: number;
|
||
}
|
||
|
||
const props = defineProps<Props>();
|
||
|
||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||
const { sys_user_sex, stgl_registration_type, stgl_approval_status } = toRefs<any>(
|
||
proxy?.useDict('sys_user_sex', 'stgl_registration_type', 'stgl_approval_status')
|
||
);
|
||
|
||
const memberList = ref<ExtendedActivityRegisterVO[]>([]);
|
||
const buttonLoading = ref(false);
|
||
const loading = ref(true);
|
||
const showSearch = ref(true);
|
||
const ids = ref<Array<string | number>>([]);
|
||
const single = ref(true);
|
||
const multiple = ref(true);
|
||
const total = ref(0);
|
||
|
||
const queryFormRef = ref<ElFormInstance>();
|
||
const memberFormRef = ref<ElFormInstance>();
|
||
|
||
const dialog = reactive<DialogOption>({
|
||
visible: false,
|
||
title: ''
|
||
});
|
||
|
||
const viewDialog = reactive<DialogOption>({
|
||
visible: false,
|
||
title: ''
|
||
});
|
||
|
||
const signRecordsDialog = reactive<DialogOption>({
|
||
visible: false,
|
||
title: ''
|
||
});
|
||
|
||
const viewForm = reactive<ActivityRegisterVO>({
|
||
id: undefined,
|
||
activityId: undefined,
|
||
studentId: undefined,
|
||
studentName: '',
|
||
studentNo: '',
|
||
gender: '',
|
||
department: '',
|
||
major: '',
|
||
className: '',
|
||
phone: '',
|
||
registrationType: undefined,
|
||
teamName: '',
|
||
approvalStatus: undefined,
|
||
registrationTime: '',
|
||
approvalTime: '',
|
||
remark: '',
|
||
score: '',
|
||
scoreTime: ''
|
||
});
|
||
|
||
const initFormData: ExtendedActivityRegisterForm = {
|
||
id: undefined,
|
||
activityId: props.activityId,
|
||
studentId: undefined,
|
||
selectedStudent: null,
|
||
studentName: '',
|
||
studentNo: '',
|
||
gender: '',
|
||
department: '',
|
||
major: '',
|
||
className: '',
|
||
phone: '',
|
||
registrationType: '1',
|
||
teamName: '',
|
||
approvalStatus: 1,
|
||
registrationTime: undefined,
|
||
approvalTime: undefined,
|
||
remark: '',
|
||
score: '',
|
||
scoreTime: undefined
|
||
};
|
||
|
||
const data = reactive<PageData<ExtendedActivityRegisterForm, ActivityRegisterQuery>>({
|
||
form: { ...initFormData },
|
||
queryParams: {
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
activityId: props.activityId,
|
||
approvalStatus: undefined,
|
||
studentName: undefined,
|
||
studentNo: undefined,
|
||
phone: undefined
|
||
},
|
||
rules: {
|
||
selectedStudent: [{ required: true, message: '请选择学生', trigger: 'change' }],
|
||
registrationType: [{ required: true, message: '请选择报名类型', trigger: 'change' }],
|
||
teamName: [{ required: true, message: '请输入团队名称', trigger: 'blur' }]
|
||
}
|
||
});
|
||
|
||
const { queryParams, form, rules } = toRefs(data);
|
||
|
||
// 签到记录相关
|
||
const signRecords = ref<SignRecord[]>([]);
|
||
const signRecordsLoading = ref(false);
|
||
|
||
// 上传相关
|
||
const uploadRef = ref<UploadInstance>();
|
||
const upload = reactive<{
|
||
open: boolean;
|
||
title: string;
|
||
isUploading: boolean;
|
||
updateSupport: boolean;
|
||
url: string;
|
||
headers: { Authorization: string };
|
||
}>({
|
||
open: false,
|
||
title: '',
|
||
isUploading: false,
|
||
updateSupport: false,
|
||
url: import.meta.env.VITE_APP_BASE_API + '/stgl/activity/register/importData',
|
||
headers: globalHeaders()
|
||
});
|
||
|
||
/** 查询成员列表 */
|
||
const getList = async () => {
|
||
loading.value = true;
|
||
try {
|
||
// 构建查询参数,查询已审核通过的报名记录
|
||
const queryData = { ...queryParams.value };
|
||
|
||
// 如果状态字段为空或未选择,则查询已通过(approvalStatus=1)的报名记录
|
||
if (!queryData.approvalStatus) {
|
||
queryData.approvalStatus = 1;
|
||
}
|
||
|
||
console.log('ActivityRegistrationView: getList 开始查询,参数:', queryData);
|
||
console.log('ActivityRegistrationView: props.activityId:', props.activityId);
|
||
|
||
const res = await listActivityRegister(queryData);
|
||
console.log('ActivityRegistrationView: 查询结果完整对象:', res);
|
||
console.log('ActivityRegistrationView: res 的类型:', typeof res);
|
||
console.log('ActivityRegistrationView: res.data:', res.data);
|
||
console.log('ActivityRegistrationView: res.rows:', res.rows);
|
||
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 不存在');
|
||
console.error('ActivityRegistrationView: 完整响应:', JSON.stringify(res, null, 2));
|
||
proxy?.$modal.msgError('API返回数据格式异常,请检查控制台日志');
|
||
memberList.value = [];
|
||
total.value = 0;
|
||
return;
|
||
}
|
||
|
||
if (!Array.isArray(res.rows)) {
|
||
console.error('ActivityRegistrationView: API返回数据异常 - res.rows 不是数组');
|
||
console.error('ActivityRegistrationView: res.rows 实际值:', res.rows);
|
||
console.error('ActivityRegistrationView: res.rows 类型:', typeof res.rows);
|
||
proxy?.$modal.msgError('API返回数据格式异常 - rows字段不是数组');
|
||
memberList.value = [];
|
||
total.value = 0;
|
||
return;
|
||
}
|
||
|
||
console.log('ActivityRegistrationView: res.rows 数组长度:', res.rows.length);
|
||
|
||
// 为每个成员添加签到信息
|
||
const membersWithSignInfo = await Promise.all(
|
||
res.rows.map(async (member) => {
|
||
try {
|
||
// 查询该用户该活动的最新签到记录
|
||
const signRes = await request({
|
||
url: '/stgl/activityAttendance/getAllByActivityAndUser',
|
||
method: 'get',
|
||
params: {
|
||
activityId: props.activityId,
|
||
studentNo: member.studentNo
|
||
}
|
||
});
|
||
|
||
if (signRes.code === 200 && signRes.data && signRes.data.length > 0) {
|
||
// 获取最新的签到和签退时间
|
||
const latestSignIn = signRes.data
|
||
.filter((record: any) => record.signInTime)
|
||
.sort((a: any, b: any) => new Date(b.signInTime).getTime() - new Date(a.signInTime).getTime())[0];
|
||
|
||
const latestSignOut = signRes.data
|
||
.filter((record: any) => record.signOutTime)
|
||
.sort((a: any, b: any) => new Date(b.signOutTime).getTime() - new Date(a.signOutTime).getTime())[0];
|
||
|
||
return {
|
||
...member,
|
||
latestSignInTime: latestSignIn?.signInTime,
|
||
latestSignOutTime: latestSignOut?.signOutTime
|
||
};
|
||
}
|
||
} catch (error) {
|
||
console.warn('获取签到信息失败:', error);
|
||
}
|
||
|
||
return {
|
||
...member,
|
||
latestSignInTime: undefined,
|
||
latestSignOutTime: undefined
|
||
};
|
||
})
|
||
);
|
||
|
||
memberList.value = membersWithSignInfo;
|
||
total.value = res.total;
|
||
} catch (error: any) {
|
||
console.error('ActivityRegistrationView: 查询成员列表失败:', error);
|
||
console.error('ActivityRegistrationView: 错误详细信息:', {
|
||
message: error?.message,
|
||
stack: error?.stack,
|
||
response: error?.response,
|
||
request: error?.request,
|
||
config: error?.config
|
||
});
|
||
|
||
// 检查是否是网络错误
|
||
if (error?.code === 'NETWORK_ERROR' || error?.code === 'ECONNABORTED') {
|
||
proxy?.$modal.msgError('网络连接失败,请检查网络状态');
|
||
} else if (error?.response) {
|
||
// 服务器返回了错误响应
|
||
console.error('ActivityRegistrationView: 服务器响应错误:', error.response.data);
|
||
console.error('ActivityRegistrationView: HTTP状态码:', error.response.status);
|
||
proxy?.$modal.msgError(`服务器错误: ${error.response.status} - ${error.response.data?.msg || '未知错误'}`);
|
||
} else if (error?.request) {
|
||
// 请求发出但没有收到响应
|
||
console.error('ActivityRegistrationView: 请求超时或无响应');
|
||
proxy?.$modal.msgError('请求超时,请稍后重试');
|
||
} else {
|
||
// 其他错误(如代码逻辑错误)
|
||
proxy?.$modal.msgError(`查询成员列表失败: ${error?.message || '未知错误'}`);
|
||
}
|
||
|
||
// 确保在错误情况下清空数据
|
||
memberList.value = [];
|
||
total.value = 0;
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
|
||
/** 显示签到记录 */
|
||
const showSignRecords = async (member: ExtendedActivityRegisterVO) => {
|
||
signRecordsDialog.visible = true;
|
||
signRecordsDialog.title = `${member.studentName}(${member.studentNo})的签到记录`;
|
||
signRecordsLoading.value = true;
|
||
|
||
try {
|
||
const res = await request({
|
||
url: '/stgl/activityAttendance/getAllByActivityAndUser',
|
||
method: 'get',
|
||
params: {
|
||
activityId: props.activityId,
|
||
studentNo: member.studentNo
|
||
}
|
||
});
|
||
|
||
if (res.code === 200) {
|
||
signRecords.value = res.data || [];
|
||
} else {
|
||
signRecords.value = [];
|
||
proxy?.$modal.msgError(res.msg || '获取签到记录失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('获取签到记录失败:', error);
|
||
signRecords.value = [];
|
||
proxy?.$modal.msgError('获取签到记录失败');
|
||
} finally {
|
||
signRecordsLoading.value = false;
|
||
}
|
||
};
|
||
|
||
/** 格式化签到时间 */
|
||
const formatSignTime = (timeStr: string): string => {
|
||
if (!timeStr) return '';
|
||
const date = new Date(timeStr);
|
||
return date.toLocaleString('zh-CN', {
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit'
|
||
});
|
||
};
|
||
|
||
/** 取消按钮 */
|
||
const cancel = () => {
|
||
reset();
|
||
dialog.visible = false;
|
||
};
|
||
|
||
/** 表单重置 */
|
||
const reset = () => {
|
||
form.value = { ...initFormData, activityId: props.activityId };
|
||
memberFormRef.value?.resetFields();
|
||
};
|
||
|
||
/** 搜索按钮操作 */
|
||
const handleQuery = () => {
|
||
queryParams.value.pageNum = 1;
|
||
getList();
|
||
};
|
||
|
||
/** 重置按钮操作 */
|
||
const resetQuery = () => {
|
||
queryFormRef.value?.resetFields();
|
||
queryParams.value = {
|
||
pageNum: 1,
|
||
pageSize: 10,
|
||
activityId: props.activityId,
|
||
approvalStatus: 1,
|
||
studentName: undefined,
|
||
studentNo: undefined,
|
||
phone: undefined
|
||
};
|
||
handleQuery();
|
||
};
|
||
|
||
/** 多选框选中数据 */
|
||
const handleSelectionChange = (selection: ExtendedActivityRegisterVO[]) => {
|
||
ids.value = selection.map((item) => item.id);
|
||
single.value = selection.length != 1;
|
||
multiple.value = !selection.length;
|
||
};
|
||
|
||
/** 新增按钮操作 */
|
||
const handleAdd = () => {
|
||
reset();
|
||
dialog.visible = true;
|
||
dialog.title = '添加活动报名';
|
||
};
|
||
|
||
/** 修改按钮操作 */
|
||
const handleUpdate = async (row?: ExtendedActivityRegisterVO) => {
|
||
reset();
|
||
const _id = row?.id || ids.value[0];
|
||
const res = await getActivityRegister(_id);
|
||
Object.assign(form.value, res.data);
|
||
|
||
// 设置学生选择器数据
|
||
if (res.data.studentName) {
|
||
form.value.selectedStudent = {
|
||
id: res.data.studentId || 0,
|
||
studentNo: res.data.studentNo || '',
|
||
name: res.data.studentName,
|
||
gender: res.data.gender || '0',
|
||
phone: res.data.phone || '',
|
||
collegeName: res.data.department || '',
|
||
majorName: res.data.major || '',
|
||
className: res.data.className || ''
|
||
} as StudentVO;
|
||
}
|
||
|
||
dialog.visible = true;
|
||
dialog.title = '修改活动报名';
|
||
};
|
||
|
||
/** 提交按钮 */
|
||
const submitForm = () => {
|
||
memberFormRef.value?.validate(async (valid: boolean) => {
|
||
if (valid) {
|
||
buttonLoading.value = true;
|
||
|
||
// 创建提交数据,排除selectedStudent字段
|
||
const { selectedStudent, ...submitData } = form.value;
|
||
|
||
// 确保包含必要字段
|
||
submitData.activityId = props.activityId;
|
||
submitData.approvalStatus = 1; // 新增的报名默认为已通过状态
|
||
|
||
try {
|
||
if (form.value.id) {
|
||
await updateActivityRegister(submitData as ActivityRegisterForm);
|
||
} else {
|
||
await addActivityRegister(submitData as ActivityRegisterForm);
|
||
}
|
||
proxy?.$modal.msgSuccess('操作成功');
|
||
dialog.visible = false;
|
||
await getList();
|
||
} catch (error) {
|
||
console.error('提交失败:', error);
|
||
} finally {
|
||
buttonLoading.value = false;
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
/** 删除按钮操作 */
|
||
const handleDelete = async (row?: ExtendedActivityRegisterVO) => {
|
||
const _ids = row?.id || ids.value;
|
||
await proxy?.$modal.confirm('是否确认删除选中的活动报名记录?');
|
||
|
||
try {
|
||
if (Array.isArray(_ids)) {
|
||
await delActivityRegister(_ids.join(','));
|
||
} else {
|
||
await delActivityRegister(_ids);
|
||
}
|
||
|
||
proxy?.$modal.msgSuccess('删除成功');
|
||
await getList();
|
||
} catch (error) {
|
||
console.error('删除失败:', error);
|
||
}
|
||
};
|
||
|
||
/** 查看按钮操作 */
|
||
const handleView = async (row: ExtendedActivityRegisterVO) => {
|
||
const res = await getActivityRegister(row.id);
|
||
Object.assign(viewForm, res.data);
|
||
viewDialog.visible = true;
|
||
viewDialog.title = '查看活动报名详情';
|
||
};
|
||
|
||
/** 学生选择变化处理 */
|
||
const handleStudentChange = (student: StudentVO | null) => {
|
||
if (student) {
|
||
form.value.studentId = student.id;
|
||
form.value.studentNo = student.studentNo;
|
||
form.value.studentName = student.name;
|
||
form.value.gender = student.gender;
|
||
form.value.department = student.collegeName;
|
||
form.value.major = student.majorName;
|
||
form.value.className = student.className;
|
||
form.value.phone = student.phone;
|
||
} else {
|
||
form.value.studentId = undefined;
|
||
form.value.studentNo = '';
|
||
form.value.studentName = '';
|
||
form.value.gender = '';
|
||
form.value.department = '';
|
||
form.value.major = '';
|
||
form.value.className = '';
|
||
form.value.phone = '';
|
||
}
|
||
};
|
||
|
||
/** 导出按钮操作 */
|
||
const handleExport = () => {
|
||
proxy?.download(
|
||
'stgl/activity/register/export',
|
||
{
|
||
...queryParams.value
|
||
},
|
||
`activity_register_${new Date().getTime()}.xlsx`
|
||
);
|
||
};
|
||
|
||
/** 导入模板下载 */
|
||
const importTemplate = () => {
|
||
proxy?.download('stgl/activity/register/importTemplate', {}, `活动报名导入模板_${new Date().getTime()}.xlsx`);
|
||
};
|
||
|
||
/** 导入按钮操作 */
|
||
const handleImport = () => {
|
||
upload.title = '活动报名导入';
|
||
upload.open = true;
|
||
};
|
||
|
||
/** 提交上传文件 */
|
||
const submitFileForm = () => {
|
||
uploadRef.value?.submit();
|
||
};
|
||
|
||
/** 文件上传中处理 */
|
||
const handleFileUploadProgress = () => {
|
||
upload.isUploading = true;
|
||
};
|
||
|
||
/** 文件上传成功处理 */
|
||
const handleFileSuccess = (response: any) => {
|
||
upload.open = false;
|
||
upload.isUploading = false;
|
||
uploadRef.value?.clearFiles();
|
||
proxy?.$modal.msgSuccess(response.msg);
|
||
if (response.msg.includes('成功')) {
|
||
getList();
|
||
}
|
||
};
|
||
|
||
// 监听activityId变化,重新获取数据
|
||
watch(
|
||
() => props.activityId,
|
||
(newActivityId) => {
|
||
if (newActivityId) {
|
||
console.log('ActivityRegistrationView: activityId changed to:', newActivityId);
|
||
// 更新查询参数中的activityId
|
||
queryParams.value.activityId = newActivityId;
|
||
// 重新获取数据
|
||
getList();
|
||
}
|
||
},
|
||
{ immediate: true }
|
||
);
|
||
|
||
// 暴露给父组件的方法
|
||
defineExpose({
|
||
getList
|
||
});
|
||
|
||
onMounted(() => {
|
||
// 组件挂载时如果有activityId则获取数据(immediate: true的watch会处理这个情况)
|
||
if (props.activityId) {
|
||
getList();
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.activity-registration-view {
|
||
padding: 0;
|
||
}
|
||
|
||
.view-description {
|
||
padding: 12px;
|
||
border: 1px solid #e4e7ed;
|
||
border-radius: 4px;
|
||
min-height: 80px;
|
||
font-size: 14px;
|
||
color: #303133;
|
||
line-height: 1.6;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.time-button {
|
||
padding: 0;
|
||
height: auto;
|
||
font-size: 14px;
|
||
color: #409eff;
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.time-button:hover {
|
||
color: #66b1ff;
|
||
}
|
||
|
||
.text-gray-400 {
|
||
color: #909399;
|
||
}
|
||
</style>
|