社团管理1.0

This commit is contained in:
李旭明 committed 2025-08-25 15:53:02 +08:00
1 parent 4d8267e561
commit d256028f8a
83 files changed
+2040 -1096

No files matched your search

+10 -2
View File
@@ -1,6 +1,14 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { StglActivityVO, StglActivityForm, StglActivityQuery, ClubOption, ActivityCopyParams, ActivitySummaryParams, ActivityRatingVO } from './types';
import {
StglActivityVO,
StglActivityForm,
StglActivityQuery,
ClubOption,
ActivityCopyParams,
ActivitySummaryParams,
ActivityRatingVO
} from './types';
/**
* 查询活动信息列表
@@ -137,4 +145,4 @@ export const getActivityRatings = (activityId: string | number): AxiosPromise<Ac
url: '/stgl/activity/' + activityId + '/ratings',
method: 'get'
});
};
};
+1 -1
View File
@@ -118,4 +118,4 @@ export const signOut = (data: StglActivityAttendanceForm) => {
method: 'post',
data: data
});
};
};
+1 -1
View File
@@ -329,4 +329,4 @@ export interface StglActivityAttendanceQuery {
* 日期范围参数
*/
params?: any;
}
}
+1 -1
View File
@@ -299,4 +299,4 @@ export function batchReviewSubmissions(ids: string, reviewStatus: number, review
method: 'put',
params: params
});
}
}
+1 -1
View File
@@ -533,4 +533,4 @@ export interface MaterialTypeOption {
* 是否选中
*/
checked?: boolean;
}
}
+12 -12
View File
@@ -12,7 +12,7 @@ export const listActivityRegister = (query?: any): AxiosPromise<StglActivityRegi
return request({
url: '/stgl/activity/register/list',
method: 'get',
params: query,
params: query
});
};
@@ -23,7 +23,7 @@ export const listActivityRegister = (query?: any): AxiosPromise<StglActivityRegi
export const getActivityRegister = (id: string | number): AxiosPromise<StglActivityRegistrationVo> => {
return request({
url: '/stgl/activity/register/' + id,
method: 'get',
method: 'get'
});
};
@@ -35,7 +35,7 @@ export const addActivityRegister = (data: StglActivityRegistrationForm) => {
return request({
url: '/stgl/activity/register',
method: 'post',
data: data,
data: data
});
};
@@ -47,7 +47,7 @@ export const updateActivityRegister = (data: StglActivityRegistrationForm) => {
return request({
url: '/stgl/activity/register',
method: 'put',
data: data,
data: data
});
};
@@ -58,7 +58,7 @@ export const updateActivityRegister = (data: StglActivityRegistrationForm) => {
export const delActivityRegister = (id: string | number | Array<string | number>) => {
return request({
url: '/stgl/activity/register/' + id,
method: 'delete',
method: 'delete'
});
};
@@ -74,8 +74,8 @@ export const approveActivityRegister = (id: string | number, approvalStatus: num
method: 'post',
params: {
approvalStatus,
approvalRemark,
},
approvalRemark
}
});
};
@@ -92,8 +92,8 @@ export const batchApproveActivityRegister = (ids: Array<string | number>, approv
data: {
ids,
approvalStatus,
approvalRemark,
},
approvalRemark
}
});
};
@@ -121,7 +121,7 @@ export const recordActivityScore = (id: string | number, score: string) => {
url: `/stgl/activity/register/score/${id}`,
method: 'post',
params: {
score,
},
score
}
});
};
};
+1 -1
View File
@@ -341,4 +341,4 @@ export interface TeamMemberInfo {
export interface PageQuery {
pageNum?: number;
pageSize?: number;
}
}
@@ -11,7 +11,7 @@ export const listRegistrableActivities = (query?: any): AxiosPromise<StglActivit
return request({
url: '/stgl/activity/registerhall/list',
method: 'get',
params: query,
params: query
});
};
@@ -22,7 +22,7 @@ export const listRegistrableActivities = (query?: any): AxiosPromise<StglActivit
export const getActivityDetail = (id: string | number): AxiosPromise<StglActivityVo> => {
return request({
url: '/stgl/activity/registerhall/' + id,
method: 'get',
method: 'get'
});
};
@@ -34,7 +34,7 @@ export const registerActivity = (data: StglActivityRegistrationForm) => {
return request({
url: '/stgl/activity/registerhall/register',
method: 'post',
data: data,
data: data
});
};
@@ -45,7 +45,7 @@ export const registerActivity = (data: StglActivityRegistrationForm) => {
export const checkRegistration = (activityId: string | number): AxiosPromise<boolean> => {
return request({
url: `/stgl/activity/registerhall/checkRegistration/${activityId}`,
method: 'get',
method: 'get'
});
};
@@ -57,6 +57,6 @@ export const getMyRegistrations = (query?: any): AxiosPromise<StglActivityRegist
return request({
url: '/stgl/activity/registerhall/myRegistrations',
method: 'get',
params: query,
params: query
});
};
};
@@ -422,4 +422,4 @@ export interface TeamMemberInfo {
* 是否为队长(0-否 1-是)
*/
isLeader: number;
}
}
+19
View File
@@ -120,3 +120,22 @@ export const checkPresidentRole = (id: string | number) => {
}
});
};
/**
* 修改社团报名开启状态
* @param id 社团ID
* @param sighOpen 报名开启状态(Y/N)
*/
export const changeSighOpenStatus = (id: string | number, sighOpen: string) => {
return request({
url: '/stgl/club/changeSighOpenStatus',
method: 'put',
params: {
id,
sighOpen
},
headers: {
isEncrypt: 'true'
}
});
};
+10
View File
@@ -189,6 +189,11 @@ export interface StglClubVO {
*/
status: string;
/**
* 是否开启报名
*/
sighOpen: string;
/**
* 备注
*/
@@ -381,6 +386,11 @@ export interface StglClubForm {
*/
status?: string;
/**
* 是否开启报名
*/
sighOpen?: string;
/**
* 备注
*/
+6 -6
View File
@@ -67,9 +67,9 @@ export interface StglMemberVO {
email: string;
/**
* 角色类型
* 成员角色
*/
roleType: number;
roleType: string;
/**
* 角色名称
@@ -164,9 +164,9 @@ export interface StglMemberForm {
email?: string;
/**
* 角色类型
* 成员角色
*/
roleType?: number;
roleType?: string;
/**
* 角色名称
@@ -216,9 +216,9 @@ export interface StglMemberQuery extends PageQuery {
studentName?: string;
/**
* 角色类型
* 成员角色
*/
roleType?: number;
roleType?: string;
/**
* 状态
+13 -3
View File
@@ -1,6 +1,16 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { MyClubVO, MyVolunteerTeamVO, MyActivityVO, MyRegistrationVO, ActivityRatingForm, MyRegistrationQuery, MyClubQuery, MyVolunteerQuery, MyActivityQuery } from './types';
import {
MyClubVO,
MyVolunteerTeamVO,
MyActivityVO,
MyRegistrationVO,
ActivityRatingForm,
MyRegistrationQuery,
MyClubQuery,
MyVolunteerQuery,
MyActivityQuery
} from './types';
/**
* 查询我参与的社团列表
@@ -18,7 +28,7 @@ export function getMyClubs(query?: MyClubQuery): AxiosPromise<MyClubVO[]> {
*/
export function getMyVolunteerTeams(query?: MyVolunteerQuery): AxiosPromise<MyVolunteerTeamVO[]> {
return request({
url: '/stgl/my/volunteerTeams',
url: '/stgl/my/volunteerTeams',
method: 'get',
params: query
});
@@ -65,4 +75,4 @@ export function canRateActivity(activityId: string | number): AxiosPromise<boole
url: `/stgl/my/activities/${activityId}/canRate`,
method: 'get'
});
}
}
+1 -1
View File
@@ -211,4 +211,4 @@ export interface MyRegistrationQuery {
registrationType?: string;
/** 审核状态 */
approvalStatus?: string;
}
}
+10
View File
@@ -49,6 +49,11 @@ export interface StglClubRegisterVO {
*/
canRegister: boolean;
/**
* 报名开启状态
*/
sighOpen?: string;
/**
* 招新周期
*/
@@ -242,6 +247,11 @@ export interface StglClubRegisterQuery extends PageQuery {
*/
canRegister?: boolean;
/**
* 报名开启状态
*/
sighOpen?: string;
/**
* 日期范围参数
*/
+23 -3
View File
@@ -4,6 +4,16 @@ export interface VolunteerMemberVO {
*/
id: string | number;
/**
* 用户ID
*/
userId?: string | number;
/**
* 成员类型(1-社团成员,2-志愿者成员)
*/
memberType?: number;
/**
* 学号
*/
@@ -65,7 +75,7 @@ export interface VolunteerMemberVO {
joinTime: string;
/**
* 状态(0正常 1停用)
* 状态(0待审核 1审核中 2已加入 3已驳回 4已退出)
*/
status: string;
@@ -101,6 +111,16 @@ export interface VolunteerMemberForm {
*/
id?: string | number;
/**
* 用户ID
*/
userId?: string | number;
/**
* 成员类型(1-社团成员,2-志愿者成员)
*/
memberType?: number;
/**
* 学号
*/
@@ -157,7 +177,7 @@ export interface VolunteerMemberForm {
joinTime?: string;
/**
* 状态(0正常 1停用)
* 状态(0待审核 1审核中 2已加入 3已驳回 4已退出)
*/
status?: string;
@@ -209,7 +229,7 @@ export interface VolunteerMemberQuery {
relationId?: string | number;
/**
* 状态(0正常 1停用)
* 状态(0待审核 1审核中 2已加入 3已驳回 4已退出)
*/
status?: string;
+19
View File
@@ -212,3 +212,22 @@ export function listVolunteerTeamForRegistration(query) {
params: query
});
}
/**
* 修改志愿者团队报名开启状态
* @param id 团队ID
* @param sighOpen 报名开启状态(Y/N)
*/
export function changeSighOpenStatus(id, sighOpen) {
return request({
url: '/stgl/volunteerTeam/changeSighOpenStatus',
method: 'put',
params: {
id,
sighOpen
},
headers: {
isEncrypt: 'true'
}
});
}
+33 -46
View File
@@ -1,33 +1,16 @@
<template>
<div class="activity-selector">
<el-input
v-model="selectedActivityName"
placeholder="请选择活动"
readonly
@click="openSelector"
style="cursor: pointer"
>
<el-input v-model="selectedActivityName" placeholder="请选择活动" readonly @click="openSelector" style="cursor: pointer">
<template #suffix>
<el-icon class="el-input__icon"><Search /></el-icon>
</template>
</el-input>
<!-- 活动选择对话框 -->
<el-dialog
v-model="dialogVisible"
title="选择活动"
width="800px"
append-to-body
>
<el-dialog v-model="dialogVisible" title="选择活动" width="800px" append-to-body>
<!-- 搜索框 -->
<div class="mb-4">
<el-input
v-model="searchKeyword"
placeholder="请输入活动名称搜索"
clearable
@input="handleSearch"
style="width: 300px"
>
<el-input v-model="searchKeyword" placeholder="请输入活动名称搜索" clearable @input="handleSearch" style="width: 300px">
<template #prefix>
<el-icon><Search /></el-icon>
</template>
@@ -158,10 +141,10 @@ const confirmSelect = () => {
if (selectedActivity.value) {
const activityId = selectedActivity.value.id;
selectedActivityName.value = selectedActivity.value.activityName;
emit('update:modelValue', activityId);
emit('change', activityId);
dialogVisible.value = false;
}
};
@@ -169,13 +152,13 @@ const confirmSelect = () => {
/** 获取状态类型 */
const getStatusType = (status: string): string => {
const statusMap: Record<string, string> = {
'1': 'info', // 筹备中
'2': 'warning', // 待审批
'3': 'success', // 已通过
'4': 'primary', // 进行中
'5': 'info', // 已结束
'6': 'danger', // 已取消
'7': 'danger' // 已驳回
'1': 'info', // 筹备中
'2': 'warning', // 待审批
'3': 'success', // 已通过
'4': 'primary', // 进行中
'5': 'info', // 已结束
'6': 'danger', // 已取消
'7': 'danger' // 已驳回
};
return statusMap[status] || 'info';
};
@@ -202,24 +185,28 @@ const formatDate = (dateStr: string): string => {
};
/** 监听外部值变化 */
watch(() => props.modelValue, async (newVal) => {
if (newVal) {
try {
// 这里可以根据ID获取活动名称
const res = await listStglActivity({
pageNum: 1,
pageSize: 1
});
if (res.rows && res.rows.length > 0) {
selectedActivityName.value = res.rows[0].activityName;
watch(
() => props.modelValue,
async (newVal) => {
if (newVal) {
try {
// 这里可以根据ID获取活动名称
const res = await listStglActivity({
pageNum: 1,
pageSize: 1
});
if (res.rows && res.rows.length > 0) {
selectedActivityName.value = res.rows[0].activityName;
}
} catch (error) {
console.error('获取活动信息失败:', error);
}
} catch (error) {
console.error('获取活动信息失败:', error);
} else {
selectedActivityName.value = '';
}
} else {
selectedActivityName.value = '';
}
}, { immediate: true });
},
{ immediate: true }
);
</script>
<style scoped>
@@ -235,4 +222,4 @@ watch(() => props.modelValue, async (newVal) => {
.dialog-footer {
text-align: right;
}
</style>
</style>
+11 -3
View File
@@ -93,24 +93,32 @@ const currentHash = computed(() => {
watch(
() => props.modelValue,
(newValue) => {
console.log('AdvisorManager - 外部值变化:', newValue);
if (!updateLock.value) {
updateLock.value = true;
nextTick(() => {
advisors.value = newValue.map((item) => ({ ...item }));
// 确保深拷贝,避免引用问题
advisors.value = newValue ? newValue.map((item) => ({ ...item })) : [];
console.log('AdvisorManager - 更新内部值:', advisors.value);
updateLock.value = false;
});
}
},
{ immediate: true }
{ immediate: true, deep: false }
);
// 监听内部值变化 - 使用hash比较避免深度比较
watch(currentHash, (newHash) => {
watch(currentHash, (newHash, oldHash) => {
// 避免初始化时的重复触发
if (oldHash === undefined) return;
console.log('AdvisorManager - 内部值变化检测到:', newHash);
if (!updateLock.value) {
updateLock.value = true;
// 延迟emit,避免在TabPane环境下的立即更新冲突
nextTick(() => {
const newValue = advisors.value.map((advisor) => ({ ...advisor }));
console.log('AdvisorManager - 发送更新事件:', newValue);
emit('update:modelValue', newValue);
// 重新添加change事件,但添加防护机制
emit('change', newValue);
+27 -31
View File
@@ -1,7 +1,7 @@
<template>
<div class="club-selector">
<el-select
:model-value="modelValue"
<el-select
:model-value="modelValue"
@update:model-value="handleChange"
:placeholder="placeholder"
:clearable="clearable"
@@ -14,12 +14,7 @@
value-key="uniqueKey"
style="width: 100%"
>
<el-option
v-for="option in options"
:key="`${option.relatedType}-${option.relatedClubId}`"
:label="formatOptionLabel(option)"
:value="option"
>
<el-option v-for="option in options" :key="option.uniqueKey || `${option.relatedType}-${option.relatedClubId}`" :label="formatOptionLabel(option)" :value="option">
<div class="option-content">
<span class="option-name">{{ option.relatedClubName }}</span>
<span class="option-type">{{ getTypeLabel(option.relatedType) }}</span>
@@ -31,12 +26,7 @@
<el-dialog v-model="dialogVisible" title="选择社团/志愿者队伍" width="800px" append-to-body>
<el-tabs v-model="activeTab" type="card">
<el-tab-pane label="社团" name="club">
<el-table
:data="clubOptions"
@row-click="handleRowClick"
highlight-current-row
height="400"
>
<el-table :data="clubOptions" @row-click="handleRowClick" highlight-current-row height="400">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="社团名称" prop="relatedClubName" />
<el-table-column label="类型" width="80" align="center">
@@ -47,12 +37,7 @@
</el-table>
</el-tab-pane>
<el-tab-pane label="志愿者队伍" name="volunteer">
<el-table
:data="volunteerOptions"
@row-click="handleRowClick"
highlight-current-row
height="400"
>
<el-table :data="volunteerOptions" @row-click="handleRowClick" highlight-current-row height="400">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="队伍名称" prop="relatedClubName" />
<el-table-column label="类型" width="100" align="center">
@@ -63,7 +48,7 @@
</el-table>
</el-tab-pane>
</el-tabs>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
@@ -111,6 +96,12 @@ const clubOptions = ref<ClubOption[]>([]);
const volunteerOptions = ref<ClubOption[]>([]);
const selectedOption = ref<ClubOption | null>(null);
// 监听 modelValue 变化
import { watch } from 'vue';
watch(() => props.modelValue, (newValue) => {
console.log('ClubSelector - modelValue changed:', newValue);
}, { immediate: true });
// 格式化选项标签
const formatOptionLabel = (option: ClubOption): string => {
const typeText = option.relatedType === 1 ? '社团' : '志愿者队伍';
@@ -124,6 +115,7 @@ const getTypeLabel = (type: number): string => {
// 处理值变化
const handleChange = (value: ClubOption | null) => {
console.log('ClubSelector - handleChange:', value);
emit('update:modelValue', value);
emit('change', value);
};
@@ -134,10 +126,10 @@ const remoteMethod = async (query: string) => {
loading.value = true;
try {
// 根据查询条件过滤选项
const filteredOptions = [...clubOptions.value, ...volunteerOptions.value].filter(option =>
const filteredOptions = [...clubOptions.value, ...volunteerOptions.value].filter((option) =>
option.relatedClubName.toLowerCase().includes(query.toLowerCase())
);
options.value = filteredOptions.map(item => ({
options.value = filteredOptions.map((item) => ({
...item,
uniqueKey: item.uniqueKey || `${item.relatedType}-${item.relatedClubId}`
}));
@@ -168,21 +160,25 @@ const confirmSelection = () => {
const loadOptions = async () => {
loading.value = true;
try {
const [clubRes, volunteerRes] = await Promise.all([
getClubOptions(),
getVolunteerTeamOptions()
]);
const [clubRes, volunteerRes] = await Promise.all([getClubOptions(), getVolunteerTeamOptions()]);
// 为每个选项添加唯一标识
clubOptions.value = (clubRes.data || []).map(item => ({
clubOptions.value = (clubRes.data || []).map((item) => ({
...item,
uniqueKey: `${item.relatedType}-${item.relatedClubId}`
}));
volunteerOptions.value = (volunteerRes.data || []).map(item => ({
volunteerOptions.value = (volunteerRes.data || []).map((item) => ({
...item,
uniqueKey: `${item.relatedType}-${item.relatedClubId}`
}));
options.value = [...clubOptions.value, ...volunteerOptions.value];
console.log('ClubSelector - 加载选项数据完成:', {
clubCount: clubOptions.value.length,
volunteerCount: volunteerOptions.value.length,
totalCount: options.value.length,
options: options.value
});
} catch (error) {
console.error('加载选项失败:', error);
} finally {
@@ -223,4 +219,4 @@ onMounted(() => {
justify-content: flex-end;
gap: 10px;
}
</style>
</style>
+25 -60
View File
@@ -13,11 +13,7 @@
</div>
<div v-else class="organizer-list">
<div
v-for="(organizer, index) in coOrganizers"
:key="index"
class="organizer-item"
>
<div v-for="(organizer, index) in coOrganizers" :key="index" class="organizer-item">
<div class="organizer-content">
<div class="organizer-info">
<div class="info-row">
@@ -30,13 +26,7 @@
</div>
</div>
<div class="organizer-actions">
<el-button
type="danger"
circle
size="small"
@click="handleDelete(index)"
title="删除"
>
<el-button type="danger" circle size="small" @click="handleDelete(index)" title="删除">
<el-icon><Delete /></el-icon>
</el-button>
</div>
@@ -45,43 +35,20 @@
</div>
<!-- 添加/编辑承办方对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="500px"
append-to-body
>
<el-form
ref="formRef"
:model="currentOrganizer"
:rules="rules"
label-width="100px"
>
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" append-to-body>
<el-form ref="formRef" :model="currentOrganizer" :rules="rules" label-width="100px">
<el-form-item label="承办方名称" prop="name">
<el-input
v-model="currentOrganizer.name"
placeholder="请输入承办方名称"
maxlength="100"
show-word-limit
/>
<el-input v-model="currentOrganizer.name" placeholder="请输入承办方名称" maxlength="100" show-word-limit />
</el-form-item>
<el-form-item label="联系人" prop="contact">
<el-input
v-model="currentOrganizer.contact"
placeholder="请输入联系人姓名"
maxlength="50"
/>
<el-input v-model="currentOrganizer.contact" placeholder="请输入联系人姓名" maxlength="50" />
</el-form-item>
<el-form-item label="联系电话" prop="phone">
<el-input
v-model="currentOrganizer.phone"
placeholder="请输入联系电话"
maxlength="20"
/>
<el-input v-model="currentOrganizer.phone" placeholder="请输入联系电话" maxlength="20" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleCancel">取消</el-button>
@@ -138,9 +105,7 @@ const rules: FormRules = {
{ min: 1, max: 100, message: '承办方名称长度在1到100个字符', trigger: 'blur' }
],
contact: [
{ max: 50, message: '联系人姓名长度不能超过50个字符', trigger: 'blur' }
],
contact: [{ max: 50, message: '联系人姓名长度不能超过50个字符', trigger: 'blur' }],
phone: [
{ max: 20, message: '联系电话长度不能超过20个字符', trigger: 'blur' },
{ pattern: /^[0-9\-\s\+\(\)]*$/, message: '请输入正确的电话号码格式', trigger: 'blur' }
@@ -177,42 +142,42 @@ const handleAdd = () => {
dialogVisible.value = true;
};
// 删除承办方
const handleDelete = (index: number) => {
ElMessageBox.confirm('确定要删除这个承办方吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
coOrganizers.value.splice(index, 1);
ElMessage.success('删除成功');
}).catch(() => {
// 用户取消删除
});
})
.then(() => {
coOrganizers.value.splice(index, 1);
ElMessage.success('删除成功');
})
.catch(() => {
// 用户取消删除
});
};
// 确认添加/编辑
const handleConfirm = () => {
if (!formRef.value) return;
formRef.value.validate((valid) => {
if (valid) {
// 保存当前操作类型和数据,避免状态重置后丢失
const isEdit = editIndex.value >= 0;
const currentData = { ...currentOrganizer.value };
const currentIndex = editIndex.value;
// 立即关闭弹窗,避免后续操作影响弹窗状态
dialogVisible.value = false;
// 重置编辑状态
editIndex.value = -1;
// 重置表单
formRef.value?.resetFields();
// 延迟一帧更新数据,确保弹窗已完全关闭
setTimeout(() => {
if (isEdit) {
@@ -233,10 +198,10 @@ const handleConfirm = () => {
const handleCancel = () => {
// 立即关闭弹窗
dialogVisible.value = false;
// 重置编辑状态
editIndex.value = -1;
// 重置表单
formRef.value?.resetFields();
};
@@ -328,4 +293,4 @@ const handleCancel = () => {
justify-content: flex-end;
gap: 10px;
}
</style>
</style>
+21 -60
View File
@@ -13,11 +13,7 @@
</div>
<div v-else class="instructor-list">
<div
v-for="(instructor, index) in instructors"
:key="index"
class="instructor-item"
>
<div v-for="(instructor, index) in instructors" :key="index" class="instructor-item">
<div class="instructor-content">
<div class="instructor-info">
<div class="info-row">
@@ -56,58 +52,25 @@
</div>
<!-- 添加/编辑指导老师对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="500px"
append-to-body
>
<el-form
ref="formRef"
:model="currentInstructor"
:rules="rules"
label-width="100px"
>
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" append-to-body>
<el-form ref="formRef" :model="currentInstructor" :rules="rules" label-width="100px">
<el-form-item label="老师姓名" prop="name">
<el-input
v-model="currentInstructor.name"
placeholder="请输入老师姓名"
maxlength="50"
show-word-limit
/>
<el-input v-model="currentInstructor.name" placeholder="请输入老师姓名" maxlength="50" show-word-limit />
</el-form-item>
<el-form-item label="所属部门" prop="dept">
<el-input
v-model="currentInstructor.dept"
placeholder="请输入所属部门"
maxlength="100"
/>
<el-input v-model="currentInstructor.dept" placeholder="请输入所属部门" maxlength="100" />
</el-form-item>
<el-form-item label="联系电话" prop="phone">
<el-input
v-model="currentInstructor.phone"
placeholder="请输入联系电话"
maxlength="20"
/>
<el-input v-model="currentInstructor.phone" placeholder="请输入联系电话" maxlength="20" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input
v-model="currentInstructor.email"
placeholder="请输入邮箱地址"
maxlength="100"
/>
<el-input v-model="currentInstructor.email" placeholder="请输入邮箱地址" maxlength="100" />
</el-form-item>
<el-form-item label="指导学时" prop="hours">
<el-input-number
v-model="currentInstructor.hours"
:min="0"
:max="9999"
placeholder="请输入指导学时"
style="width: 100%"
/>
<el-input-number v-model="currentInstructor.hours" :min="0" :max="9999" placeholder="请输入指导学时" style="width: 100%" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleCancel">取消</el-button>
@@ -165,9 +128,7 @@ const rules: FormRules = {
{ required: true, message: '请输入老师姓名', trigger: 'blur' },
{ min: 1, max: 50, message: '老师姓名长度在1到50个字符', trigger: 'blur' }
],
dept: [
{ max: 100, message: '所属部门长度不能超过100个字符', trigger: 'blur' }
],
dept: [{ max: 100, message: '所属部门长度不能超过100个字符', trigger: 'blur' }],
phone: [
{ max: 20, message: '联系电话长度不能超过20个字符', trigger: 'blur' },
{ pattern: /^[0-9\-\s\+\(\)]*$/, message: '请输入正确的电话号码格式', trigger: 'blur' }
@@ -176,9 +137,7 @@ const rules: FormRules = {
{ max: 100, message: '邮箱长度不能超过100个字符', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
],
hours: [
{ type: 'number', min: 0, max: 9999, message: '指导学时必须在0到9999之间', trigger: 'blur' }
]
hours: [{ type: 'number', min: 0, max: 9999, message: '指导学时必须在0到9999之间', trigger: 'blur' }]
};
// 监听props变化
@@ -226,18 +185,20 @@ const handleDelete = (index: number) => {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
instructors.value.splice(index, 1);
ElMessage.success('删除成功');
}).catch(() => {
// 用户取消删除
});
})
.then(() => {
instructors.value.splice(index, 1);
ElMessage.success('删除成功');
})
.catch(() => {
// 用户取消删除
});
};
// 确认添加/编辑
const handleConfirm = async () => {
if (!formRef.value) return;
await formRef.value.validate((valid) => {
if (valid) {
if (editIndex.value >= 0) {
@@ -350,4 +311,4 @@ const handleCancel = () => {
justify-content: flex-end;
gap: 10px;
}
</style>
</style>
@@ -2,12 +2,7 @@
<div class="material-type-selector">
<div class="type-options">
<el-checkbox-group v-model="selectedTypes" @change="handleChange">
<el-checkbox
v-for="option in materialTypes"
:key="option.value"
:label="option.value"
class="type-checkbox"
>
<el-checkbox v-for="option in materialTypes" :key="option.value" :label="option.value" class="type-checkbox">
{{ option.label }}
</el-checkbox>
</el-checkbox-group>
@@ -29,9 +24,7 @@ const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { stgl_material_type } = toRefs<any>(
proxy?.useDict('stgl_material_type')
);
const { stgl_material_type } = toRefs<any>(proxy?.useDict('stgl_material_type'));
const selectedTypes = ref<string[]>([]);
@@ -48,13 +41,17 @@ const handleChange = (value: string[]) => {
};
/** 监听外部值变化 */
watch(() => props.modelValue, (newVal) => {
if (newVal) {
selectedTypes.value = newVal.split(',').filter(item => item.trim());
} else {
selectedTypes.value = [];
}
}, { immediate: true });
watch(
() => props.modelValue,
(newVal) => {
if (newVal) {
selectedTypes.value = newVal.split(',').filter((item) => item.trim());
} else {
selectedTypes.value = [];
}
},
{ immediate: true }
);
</script>
<style scoped>
@@ -89,4 +86,4 @@ watch(() => props.modelValue, (newVal) => {
color: #409eff;
font-weight: 500;
}
</style>
</style>
+12 -19
View File
@@ -6,17 +6,10 @@
<template #header>
<div class="card-header">
<span>成员 {{ index + 1 }}</span>
<el-button
type="danger"
size="small"
icon="Delete"
circle
@click="removeMember(index)"
:disabled="memberList.length <= 1"
/>
<el-button type="danger" size="small" icon="Delete" circle @click="removeMember(index)" :disabled="memberList.length <= 1" />
</div>
</template>
<el-form :model="member" label-width="80px" size="small">
<el-row :gutter="20">
<el-col :span="12">
@@ -30,7 +23,7 @@
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="性别">
@@ -46,7 +39,7 @@
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="院系">
@@ -59,7 +52,7 @@
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="班级">
@@ -76,7 +69,7 @@
</el-card>
</div>
</div>
<div class="add-member">
<el-button type="primary" icon="Plus" @click="addMember">添加成员</el-button>
</div>
@@ -179,18 +172,18 @@ nextTick(() => {
.team-member-manager {
.member-list {
margin-bottom: 20px;
.member-item {
margin-bottom: 16px;
&:last-child {
margin-bottom: 0;
}
}
.member-card {
border-radius: 8px;
.card-header {
display: flex;
justify-content: space-between;
@@ -200,11 +193,11 @@ nextTick(() => {
}
}
}
.add-member {
text-align: center;
padding: 16px 0;
border-top: 1px dashed #e4e7ed;
}
}
</style>
</style>
@@ -239,7 +239,7 @@
<!-- 签到操作区域 -->
<div v-if="form.activityId" class="sign-action-section">
<h4 class="section-title">签到操作</h4>
<el-row :gutter="20" style="text-align: center;">
<el-row :gutter="20" style="text-align: center">
<el-col :span="12">
<el-button
type="primary"
@@ -284,7 +284,7 @@
<el-descriptions-item label="活动地点">{{ viewForm.location }}</el-descriptions-item>
<el-descriptions-item label="开始时间">{{ parseTime(viewForm.startTime) }}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ parseTime(viewForm.endTime) }}</el-descriptions-item>
<!-- 用户信息 -->
<el-descriptions-item label="姓名">{{ viewForm.studentName }}</el-descriptions-item>
<el-descriptions-item label="学号">{{ viewForm.studentNo }}</el-descriptions-item>
@@ -293,7 +293,7 @@
<el-descriptions-item label="班级">{{ viewForm.className }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ viewForm.phone }}</el-descriptions-item>
<el-descriptions-item label="邮箱">{{ viewForm.email }}</el-descriptions-item>
<!-- 签到信息 -->
<el-descriptions-item label="签到状态">
<dict-tag :options="stgl_activity_sign_status" :value="viewForm.overallStatus" />
@@ -331,9 +331,7 @@ import {
import { StglActivityAttendanceVO, StglActivityAttendanceQuery, StglActivityAttendanceForm } from '@/api/stgl/activityattendance/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { stgl_activity_type, stgl_activity_sign_status } = toRefs<any>(
proxy?.useDict('stgl_activity_type', 'stgl_activity_sign_status')
);
const { stgl_activity_type, stgl_activity_sign_status } = toRefs<any>(proxy?.useDict('stgl_activity_type', 'stgl_activity_sign_status'));
const attendanceList = ref<StglActivityAttendanceVO[]>([]);
const availableActivities = ref<StglActivityAttendanceVO[]>([]);
@@ -489,10 +487,10 @@ const handleActivityChange = async (activityId: string | number) => {
}
// 设置选中的活动信息
const activity = availableActivities.value.find(item => item.activityId === activityId);
const activity = availableActivities.value.find((item) => item.activityId === activityId);
if (activity) {
selectedActivity.value = activity;
// 填充用户信息
form.value.studentName = activity.studentName;
form.value.studentNo = activity.studentNo;
@@ -537,7 +535,7 @@ const handleSignIn = async () => {
await signIn(signInData);
proxy?.$modal.msgSuccess('签到成功');
// 刷新当前签到状态
await handleActivityChange(form.value.activityId);
await getList();
@@ -571,7 +569,7 @@ const handleSignOut = async () => {
await signOut(signOutData);
proxy?.$modal.msgSuccess('签退成功');
// 刷新当前签到状态
await handleActivityChange(form.value.activityId);
await getList();
@@ -701,4 +699,4 @@ onMounted(() => {
display: flex;
align-items: center;
}
</style>
</style>
@@ -19,7 +19,7 @@
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="detail-item">
@@ -34,7 +34,7 @@
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="detail-item">
@@ -49,7 +49,7 @@
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="detail-item">
@@ -66,7 +66,7 @@
</div>
</el-col>
</el-row>
<div class="detail-item">
<label class="detail-label">活动海报</label>
<div class="detail-value">
@@ -78,7 +78,7 @@
</div>
</div>
</div>
<div class="detail-item">
<label class="detail-label">活动简介</label>
<div class="detail-value">
@@ -208,7 +208,7 @@
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="detail-item">
@@ -223,7 +223,7 @@
</div>
</el-col>
</el-row>
<el-row :gutter="20" v-if="activity.maxParticipants">
<el-col :span="12">
<div class="detail-item">
@@ -232,7 +232,7 @@
</div>
</el-col>
</el-row>
<el-row :gutter="20" v-if="activity.maxTeams || activity.teamSizeMin || activity.teamSizeMax">
<el-col :span="8" v-if="activity.maxTeams">
<div class="detail-item">
@@ -253,7 +253,7 @@
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="detail-item">
@@ -272,6 +272,11 @@
</el-row>
</el-tab-pane>
<!-- 报名查看标签页 -->
<el-tab-pane label="报名查看" name="registration-view">
<ActivityRegistrationView :activity-id="activity.id" :is-view-mode="true" />
</el-tab-pane>
<!-- 其他信息标签页(仅已办活动显示) -->
<el-tab-pane label="其他信息" name="other" v-if="activity.isFinished === 1">
<div class="detail-item">
@@ -282,7 +287,7 @@
</div>
</div>
</div>
<div class="detail-item" v-if="summaryAttachmentList.length > 0">
<label class="detail-label">总结报告附件</label>
<div class="detail-value">
@@ -301,7 +306,7 @@
</div>
</div>
</div>
<div class="detail-item">
<label class="detail-label">活动成果</label>
<div class="detail-value">
@@ -310,7 +315,7 @@
</div>
</div>
</div>
<div class="detail-item" v-if="achievementsAttachmentList.length > 0">
<label class="detail-label">成果附件</label>
<div class="detail-value">
@@ -334,7 +339,7 @@
<!-- 活动评分标签页 -->
<el-tab-pane label="活动评分" name="rating" v-if="activity.id">
<ActivityRatingView :activity-id="activity.id" />
<!-- 统计信息区域(只在活动评分标签页显示) -->
<div class="statistics-section">
<el-divider content-position="left">统计信息</el-divider>
@@ -362,6 +367,7 @@
import { computed, ref, onMounted, watch } from 'vue';
import { StglActivityVO, CoOrganizerInfo, InstructorInfo } from '@/api/stgl/activity/types';
import ActivityRatingView from './ActivityRatingView.vue';
import ActivityRegistrationView from './ActivityRegistrationView.vue';
import { listByIds } from '@/api/system/oss';
interface Props {
@@ -404,11 +410,10 @@ const instructorList = computed<InstructorInfo[]>(() => {
// 加载附件信息
const loadAttachments = async () => {
// 重置附件列表
summaryAttachmentList.value = [];
achievementsAttachmentList.value = [];
// 加载总结报告附件
if (props.activity.summaryAttachments) {
// 检查数据格式:如果以 [ 开头,说明是JSON格式;否则是ossId格式
@@ -423,7 +428,7 @@ const loadAttachments = async () => {
}
} else {
// ossId格式:通过API获取真实附件信息
const ossIds = attachmentStr.split(',').filter(id => id.trim());
const ossIds = attachmentStr.split(',').filter((id) => id.trim());
if (ossIds.length > 0) {
try {
const res = await listByIds(ossIds.join(','));
@@ -454,7 +459,7 @@ const loadAttachments = async () => {
}
} else {
// ossId格式:通过API获取真实附件信息
const ossIds = attachmentStr.split(',').filter(id => id.trim());
const ossIds = attachmentStr.split(',').filter((id) => id.trim());
if (ossIds.length > 0) {
try {
const res = await listByIds(ossIds.join(','));
@@ -480,11 +485,15 @@ const downloadAttachment = (file: any) => {
};
// 监听props变化,重新加载附件信息
watch(() => props.activity, (newActivity) => {
if (newActivity && newActivity.id) {
loadAttachments();
}
}, { immediate: true });
watch(
() => props.activity,
(newActivity) => {
if (newActivity && newActivity.id) {
loadAttachments();
}
},
{ immediate: true }
);
// 组件挂载时加载附件信息
onMounted(() => {
@@ -657,4 +666,4 @@ onMounted(() => {
.attachment-icon {
font-size: 14px;
}
</style>
</style>
@@ -3,7 +3,7 @@
<div v-if="ratings.length === 0" class="empty-state">
<el-empty description="暂无评分信息" :image-size="80" />
</div>
<div v-else>
<div class="rating-summary">
<el-row :gutter="20">
@@ -18,9 +18,9 @@
</el-col>
</el-row>
</div>
<el-divider />
<el-table :data="ratings" height="400" border>
<el-table-column label="评分人" prop="userName" width="120" />
<el-table-column label="评分" prop="ratingScore" width="120" align="center">
@@ -59,13 +59,13 @@ const averageRating = computed(() => {
// 计算最高评分
const maxRating = computed(() => {
if (ratings.value.length === 0) return 0;
return Math.max(...ratings.value.map(rating => rating.ratingScore));
return Math.max(...ratings.value.map((rating) => rating.ratingScore));
});
// 加载评分数据
const loadRatings = async () => {
if (!props.activityId) return;
loading.value = true;
try {
const res = await getActivityRatings(props.activityId);
@@ -101,4 +101,4 @@ onMounted(() => {
text-align: center;
padding: 60px 20px;
}
</style>
</style>
+34 -46
View File
@@ -124,24 +124,10 @@
></el-button>
</el-tooltip>
<el-tooltip content="已报名" placement="top" v-else-if="isRegistered(scope.row)">
<el-button
link
type="warning"
icon="Check"
size="default"
style="font-size: 16px"
disabled
></el-button>
<el-button link type="warning" icon="Check" size="default" style="font-size: 16px" disabled></el-button>
</el-tooltip>
<el-tooltip content="报名已结束" placement="top" v-else>
<el-button
link
type="info"
icon="Clock"
size="default"
style="font-size: 16px"
disabled
></el-button>
<el-button link type="info" icon="Clock" size="default" style="font-size: 16px" disabled></el-button>
</el-tooltip>
</template>
</el-table-column>
@@ -169,7 +155,7 @@
<el-form-item label="活动名称">
<el-input v-model="selectedActivity.activityName" disabled />
</el-form-item>
<el-form-item label="报名类型" prop="registrationType">
<el-radio-group v-model="registerForm.registrationType" @change="handleRegistrationTypeChange">
<el-radio v-if="isRegistrationTypeAllowed(1)" :value="1">个人报名</el-radio>
@@ -240,7 +226,7 @@
<el-form-item label="团队描述" prop="teamDescription">
<el-input v-model="registerForm.teamDescription" type="textarea" :rows="3" placeholder="请输入团队描述" />
</el-form-item>
<!-- 团队负责人信息 -->
<el-divider content-position="left">团队负责人</el-divider>
<el-row :gutter="20">
@@ -301,18 +287,18 @@
</el-row>
<!-- 团队成员管理 -->
<el-divider content-position="left" style="margin-left: 0px;">团队成员</el-divider>
<VicePresidentManager
:model-value="registerForm.teamMembersNew"
<el-divider content-position="left" style="margin-left: 0px">团队成员</el-divider>
<VicePresidentManager
:model-value="registerForm.teamMembersNew"
title="团队管理"
addButtonText="添加团队成员"
emptyTip="暂无团队成员,点击上方按钮添加"
selectPlaceholder="选择团队成员"
@change="handleTeamMembersNewChange"
@change="handleTeamMembersNewChange"
/>
</div>
<el-form-item label="申请理由" prop="remark" style="margin-top: 24px;">
<el-form-item label="申请理由" prop="remark" style="margin-top: 24px">
<el-input v-model="registerForm.remark" type="textarea" :rows="4" placeholder="请简要说明您的申请理由和个人优势(选填)" />
</el-form-item>
</el-form>
@@ -398,10 +384,12 @@ const data = reactive<PageData<StglActivityRegistrationForm, StglActivityQuery>>
const { queryParams } = toRefs(data);
// 报名表单
const registerForm = reactive<StglActivityRegistrationForm & {
presidentStudent?: StudentVO | null;
teamMembersNew?: VicePresidentVO[];
}>({
const registerForm = reactive<
StglActivityRegistrationForm & {
presidentStudent?: StudentVO | null;
teamMembersNew?: VicePresidentVO[];
}
>({
activityId: undefined,
registrationType: undefined,
studentName: undefined,
@@ -426,10 +414,10 @@ const registerRules = reactive({
studentNo: [{ required: true, message: '学号不能为空', trigger: 'blur' }],
phone: [
{ required: true, message: '联系电话不能为空', trigger: 'blur' },
{
pattern: /^1[3-9]\d{9}$/,
message: '请输入正确的手机号码',
trigger: 'blur'
{
pattern: /^1[3-9]\d{9}$/,
message: '请输入正确的手机号码',
trigger: 'blur'
} as any
],
teamName: [{ required: true, message: '团队名称不能为空', trigger: 'blur' }]
@@ -442,7 +430,7 @@ const getList = async () => {
const res = await listRegistrableActivities(queryParams.value);
activityList.value = res.rows;
total.value = res.total;
// 批量检查用户报名状态
await checkUserRegistrationStatus();
} catch (error) {
@@ -464,7 +452,7 @@ const checkUserRegistrationStatus = async () => {
userRegistrationStatus.value[activity.id as number] = false;
}
});
await Promise.all(promises);
};
@@ -492,7 +480,7 @@ const handleView = async (row: StglActivityVO) => {
const handleRegister = async (row: StglActivityVO) => {
selectedActivity.value = row;
resetRegisterForm();
registerDialog.visible = true;
};
@@ -516,7 +504,7 @@ const resetRegisterForm = () => {
remark: undefined,
presidentStudent: null
});
nextTick(() => {
registerFormRef.value?.resetFields();
});
@@ -527,7 +515,7 @@ const loadUserInfoToForm = async () => {
try {
const response = await getPersonalInfo();
const studentInfo: StudentMsgForm = response.data;
if (studentInfo) {
registerForm.studentName = studentInfo.name || '';
registerForm.studentNo = studentInfo.studentNo || '';
@@ -555,7 +543,7 @@ const handleRegistrationTypeChange = async (value: number) => {
registerForm.teamMembers = [];
registerForm.teamMembersNew = [];
registerForm.presidentStudent = null;
// 自动填充当前用户信息
await loadUserInfoToForm();
} else if (value === 2) {
@@ -583,7 +571,7 @@ const handlePresidentChange = async (student: StudentVO | null) => {
registerForm.department = student.collegeName || '';
registerForm.major = student.majorName || '';
registerForm.className = student.className || '';
// 清空email,让用户手动填写或通过其他方式获取
registerForm.email = undefined;
} else {
@@ -602,9 +590,9 @@ const handlePresidentChange = async (student: StudentVO | null) => {
/** 新版团队成员变化处理 */
const handleTeamMembersNewChange = (members: VicePresidentVO[]) => {
registerForm.teamMembersNew = members;
// 同步到原有字段,用于兼容后端
registerForm.teamMembers = members.map(member => ({
registerForm.teamMembers = members.map((member) => ({
userId: member.student?.id || 0,
studentNo: member.student?.studentNo || '',
studentName: member.name || member.student?.name || '',
@@ -616,7 +604,7 @@ const handleTeamMembersNewChange = (members: VicePresidentVO[]) => {
className: member.class || member.student?.className || '',
isLeader: 0 // 团队成员,非负责人
}));
// 验证团队人数限制
const totalMembers = members.length + 1; // 包含负责人
if (selectedActivity.value.teamSizeMin && totalMembers < selectedActivity.value.teamSizeMin) {
@@ -630,7 +618,7 @@ const handleTeamMembersNewChange = (members: VicePresidentVO[]) => {
/** 团队成员变化处理(兼容原有方法) */
const handleTeamMembersChange = (members: TeamMemberInfo[]) => {
registerForm.teamMembers = members;
// 验证团队人数限制
const totalMembers = members.length + 1; // 包含负责人
if (selectedActivity.value.teamSizeMin && totalMembers < selectedActivity.value.teamSizeMin) {
@@ -679,7 +667,7 @@ const submitRegister = () => {
if (registerForm.registrationType === 2) {
submitData.teamName = registerForm.teamName;
submitData.teamDescription = registerForm.teamDescription;
// 将团队成员数组转换为JSON字符串(后端期望String类型)
if (registerForm.teamMembers && Array.isArray(registerForm.teamMembers)) {
submitData.teamMembers = JSON.stringify(registerForm.teamMembers);
@@ -688,11 +676,11 @@ const submitRegister = () => {
}
}
// 个人报名时不发送任何团队相关字段
await registerActivity(submitData);
proxy?.$modal.msgSuccess('报名成功,请等待审核');
registerDialog.visible = false;
// 更新报名状态
userRegistrationStatus.value[selectedActivity.value.id as number] = true;
} catch (error) {
@@ -792,4 +780,4 @@ onMounted(() => {
margin-bottom: 0 !important;
}
}
</style>
</style>
+198 -44
View File
@@ -45,6 +45,11 @@
</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
@@ -80,23 +85,20 @@
<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-button icon="Edit" @click="handleUpdate()" 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')"
@click="handleDelete()"
<el-button
icon="Delete"
:disabled="multiple || (selectedActivity && selectedActivity.activityStatus !== '1')"
@click="handleDelete()"
v-hasPermi="['stgl:activity:remove']"
:title="selectedActivity && selectedActivity.activityStatus !== '1' ? '只有筹备中的活动可以删除' : ''"
>删除</el-button>
>删除</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="success" icon="Download" @click="handleExport" v-hasPermi="['stgl:activity:export']">导出</el-button>
@@ -180,7 +182,7 @@
</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="280" fixed="right">
<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
@@ -226,6 +228,17 @@
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="scope.row.activityStatus === '1'">
<el-button
link
@@ -356,7 +369,11 @@
<div class="section-divider">
<h4 class="section-title">指导老师信息</h4>
</div>
<AdvisorManager v-model="form.advisors" @change="handleAdvisorsChange" />
<AdvisorManager
:key="`advisor-manager-${form.id || 'new'}-${formKey}`"
v-model="form.advisors"
@change="handleAdvisorsChange"
/>
</el-tab-pane>
<el-tab-pane label="报名设置" name="registration">
@@ -427,24 +444,9 @@
</el-row>
</el-tab-pane>
<el-tab-pane label="其他信息" name="other" v-if="form.isFinished === 1">
<el-form-item label="活动总结报告" prop="summary">
<el-input v-model="form.summary" type="textarea" placeholder="请输入活动总结报告" resize="none" :rows="6" />
</el-form-item>
<el-form-item label="总结报告附件" prop="summaryAttachments">
<file-upload v-model="form.summaryAttachments" />
</el-form-item>
<el-form-item label="活动成果" prop="achievements">
<el-input v-model="form.achievements" type="textarea" placeholder="请输入活动成果" resize="none" :rows="6" />
</el-form-item>
<el-form-item label="成果附件" prop="achievementsAttachments">
<file-upload v-model="form.achievementsAttachments" />
</el-form-item>
</el-tab-pane>
<el-tab-pane label="活动评分" name="rating" v-if="form.id && !dialog.title.includes('新增')">
<ActivityRatingView :activity-id="form.id" />
</el-tab-pane>
</el-tabs>
</el-form>
<template #footer>
@@ -509,11 +511,20 @@
</div>
</template>
</el-dialog>
<!-- 成员审核弹窗 -->
<ActivityMembersDialog
v-model:visible="membersDialog.visible"
:activity-id="selectedActivity?.id"
:activity-name="selectedActivity?.activityName"
ref="membersDialogRef"
/>
</div>
</template>
<script setup name="StglActivity" lang="ts">
import { nextTick } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import {
listStglActivity,
getStglActivity,
@@ -531,9 +542,13 @@ 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';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const route = useRoute();
const router = useRouter();
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')
);
@@ -549,11 +564,13 @@ 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 membersDialogRef = ref();
const dialog = reactive<DialogOption>({
visible: false,
@@ -575,6 +592,11 @@ const summaryDialog = reactive<DialogOption>({
title: ''
});
const membersDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const copyLoading = ref(false);
const summaryLoading = ref(false);
@@ -619,7 +641,7 @@ const initFormData: StglActivityForm = {
remark: undefined
};
const data = reactive<PageData<StglActivityForm, StglActivityQuery>>({
const data = reactive<PageData<StglActivityForm, StglActivityQuery & { relatedClub?: ClubOption | null }>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
@@ -631,6 +653,7 @@ const data = reactive<PageData<StglActivityForm, StglActivityQuery>>({
endTime: undefined,
location: undefined,
relatedClubId: undefined,
relatedClub: null,
organizerName: undefined,
registrationType: undefined,
registrationStart: undefined,
@@ -654,6 +677,45 @@ 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,
@@ -714,6 +776,14 @@ const reset = () => {
// 使用nextTick来避免递归更新
nextTick(() => {
form.value = { ...initFormData };
// 确保指导老师字段被正确初始化
form.value.advisors = [];
form.value.instructorList = [];
form.value.instructors = undefined;
// 更新formKey以强制重新渲染组件
formKey.value++;
activeTab.value = 'basic';
nextTick(() => {
activityFormRef.value?.resetFields();
@@ -744,19 +814,66 @@ const handleSelectionChange = (selection: StglActivityVO[]) => {
/** 新增按钮操作 */
const handleAdd = () => {
reset();
// 先重置表单
form.value = { ...initFormData };
activeTab.value = 'basic';
// 确保指导老师字段被正确初始化
form.value.advisors = [];
form.value.instructorList = [];
form.value.instructors = undefined;
// 更新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;
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
});
}
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);
// 检查活动状态是否允许修改(2:报名中、3:报名结束、4:进行中、5:已结束 不允许修改)
if (['2', '3', '4', '5'].includes(res.data.activityStatus)) {
const statusDict = stgl_activity_status.value.find((dict: any) => dict.value === res.data.activityStatus);
@@ -857,17 +974,23 @@ const submitForm = () => {
// 处理AdvisorManager组件的指导老师数据
if (submitData.advisors && submitData.advisors.length > 0) {
console.log('提交前 - 原始advisors数据:', submitData.advisors);
// 确保advisors数据完整
submitData.advisors = submitData.advisors.map((advisor: any) => {
return {
const processedAdvisor = {
...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 || ''
};
console.log('处理后的指导老师数据:', processedAdvisor);
return processedAdvisor;
});
submitData.instructors = JSON.stringify(submitData.advisors);
console.log('提交的指导老师JSON数据:', submitData.instructors);
// 同步第一个指导老师信息到instructorList(保持兼容性)
if (submitData.advisors.length > 0) {
@@ -877,7 +1000,13 @@ const submitForm = () => {
phone: advisor.phone,
email: advisor.email
}));
console.log('同步的instructorList数据:', submitData.instructorList);
}
} else {
console.log('提交时 - 没有指导老师数据或数据为空');
submitData.advisors = [];
submitData.instructors = '';
submitData.instructorList = [];
}
try {
@@ -902,7 +1031,7 @@ const submitForm = () => {
const handleDelete = async (row?: StglActivityVO) => {
const _ids = row?.id || ids.value;
let confirmMessage = '';
// 检查删除权限 - 只有筹备中(状态1)的活动可以删除
if (row) {
if (row.activityStatus !== '1') {
@@ -914,15 +1043,15 @@ const handleDelete = async (row?: StglActivityVO) => {
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) => item.activityStatus !== '1');
if (nonDeletableItems.length > 0) {
const names = nonDeletableItems.map(item => item.activityName).join('、');
const names = nonDeletableItems.map((item) => item.activityName).join('、');
proxy?.$modal.msgWarning(`活动"${names}"不是筹备中状态,只有筹备中的活动可以删除`);
return;
}
if (selectedItems.length === 1) {
confirmMessage = `是否确认删除活动"${selectedItems[0].activityName}"?`;
} else {
@@ -1009,12 +1138,12 @@ const handleSummary = async (row?: StglActivityVO) => {
summaryForm.achievements = fullActivity.achievements || '';
summaryForm.summaryAttachments = fullActivity.summaryAttachments || '';
summaryForm.achievementsAttachments = fullActivity.achievementsAttachments || '';
console.log('handleSummary - 设置到表单的数据:', {
summaryAttachments: summaryForm.summaryAttachments,
achievementsAttachments: summaryForm.achievementsAttachments
});
summaryDialog.visible = true;
};
@@ -1039,7 +1168,7 @@ const confirmSummary = async () => {
summaryAttachments: summaryForm.summaryAttachments,
achievementsAttachments: summaryForm.achievementsAttachments
});
await summarizeActivity({
id: summaryForm.id!,
summary: summaryForm.summary,
@@ -1067,8 +1196,6 @@ const handleAchievementsAttachmentsChange = (attachments: string) => {
summaryForm.achievementsAttachments = attachments;
};
/** 导出按钮操作 */
const handleExport = async () => {
try {
@@ -1082,11 +1209,26 @@ const handleExport = async () => {
/** 关联社团变化处理 */
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;
@@ -1099,7 +1241,9 @@ const handleInstructorsChange = (instructors: InstructorInfo[]) => {
/** 指导老师变化处理 */
const handleAdvisorsChange = (advisors: AdvisorVO[]) => {
console.log('handleAdvisorsChange - 指导老师数据变化:', advisors);
form.value.advisors = advisors;
console.log('handleAdvisorsChange - 更新后的form.advisors:', form.value.advisors);
};
/** 报名方式变化处理 */
@@ -1122,6 +1266,13 @@ const shouldShowSummaryButton = (row: StglActivityVO): boolean => {
return row.activityStatus === '4' || row.activityStatus === '5';
};
/** 成员审核按钮操作 */
const handleMembersReview = (row: StglActivityVO) => {
selectedActivity.value = row;
membersDialog.visible = true;
membersDialog.title = `${row.activityName}活动报名管理`;
};
/** 格式化日期 */
const formatDate = (dateStr: string): string => {
if (!dateStr) return '';
@@ -1153,6 +1304,7 @@ const getAdvisorNames = (activity: StglActivityVO): string[] => {
};
onMounted(() => {
initUrlParams();
getList();
});
</script>
@@ -1269,4 +1421,6 @@ onMounted(() => {
border: 1px solid #dcdfe6;
}
}
</style>
@@ -18,18 +18,10 @@
<el-descriptions-item label="截止时间">
{{ formatDate(task.deadline) || '未设置' }}
</el-descriptions-item>
<el-descriptions-item label="文件大小限制">
{{ task.fileSizeLimit || 0 }} MB
</el-descriptions-item>
<el-descriptions-item label="文件数量限制">
{{ task.fileCountLimit || 0 }} 个
</el-descriptions-item>
<el-descriptions-item label="总参与人数">
{{ task.totalParticipants || 0 }} 人
</el-descriptions-item>
<el-descriptions-item label="已提交人数">
{{ task.submittedCount || 0 }} 人
</el-descriptions-item>
<el-descriptions-item label="文件大小限制"> {{ task.fileSizeLimit || 0 }} MB </el-descriptions-item>
<el-descriptions-item label="文件数量限制"> {{ task.fileCountLimit || 0 }} 个 </el-descriptions-item>
<el-descriptions-item label="总参与人数"> {{ task.totalParticipants || 0 }} 人 </el-descriptions-item>
<el-descriptions-item label="已提交人数"> {{ task.submittedCount || 0 }} 人 </el-descriptions-item>
<el-descriptions-item label="创建者">
{{ task.createByName || '未知' }}
</el-descriptions-item>
@@ -38,13 +30,7 @@
</el-descriptions-item>
<el-descriptions-item label="材料收集类型" :span="2">
<div class="collection-types">
<el-tag
v-for="type in parseCollectionTypes(task.collectionTypes)"
:key="type"
type="info"
size="small"
class="mr-2"
>
<el-tag v-for="type in parseCollectionTypes(task.collectionTypes)" :key="type" type="info" size="small" class="mr-2">
{{ getMaterialTypeLabel(type) }}
</el-tag>
</div>
@@ -96,13 +82,7 @@
</el-card>
<!-- 提交记录列表 -->
<el-table
v-loading="submissionsLoading"
:data="submissions"
border
style="width: 100%"
max-height="400px"
>
<el-table v-loading="submissionsLoading" :data="submissions" border style="width: 100%" max-height="400px">
<el-table-column label="学号" prop="studentNo" width="120" />
<el-table-column label="学生姓名" prop="studentName" width="100" />
<el-table-column label="提交类型" width="100">
@@ -120,10 +100,7 @@
</el-table-column>
<el-table-column label="审核状态" width="100">
<template #default="scope">
<el-tag
:type="getReviewStatusType(scope.row.reviewStatus) as any"
size="small"
>
<el-tag :type="getReviewStatusType(scope.row.reviewStatus) as any" size="small">
{{ getReviewStatusText(scope.row.reviewStatus) }}
</el-tag>
</template>
@@ -140,14 +117,7 @@
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
size="small"
@click="viewSubmissionDetail(scope.row)"
>
查看详情
</el-button>
<el-button link type="primary" size="small" @click="viewSubmissionDetail(scope.row)"> 查看详情 </el-button>
</template>
</el-table-column>
</el-table>
@@ -169,12 +139,7 @@
</el-tabs>
<!-- 提交详情对话框 -->
<el-dialog
v-model="submissionDetailVisible"
title="提交详情"
width="600px"
append-to-body
>
<el-dialog v-model="submissionDetailVisible" title="提交详情" width="600px" append-to-body>
<SubmissionDetailView :submission="selectedSubmission" />
</el-dialog>
</div>
@@ -192,9 +157,7 @@ interface Props {
const props = defineProps<Props>();
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { stgl_material_type } = toRefs<any>(
proxy?.useDict('stgl_material_type')
);
const { stgl_material_type } = toRefs<any>(proxy?.useDict('stgl_material_type'));
const activeTab = ref('task');
const submissionsLoading = ref(false);
@@ -213,7 +176,7 @@ const submissionQuery = reactive({
/** 获取提交记录 */
const getSubmissions = async () => {
if (!props.task.id) return;
submissionsLoading.value = true;
try {
submissionQuery.taskId = String(props.task.id);
@@ -230,7 +193,7 @@ const getSubmissions = async () => {
/** 获取统计信息 */
const getStatistics = async () => {
if (!props.task.id) return;
try {
const res = await getSubmissionStatistics(props.task.id);
statistics.value = res.data || {};
@@ -248,7 +211,7 @@ const viewSubmissionDetail = (submission: StglMaterialSubmissionVO) => {
/** 解析收集类型 */
const parseCollectionTypes = (types: string): string[] => {
if (!types) return [];
return types.split(',').filter(type => type.trim());
return types.split(',').filter((type) => type.trim());
};
/** 获取材料类型标签 */
@@ -273,9 +236,9 @@ const formatDate = (dateStr: string): string => {
/** 获取审核状态类型 */
const getReviewStatusType = (status: number): string => {
const statusMap: Record<number, string> = {
0: 'warning', // 待审核
1: 'success', // 通过
2: 'danger' // 驳回
0: 'warning', // 待审核
1: 'success', // 通过
2: 'danger' // 驳回
};
return statusMap[status] || 'info';
};
@@ -299,12 +262,16 @@ watch(activeTab, (newTab) => {
});
/** 监听任务变化 */
watch(() => props.task.id, (newId) => {
if (newId && activeTab.value === 'submissions') {
getSubmissions();
getStatistics();
}
}, { immediate: true });
watch(
() => props.task.id,
(newId) => {
if (newId && activeTab.value === 'submissions') {
getSubmissions();
getStatistics();
}
},
{ immediate: true }
);
</script>
<style scoped>
@@ -370,4 +337,4 @@ watch(() => props.task.id, (newId) => {
display: flex;
justify-content: center;
}
</style>
</style>
@@ -7,21 +7,16 @@
<el-radio :label="2">团队提交</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="文本内容" prop="contentText">
<el-input
v-model="form.contentText"
type="textarea"
placeholder="请输入文本内容"
:rows="4"
/>
<el-input v-model="form.contentText" type="textarea" placeholder="请输入文本内容" :rows="4" />
</el-form-item>
<el-form-item label="附件上传">
<file-upload v-model="form.fileUrls" />
</el-form-item>
</el-form>
<div class="dialog-footer">
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="loading">确定</el-button>
@@ -95,4 +90,4 @@ const handleCancel = () => {
gap: 12px;
margin-top: 24px;
}
</style>
</style>
@@ -68,9 +68,9 @@ const formatDate = (dateStr: string): string => {
/** 获取审核状态类型 */
const getReviewStatusType = (status: number): string => {
const statusMap: Record<number, string> = {
0: 'warning', // 待审核
1: 'success', // 通过
2: 'danger' // 驳回
0: 'warning', // 待审核
1: 'success', // 通过
2: 'danger' // 驳回
};
return statusMap[status] || 'info';
};
@@ -98,4 +98,4 @@ const getReviewStatusText = (status: number): string => {
max-height: 200px;
overflow-y: auto;
}
</style>
</style>
+33 -79
View File
@@ -67,45 +67,26 @@
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['stgl:activitymaterial:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
icon="Edit"
:disabled="single"
@click="handleUpdate()"
v-hasPermi="['stgl:activitymaterial:edit']"
>修改</el-button>
<el-button icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['stgl:activitymaterial:edit']">修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
icon="Delete"
:disabled="multiple"
@click="handleDelete()"
v-hasPermi="['stgl:activitymaterial:remove']"
>删除</el-button>
<el-button icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['stgl:activitymaterial:remove']">删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
icon="Open"
:disabled="single"
@click="handleStatusChange(1)"
v-hasPermi="['stgl:activitymaterial:status']"
>开启</el-button>
<el-button icon="Open" :disabled="single" @click="handleStatusChange(1)" v-hasPermi="['stgl:activitymaterial:status']">开启</el-button>
</el-col>
<el-col :span="1.5">
<el-button
icon="TurnOff"
:disabled="single"
@click="handleStatusChange(0)"
v-hasPermi="['stgl:activitymaterial:status']"
>关闭</el-button>
<el-button icon="TurnOff" :disabled="single" @click="handleStatusChange(0)" v-hasPermi="['stgl:activitymaterial:status']">关闭</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
icon="Upload"
:disabled="single || !canUpload"
@click="handleUpload()"
<el-button
type="success"
icon="Upload"
:disabled="single || !canUpload"
@click="handleUpload()"
v-hasPermi="['stgl:activitymaterial:upload']"
>上传</el-button>
>上传</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="success" icon="Download" @click="handleExport" v-hasPermi="['stgl:activitymaterial:export']">导出</el-button>
@@ -213,18 +194,10 @@
<ActivitySelector v-model="form.activityId" @change="handleActivityChange" />
</el-form-item>
<el-form-item label="任务名称" prop="taskName">
<el-input
v-model="form.taskName"
placeholder="请输入任务名称"
/>
<el-input v-model="form.taskName" placeholder="请输入任务名称" />
</el-form-item>
<el-form-item label="收集说明" prop="taskDescription">
<el-input
v-model="form.taskDescription"
type="textarea"
placeholder="请输入收集说明"
:rows="3"
/>
<el-input v-model="form.taskDescription" type="textarea" placeholder="请输入收集说明" :rows="3" />
</el-form-item>
<el-form-item label="截止时间" prop="deadline">
<el-date-picker
@@ -239,30 +212,13 @@
<MaterialTypeSelector v-model="form.collectionTypes" />
</el-form-item>
<el-form-item label="文件大小要求(MB)" prop="fileSizeLimit">
<el-input-number
v-model="form.fileSizeLimit"
:min="1"
:max="1024"
style="width: 100%"
controls-position="right"
/>
<el-input-number v-model="form.fileSizeLimit" :min="1" :max="1024" style="width: 100%" controls-position="right" />
</el-form-item>
<el-form-item label="上传数量上限" prop="fileCountLimit">
<el-input-number
v-model="form.fileCountLimit"
:min="1"
:max="100"
style="width: 100%"
controls-position="right"
/>
<el-input-number v-model="form.fileCountLimit" :min="1" :max="100" style="width: 100%" controls-position="right" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="form.remark"
type="textarea"
placeholder="请输入备注"
:rows="2"
/>
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" :rows="2" />
</el-form-item>
</el-form>
<template #footer>
@@ -280,10 +236,10 @@
<!-- 上传材料对话框 -->
<el-dialog title="上传材料" v-model="uploadDialog.visible" width="600px" append-to-body>
<MaterialUploadForm
:task-id="uploadDialog.taskId"
<MaterialUploadForm
:task-id="uploadDialog.taskId"
:activity-id="uploadDialog.activityId"
@success="handleUploadSuccess"
@success="handleUploadSuccess"
@cancel="uploadDialog.visible = false"
v-if="uploadDialog.visible"
/>
@@ -292,11 +248,11 @@
</template>
<script setup lang="ts">
import {
listStglMaterialTask,
getStglMaterialTask,
delStglMaterialTask,
addStglMaterialTask,
import {
listStglMaterialTask,
getStglMaterialTask,
delStglMaterialTask,
addStglMaterialTask,
updateStglMaterialTask,
updateTaskStatus,
exportStglMaterialTask
@@ -387,7 +343,7 @@ const getList = async () => {
params.deadlineStart = '';
params.deadlineEnd = '';
}
try {
const res = await listStglMaterialTask(params);
taskList.value = res.rows;
@@ -412,7 +368,7 @@ const resetQuery = () => {
/** 多选框选中数据 */
const handleSelectionChange = (selection: StglMaterialTaskVO[]) => {
ids.value = selection.map(item => item.id);
ids.value = selection.map((item) => item.id);
single.value = selection.length !== 1;
multiple.value = !selection.length;
};
@@ -420,7 +376,7 @@ const handleSelectionChange = (selection: StglMaterialTaskVO[]) => {
/** 计算是否可以上传 */
const canUpload = computed(() => {
if (single.value) return false;
const selectedTask = taskList.value.find(item => ids.value.includes(item.id));
const selectedTask = taskList.value.find((item) => ids.value.includes(item.id));
return selectedTask?.taskStatus === 1;
});
@@ -487,13 +443,11 @@ const handleStatusChange = async (status: number, row?: StglMaterialTaskVO) => {
const taskIds = row?.id ? [row.id] : ids.value;
const statusText = status === 1 ? '开启' : '关闭';
await proxy?.$modal.confirm(`确认要${statusText}所选的材料收集任务吗?`);
loading.value = true;
try {
// 处理单个或多个任务状态更新
const promises = Array.isArray(taskIds)
? taskIds.map(id => updateTaskStatus(id, status))
: [updateTaskStatus(taskIds, status)];
const promises = Array.isArray(taskIds) ? taskIds.map((id) => updateTaskStatus(id, status)) : [updateTaskStatus(taskIds, status)];
await Promise.all(promises);
await getList();
proxy?.$modal.msgSuccess(`${statusText}成功`);
@@ -512,15 +466,15 @@ const handleView = (row: StglMaterialTaskVO) => {
const handleUpload = (row?: StglMaterialTaskVO) => {
const taskId = row?.id || ids.value[0];
let activityId = '';
if (row) {
activityId = String(row.activityId);
} else {
// 如果没有传row,从选中的任务中获取activityId
const selectedTask = taskList.value.find(item => item.id === ids.value[0]);
const selectedTask = taskList.value.find((item) => item.id === ids.value[0]);
activityId = selectedTask ? String(selectedTask.activityId) : '';
}
uploadDialog.taskId = String(taskId);
uploadDialog.activityId = activityId;
uploadDialog.visible = true;
@@ -606,4 +560,4 @@ onMounted(() => {
.dialog-footer {
text-align: right;
}
</style>
</style>
+34 -33
View File
@@ -53,7 +53,15 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="info" plain icon="Check" :disabled="multiple" @click="handleBatchApprove()" v-hasPermi="['stgl:activityRegister:approve']">批量审核</el-button>
<el-button
type="info"
plain
icon="Check"
:disabled="multiple"
@click="handleBatchApprove()"
v-hasPermi="['stgl:activityRegister:approve']"
>批量审核</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['stgl:activityRegister:export']">导出</el-button>
@@ -108,7 +116,7 @@
v-hasPermi="['stgl:activityRegister:query']"
></el-button>
</el-tooltip>
<!-- 待审核状态显示审核按钮 -->
<template v-if="Number(scope.row.approvalStatus) === 0">
<el-tooltip content="审核" placement="top">
@@ -123,7 +131,7 @@
></el-button>
</el-tooltip>
</template>
<!-- 审核通过状态显示成绩录入按钮 -->
<template v-if="Number(scope.row.approvalStatus) === 1">
<el-tooltip content="录入成绩" placement="top">
@@ -138,7 +146,6 @@
></el-button>
</el-tooltip>
</template>
</template>
</el-table-column>
</el-table>
@@ -162,11 +169,15 @@
<span v-else>-</span>
</el-descriptions-item>
<el-descriptions-item label="报名时间">{{ parseTime(viewForm.registrationTime, '{y}-{m}-{d} {h}:{i}') }}</el-descriptions-item>
<el-descriptions-item label="审核时间">{{ viewForm.approvalTime ? parseTime(viewForm.approvalTime, '{y}-{m}-{d} {h}:{i}') : '-' }}</el-descriptions-item>
<el-descriptions-item label="审核时间">{{
viewForm.approvalTime ? parseTime(viewForm.approvalTime, '{y}-{m}-{d} {h}:{i}') : '-'
}}</el-descriptions-item>
<el-descriptions-item label="申请理由" :span="2">{{ viewForm.remark || '-' }}</el-descriptions-item>
<el-descriptions-item label="审核意见" :span="2">{{ viewForm.approvalRemark || '-' }}</el-descriptions-item>
<el-descriptions-item label="获得成绩">{{ viewForm.score || '-' }}</el-descriptions-item>
<el-descriptions-item label="成绩录入时间">{{ viewForm.scoreTime ? parseTime(viewForm.scoreTime, '{y}-{m}-{d} {h}:{i}') : '-' }}</el-descriptions-item>
<el-descriptions-item label="成绩录入时间">{{
viewForm.scoreTime ? parseTime(viewForm.scoreTime, '{y}-{m}-{d} {h}:{i}') : '-'
}}</el-descriptions-item>
</el-descriptions>
<template #footer>
<div class="dialog-footer">
@@ -181,20 +192,14 @@
<!-- 显示状态选择 -->
<el-form-item label="审核结果" prop="approvalStatus" v-if="approvalTitle === '审核' || approvalTitle === '批量审核'">
<el-select v-model="approvalForm.approvalStatus" placeholder="请选择审核结果" style="width: 100%">
<el-option
:value="1"
label="审核通过"
>
<span style="display: flex; align-items: center;">
<el-option :value="1" label="审核通过">
<span style="display: flex; align-items: center">
<el-icon style="color: #67c23a; margin-right: 8px"><Check /></el-icon>
审核通过
</span>
</el-option>
<el-option
:value="2"
label="审核不通过"
>
<span style="display: flex; align-items: center;">
<el-option :value="2" label="审核不通过">
<span style="display: flex; align-items: center">
<el-icon style="color: #f56c6c; margin-right: 8px"><Close /></el-icon>
审核不通过
</span>
@@ -364,15 +369,13 @@ function handleApprove(row) {
approvalOpen.value = true;
}
/** 批量审核操作 */
function handleBatchApprove() {
if (ids.value.length === 0) {
proxy.$modal.msgError('请选择要审核的数据');
return;
}
approvalForm.id = undefined; // 标识为批量审核
approvalForm.approvalStatus = undefined;
approvalForm.approvalRemark = '';
@@ -384,25 +387,23 @@ function handleBatchApprove() {
function submitApproval() {
// 所有审核都需要验证审核状态和审核意见
const validateFields = ['approvalStatus', 'approvalRemark'];
approvalRef.value.validateField(validateFields, (valid) => {
if (valid) {
if (approvalForm.id) {
// 单个审核(包括重新审核)
approveActivityRegister(approvalForm.id, approvalForm.approvalStatus, approvalForm.approvalRemark)
.then(() => {
proxy.$modal.msgSuccess('审核成功');
approvalOpen.value = false;
getList();
});
approveActivityRegister(approvalForm.id, approvalForm.approvalStatus, approvalForm.approvalRemark).then(() => {
proxy.$modal.msgSuccess('审核成功');
approvalOpen.value = false;
getList();
});
} else {
// 批量审核
batchApproveActivityRegister(ids.value, approvalForm.approvalStatus, approvalForm.approvalRemark)
.then(() => {
proxy.$modal.msgSuccess('批量审核成功');
approvalOpen.value = false;
getList();
});
batchApproveActivityRegister(ids.value, approvalForm.approvalStatus, approvalForm.approvalRemark).then(() => {
proxy.$modal.msgSuccess('批量审核成功');
approvalOpen.value = false;
getList();
});
}
}
});
@@ -454,4 +455,4 @@ onMounted(() => {
<style lang="scss" scoped>
/* 样式可以参考志愿者成员管理的样式 */
</style>
</style>
+14 -13
View File
@@ -124,7 +124,7 @@
</div>
<div class="detail-item">
<i class="el-icon-s-data"></i>
<span>成员:{{ club.memberCount || 0 }}{{ club.memberLimit ? '/' + club.memberLimit : '' }}人</span>
<span>成员:{{ club.currentMemberCount || 0 }}{{ club.memberLimit ? '/' + club.memberLimit : '' }}人</span>
</div>
<div class="detail-item">
<i class="el-icon-time"></i>
@@ -310,7 +310,7 @@
<span class="date-value">{{ selectedClub.establishDate }}</span>
</div>
<div class="member-info">
<span class="member-count">成员:{{ selectedClub.memberCount || 0 }}</span>
<span class="member-count">成员:{{ selectedClub.currentMemberCount || 0 }}</span>
<span v-if="selectedClub.memberLimit" class="member-limit">/ {{ selectedClub.memberLimit }}人</span>
</div>
</div>
@@ -365,7 +365,7 @@
<span class="info-item"><strong>姓名:</strong>{{ vp.name }}</span>
<span v-if="vp.dept" class="info-item"><strong>院系:</strong>{{ vp.dept }}</span>
<span v-if="vp.major" class="info-item"><strong>专业:</strong>{{ vp.major }}</span>
<span v-if="vp.class" class="info-item"><strong>班级:</strong>{{ vp.class }}</span>
<span v-if="vp.className" class="info-item"><strong>班级:</strong>{{ vp.className }}</span>
<span v-if="vp.phone" class="info-item"><strong>联系方式:</strong>{{ vp.phone }}</span>
<span v-if="vp.studentNo" class="info-item"><strong>学号:</strong>{{ vp.studentNo }}</span>
</div>
@@ -497,6 +497,7 @@ const queryParams = reactive<StglClubRegisterQuery>({
clubName: undefined,
clubType: undefined,
canRegister: undefined,
sighOpen: 'Y', // 只显示报名开启的社团
params: {}
});
@@ -572,14 +573,13 @@ const handleTabChange = (tabName: string) => {
const handleViewDetail = (club: StglClubRegisterVO) => {
// 🔥 调试信息:检查数据结构
console.log('handleViewDetail: 社团数据详情:', club);
console.log('handleViewDetail: advisorsJson字段:', club.advisorsJson);
selectedClub.value = club;
// 解析副社长JSON数据
if (club.vicePresidentsJson) {
// 解析副社长JSON数据(如果存在)
if ((club as any).vicePresidentsJson) {
try {
const vicePresidents = JSON.parse(club.vicePresidentsJson);
const vicePresidents = JSON.parse((club as any).vicePresidentsJson);
(club as any).vicePresidents = vicePresidents;
console.log('handleViewDetail: 解析的副社长数据:', vicePresidents);
} catch (error) {
@@ -588,7 +588,7 @@ const handleViewDetail = (club: StglClubRegisterVO) => {
}
}
// 解析指导老师JSON数据(如果有的话)
// 解析指导老师JSON数据(如果存在)
if ((club as any).advisorsJson) {
try {
const advisors = JSON.parse((club as any).advisorsJson);
@@ -598,8 +598,6 @@ const handleViewDetail = (club: StglClubRegisterVO) => {
console.warn('解析指导老师JSON数据失败:', error);
(club as any).advisors = [];
}
} else {
console.warn('handleViewDetail: 未找到advisorsJson字段');
}
detailDialog.visible = true;
@@ -667,7 +665,10 @@ const downloadCharter = async () => {
charterDownloading.value = true;
try {
// 先获取OSS文件信息
const ossResponse = await proxy?.$api.get(`/resource/oss/${charterUrl}`);
const ossResponse = await proxy?.$http.request({
url: `/resource/oss/${charterUrl}`,
method: 'get'
});
if (ossResponse && ossResponse.code === 200) {
const ossInfo = ossResponse.data;
// 使用原始文件名,如果没有则使用社团名称 + 原始文件扩展名
@@ -745,9 +746,9 @@ const getAdvisorNames = (row: any): string[] => {
console.log('getAdvisorNames: 处理行数据:', row);
// 优先从JSON字段读取指导老师数据
if (row.advisorsJson) {
if ((row as any).advisorsJson) {
try {
const advisors = JSON.parse(row.advisorsJson);
const advisors = JSON.parse((row as any).advisorsJson);
console.log('getAdvisorNames: 从JSON字段解析的指导老师数据:', advisors);
if (Array.isArray(advisors)) {
const names = advisors.map((advisor: any) => advisor.name || advisor.teacher?.nickName || '').filter((name) => name);
+189 -243
View File
@@ -96,13 +96,26 @@
</div>
</template>
</el-table-column>
<el-table-column label="成员数量" align="center" width="100">
<template #default="scope">
<span class="member-count">{{ scope.row.memberCount || 0 }}</span>
<span v-if="scope.row.memberLimit" class="member-limit">/ {{ scope.row.memberLimit }}</span>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="80">
<el-table-column label="报名开启" align="center" prop="sighOpen" width="90">
<template #default="scope">
<el-switch
v-model="scope.row.sighOpen"
active-value="Y"
inactive-value="N"
@change="handleSighOpenStatusChange(scope.row)"
v-hasPermi="['stgl:club:edit']"
></el-switch>
</template>
</el-table-column>
<el-table-column label="社团状态" align="center" prop="status" width="90">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
@@ -112,7 +125,7 @@
<div class="description-cell">{{ scope.row.description }}</div>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="240" fixed="right">
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="280" fixed="right">
<template #default="scope">
<el-tooltip content="查看" placement="top">
<el-button
@@ -136,6 +149,28 @@
v-hasPermi="['stgl:club: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="handleMemberManagement(scope.row)"
v-hasPermi="['stgl:clubmember:list']"
></el-button>
</el-tooltip>
<el-tooltip content="活动管理" placement="top">
<el-button
link
type="success"
icon="Calendar"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleActivityManagement(scope.row)"
v-hasPermi="['stgl:activity:list']"
></el-button>
</el-tooltip>
<el-tooltip content="转让" placement="top">
<el-button
link
@@ -175,12 +210,12 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="社团名称" prop="clubName">
<el-input v-model="form.clubName" placeholder="请输入社团名称" />
<el-input v-model="form.clubName" placeholder="请输入社团名称" :disabled="!hasInstructorPermission" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="社团类型" prop="clubType">
<el-select v-model="form.clubType" placeholder="请选择社团类型" style="width: 100%">
<el-select v-model="form.clubType" placeholder="请选择社团类型" style="width: 100%" :disabled="!hasInstructorPermission">
<el-option v-for="dict in stgl_club_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
@@ -195,12 +230,13 @@
placeholder="请选择成立日期"
value-format="YYYY-MM-DD"
style="width: 100%"
:disabled="!hasInstructorPermission"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="成员规模上限" prop="memberLimit">
<el-input-number v-model="form.memberLimit" :min="1" :max="9999" placeholder="请输入成员规模上限" style="width: 100%" />
<el-form-item label="成员规模上限" prop="memberLimit" required>
<el-input-number v-model="form.memberLimit" :min="1" :max="9999" placeholder="请输入成员规模上限" style="width: 100%" :disabled="!hasInstructorPermission" />
</el-form-item>
</el-col>
</el-row>
@@ -269,16 +305,12 @@
</el-row>
</div>
<!-- 副社长信息 -->
<div class="personnel-section">
<h4 class="section-title">副社长信息</h4>
<VicePresidentManager :model-value="form.vicePresidents" @change="handleVicePresidentsChange" />
</div>
<!-- 指导老师信息 -->
<div class="personnel-section">
<h4 class="section-title">指导老师信息</h4>
<AdvisorManager v-model="form.advisors" @change="handleAdvisorsChange" />
<AdvisorManager v-model="form.advisors" @change="handleAdvisorsChange" :disabled="!hasInstructorPermission" />
</div>
</el-tab-pane>
@@ -320,11 +352,22 @@
</el-tab-pane>
<el-tab-pane label="其他信息" name="other">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="开启报名" prop="sighOpen">
<el-radio-group v-model="form.sighOpen">
<el-radio v-for="dict in sys_yes_no" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" resize="none" :rows="4" />
</el-form-item>
@@ -394,31 +437,7 @@
</div>
</div>
<!-- 副社长信息 -->
<div class="position-group">
<div class="position-title">副社长:</div>
<div class="position-content">
<template v-if="viewForm.vicePresidents && viewForm.vicePresidents.length > 0">
<div v-for="(vp, index) in viewForm.vicePresidents" :key="index" class="vice-president-item">
<span class="info-item"><strong>姓名:</strong>{{ vp.name }}</span>
<span class="info-item"><strong>院系:</strong>{{ vp.dept || '暂无' }}</span>
<span class="info-item"><strong>专业:</strong>{{ vp.major || '暂无' }}</span>
<span class="info-item"><strong>班级:</strong>{{ vp.class || '暂无' }}</span>
<span class="info-item"><strong>联系方式:</strong>{{ vp.phone || '暂无' }}</span>
</div>
</template>
<template v-else-if="viewForm.vicePresidentName">
<span class="info-item"><strong>姓名:</strong>{{ viewForm.vicePresidentName }}</span>
<span class="info-item"><strong>院系:</strong>{{ viewForm.vicePresidentDept || '暂无' }}</span>
<span class="info-item"><strong>专业:</strong>{{ viewForm.vicePresidentMajor || '暂无' }}</span>
<span class="info-item"><strong>班级:</strong>{{ viewForm.vicePresidentClass || '暂无' }}</span>
<span class="info-item"><strong>联系方式:</strong>{{ viewForm.vicePresidentPhone || '暂无' }}</span>
</template>
<template v-else>
<span class="no-data">暂无副社长信息</span>
</template>
</div>
</div>
<!-- 指导老师信息 -->
<div class="position-group">
@@ -585,25 +604,51 @@
</div>
</template>
</el-dialog>
<!-- 成员管理弹窗 -->
<MemberManagementDialog
v-model:visible="memberDialog.visible"
:title="memberDialog.title"
:club-id="currentClub?.id"
:club-name="currentClub?.clubName"
:member-limit="currentClub?.memberLimit"
:current-member-count="currentClub?.memberCount"
@member-count-changed="handleMemberCountChanged"
/>
</div>
</template>
<script setup name="StglClub" lang="ts">
import { listStglClub, getStglClub, delStglClub, addStglClub, updateStglClub, transferClub, checkPresidentRole } from '@/api/stgl/club';
import { useRouter } from 'vue-router';
import {
listStglClub,
getStglClub,
delStglClub,
addStglClub,
updateStglClub,
transferClub,
checkPresidentRole,
changeSighOpenStatus
} from '@/api/stgl/club';
import { StglClubVO, StglClubQuery, StglClubForm } from '@/api/stgl/club/types';
import StudentSelector from '@/components/StudentSelector/index.vue';
import VicePresidentManager from '@/components/VicePresidentManager/index.vue';
import AdvisorManager from '@/components/AdvisorManager/index.vue';
import MemberManagementDialog from './components/MemberManagementDialog.vue';
import { StudentVO } from '@/api/xg/student/types';
import { VicePresidentVO } from '@/components/VicePresidentManager/index.vue';
import { AdvisorVO } from '@/components/AdvisorManager/index.vue';
import { Document } from '@element-plus/icons-vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { stgl_club_type, sys_normal_disable, stgl_funding_source } = toRefs<any>(
proxy?.useDict('stgl_club_type', 'sys_normal_disable', 'stgl_funding_source')
const router = useRouter();
const { stgl_club_type, sys_normal_disable, stgl_funding_source, sys_yes_no } = toRefs<any>(
proxy?.useDict('stgl_club_type', 'sys_normal_disable', 'stgl_funding_source', 'sys_yes_no')
);
// 检查是否拥有指导老师或管理员权限
const hasInstructorPermission = computed(() => {
return proxy?.$auth.hasRoleOr(['stgl_instructor', 'stgl_admin', 'superadmin']);
});
const clubList = ref<StglClubVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
@@ -640,6 +685,15 @@ const transferDialog = reactive<DialogOption>({
const transferLoading = ref(false);
const transferFormRef = ref<ElFormInstance>();
// 成员管理相关
const memberDialog = reactive<DialogOption>({
visible: false,
title: ''
});
const currentClub = ref<StglClubVO | null>(null);
const memberActiveTab = ref('joined');
interface TransferForm {
clubId?: string | number;
oldPresidentName?: string;
@@ -710,6 +764,7 @@ const viewForm = reactive<StglClubForm>({
fundingSources: [],
charterUrl: undefined,
status: '0',
sighOpen: 'Y',
remark: undefined
});
@@ -729,9 +784,7 @@ const initFormData: StglClubForm = {
presidentMajor: undefined,
presidentClass: undefined,
presidentPhone: undefined,
vicePresidents: [],
vicePresidentName: undefined,
vicePresidentStudentNo: undefined,
advisors: [],
advisorName: undefined,
advisorDept: undefined,
@@ -743,6 +796,7 @@ const initFormData: StglClubForm = {
fundingSources: [],
charterUrl: undefined,
status: '0',
sighOpen: 'N',
remark: undefined
};
@@ -763,6 +817,7 @@ const data = reactive<PageData<StglClubForm, StglClubQuery>>({
presidentName: [{ required: true, message: '社长姓名不能为空', trigger: 'blur' }],
presidentStudentNo: [{ required: true, message: '社长学号不能为空', trigger: 'blur' }],
advisorName: [{ required: true, message: '指导老师姓名不能为空', trigger: 'blur' }],
memberLimit: [{ required: true, message: '成员规模上限不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
}
});
@@ -791,6 +846,9 @@ const getList = async () => {
if (item.status !== undefined && item.status !== null) {
item.status = String(item.status);
}
if (item.sighOpen !== undefined && item.sighOpen !== null) {
item.sighOpen = String(item.sighOpen);
}
});
}
@@ -870,6 +928,9 @@ const handleUpdate = async (row?: StglClubVO) => {
if (res.data.status !== undefined && res.data.status !== null) {
(res.data as any).status = String(res.data.status);
}
if (res.data.sighOpen !== undefined && res.data.sighOpen !== null) {
(res.data as any).sighOpen = String(res.data.sighOpen);
}
// 处理社长信息:将简单字段转换为复杂对象结构
if (res.data.presidentName) {
@@ -896,39 +957,7 @@ const handleUpdate = async (row?: StglClubVO) => {
vicePresidentPhone: res.data.vicePresidentPhone
});
// 🔥 优先从JSON字段读取副社长数据
if (res.data.vicePresidentsJson) {
try {
(res.data as any).vicePresidents = JSON.parse(res.data.vicePresidentsJson);
console.log('handleUpdate: 从JSON字段读取的副社长数据:', (res.data as any).vicePresidents);
} catch (error) {
console.warn('解析副社长JSON数据失败:', error);
(res.data as any).vicePresidents = [];
}
} else if (res.data.vicePresidentName) {
// 回退到单一字段
(res.data as any).vicePresidents = [
{
name: res.data.vicePresidentName,
dept: res.data.vicePresidentDept,
major: res.data.vicePresidentMajor,
class: res.data.vicePresidentClass,
phone: res.data.vicePresidentPhone,
student: {
studentNo: res.data.vicePresidentStudentNo,
name: res.data.vicePresidentName,
collegeName: res.data.vicePresidentDept,
majorName: res.data.vicePresidentMajor,
className: res.data.vicePresidentClass,
phone: res.data.vicePresidentPhone
}
}
];
} else {
(res.data as any).vicePresidents = [];
}
console.log('handleUpdate: 处理后的副社长数据:', (res.data as any).vicePresidents);
// 处理指导老师信息:优先从JSON字段读取,如果没有则使用单一字段
// 🔥 优先从JSON字段读取指导老师数据
@@ -994,45 +1023,15 @@ const submitForm = () => {
});
}
// 确保副社长数据完整
if (submitData.vicePresidents && Array.isArray(submitData.vicePresidents)) {
submitData.vicePresidents = submitData.vicePresidents.map((vp: any) => {
return {
...vp,
// 确保name等字段有值
name: vp.name || vp.student?.name || '',
dept: vp.dept || vp.student?.collegeName || '',
major: vp.major || vp.student?.majorName || '',
class: vp.class || vp.student?.className || '',
phone: vp.phone || vp.student?.phone || ''
};
});
// 同步第一个副社长信息到旧字段(为了兼容后端)
if (submitData.vicePresidents.length > 0) {
const firstVP = submitData.vicePresidents[0];
submitData.vicePresidentName = firstVP.name;
submitData.vicePresidentStudentNo = firstVP.student?.studentNo;
submitData.vicePresidentDept = firstVP.dept;
submitData.vicePresidentMajor = firstVP.major;
submitData.vicePresidentClass = firstVP.class;
submitData.vicePresidentPhone = firstVP.phone;
}
}
// 🔥 新增:同时保存JSON字段和兼容性字段
submitData.vicePresidentsJson = JSON.stringify(submitData.vicePresidents || []);
submitData.advisorsJson = JSON.stringify(submitData.advisors || []);
// 调试日志:检查指导老师数据
console.log('提交数据 - advisors:', submitData.advisors);
console.log('提交数据 - advisorName:', submitData.advisorName);
console.log('提交数据 - advisorDept:', submitData.advisorDept);
// 调试日志:检查副社长数据
console.log('提交数据 - vicePresidents:', submitData.vicePresidents);
console.log('提交数据 - vicePresidentName:', submitData.vicePresidentName);
console.log('提交数据 - vicePresidentStudentNo:', submitData.vicePresidentStudentNo);
console.log('提交数据 - vicePresidentDept:', submitData.vicePresidentDept);
console.log('完整提交数据:', submitData);
if (form.value.id) {
@@ -1089,44 +1088,12 @@ const handleView = async (row: StglClubVO) => {
if (viewData.status !== undefined && viewData.status !== null) {
(viewData as any).status = String(viewData.status);
}
// 🔥 处理副社长信息:优先从JSON字段读取,如果没有则使用单一字段
console.log('handleView: 副社长原始数据:', {
vicePresidentsJson: viewData.vicePresidentsJson,
vicePresidentName: viewData.vicePresidentName
});
if (viewData.vicePresidentsJson) {
try {
(viewData as any).vicePresidents = JSON.parse(viewData.vicePresidentsJson);
console.log('handleView: 从JSON字段读取的副社长数据:', (viewData as any).vicePresidents);
} catch (error) {
console.warn('解析副社长JSON数据失败:', error);
(viewData as any).vicePresidents = [];
}
} else if (viewData.vicePresidentName) {
// 回退到单一字段
(viewData as any).vicePresidents = [
{
name: viewData.vicePresidentName,
dept: viewData.vicePresidentDept,
major: viewData.vicePresidentMajor,
class: viewData.vicePresidentClass,
phone: viewData.vicePresidentPhone,
student: {
studentNo: viewData.vicePresidentStudentNo,
name: viewData.vicePresidentName,
collegeName: viewData.vicePresidentDept,
majorName: viewData.vicePresidentMajor,
className: viewData.vicePresidentClass,
phone: viewData.vicePresidentPhone
}
}
];
} else {
(viewData as any).vicePresidents = [];
if (viewData.sighOpen !== undefined && viewData.sighOpen !== null) {
(viewData as any).sighOpen = String(viewData.sighOpen);
}
// 🔥 处理指导老师信息:优先从JSON字段读取,如果没有则使用单一字段
console.log('handleView: 指导老师原始数据:', {
advisorsJson: viewData.advisorsJson,
@@ -1162,7 +1129,6 @@ const handleView = async (row: StglClubVO) => {
}
console.log('handleView: 处理后的数据:', {
vicePresidents: (viewData as any).vicePresidents,
advisors: (viewData as any).advisors
});
@@ -1185,75 +1151,31 @@ const downloadCharter = async () => {
charterDownloading.value = true;
try {
// 先获取OSS文件信息
const ossResponse = await proxy?.$api.get(`/resource/oss/${viewForm.charterUrl}`);
if (ossResponse && ossResponse.code === 200) {
const ossInfo = ossResponse.data;
// 使用原始文件名,如果没有则使用社团名称 + 原始文件扩展名
let fileName = ossInfo.originalName;
if (!fileName) {
// 如果没有原始文件名,使用文件后缀和社团名称构造
const fileSuffix = ossInfo.fileSuffix || '';
fileName = `${viewForm.clubName || '社团'}_章程文档${fileSuffix}`;
}
// 使用secureOss方法下载,传递正确的文件名
proxy?.$download.secureOss(viewForm.charterUrl, fileName);
} else {
// 如果获取文件信息失败,尝试从URL中提取文件扩展名
let fileExtension = '';
try {
// 从URL中提取文件扩展名
const urlParts = viewForm.charterUrl.split('.');
if (urlParts.length > 1) {
const lastPart = urlParts[urlParts.length - 1];
// 检查是否为常见的文件扩展名(去除可能的查询参数)
const ext = lastPart.split('?')[0].toLowerCase();
if (['doc', 'docx', 'pdf', 'txt', 'xls', 'xlsx', 'ppt', 'pptx'].includes(ext)) {
fileExtension = '.' + ext;
}
// 尝试从URL中提取文件扩展名
let fileExtension = '';
try {
const urlParts = viewForm.charterUrl.split('.');
if (urlParts.length > 1) {
const lastPart = urlParts[urlParts.length - 1];
const ext = lastPart.split('?')[0].toLowerCase();
if (['doc', 'docx', 'pdf', 'txt', 'xls', 'xlsx', 'ppt', 'pptx'].includes(ext)) {
fileExtension = '.' + ext;
}
} catch (e) {
console.log('无法从URL提取扩展名:', e);
}
// 如果没有提取到扩展名,默认使用.docx(因为大部分章程文档是Word格式)
if (!fileExtension) {
fileExtension = '.docx';
}
const fileName = `${viewForm.clubName || '社团'}_章程文档${fileExtension}`;
proxy?.$download.secureOss(viewForm.charterUrl, fileName);
} catch (e) {
console.log('无法从URL提取扩展名:', e);
}
// 如果没有提取到扩展名,默认使用.docx
if (!fileExtension) {
fileExtension = '.docx';
}
const fileName = `${viewForm.clubName || '社团'}_章程文档${fileExtension}`;
proxy?.$download.secureOss(viewForm.charterUrl, fileName);
} catch (error) {
console.error('下载章程文档失败:', error);
// 如果获取文件信息失败,尝试使用默认文件名下载
try {
// 尝试从URL中提取文件扩展名
let fileExtension = '';
try {
const urlParts = viewForm.charterUrl.split('.');
if (urlParts.length > 1) {
const lastPart = urlParts[urlParts.length - 1];
const ext = lastPart.split('?')[0].toLowerCase();
if (['doc', 'docx', 'pdf', 'txt', 'xls', 'xlsx', 'ppt', 'pptx'].includes(ext)) {
fileExtension = '.' + ext;
}
}
} catch (e) {
console.log('无法从URL提取扩展名:', e);
}
// 如果没有提取到扩展名,默认使用.docx
if (!fileExtension) {
fileExtension = '.docx';
}
const fileName = `${viewForm.clubName || '社团'}_章程文档${fileExtension}`;
proxy?.$download.secureOss(viewForm.charterUrl, fileName);
} catch (downloadError) {
proxy?.$modal.msgError('下载章程文档失败,请稍后重试');
}
proxy?.$modal.msgError('下载章程文档失败,请稍后重试');
} finally {
charterDownloading.value = false;
}
@@ -1324,36 +1246,7 @@ const handlePresidentChange = (student: StudentVO | null) => {
});
};
/** 副社长列表变化处理 */
const handleVicePresidentsChange = (vicePresidents: VicePresidentVO[]) => {
// 添加防护锁,避免递归更新
if (syncLock.value) return;
syncLock.value = true;
nextTick(() => {
// 同步副社长数据到表单
form.value.vicePresidents = vicePresidents;
// 为了兼容旧版本,同步第一个副社长的信息到旧字段
if (vicePresidents.length > 0) {
const firstVP = vicePresidents[0];
form.value.vicePresidentName = firstVP.name;
form.value.vicePresidentStudentNo = firstVP.student?.studentNo;
form.value.vicePresidentDept = firstVP.student?.collegeName;
form.value.vicePresidentMajor = firstVP.student?.majorName;
form.value.vicePresidentClass = firstVP.student?.className;
form.value.vicePresidentPhone = firstVP.student?.phone;
} else {
form.value.vicePresidentName = undefined;
form.value.vicePresidentStudentNo = undefined;
form.value.vicePresidentDept = undefined;
form.value.vicePresidentMajor = undefined;
form.value.vicePresidentClass = undefined;
form.value.vicePresidentPhone = undefined;
}
syncLock.value = false;
});
};
// 防止递归更新的锁
const syncLock = ref(false);
@@ -1534,6 +1427,58 @@ const handleNewPresidentSelected = (student: StudentVO | null) => {
}
};
/** 报名开启状态修改 */
const handleSighOpenStatusChange = async (row: StglClubVO) => {
const text = row.sighOpen === 'Y' ? '开启' : '关闭';
await proxy?.$modal
.confirm('确认要"' + text + '""' + row.clubName + '"社团报名吗?')
.then(async () => {
await changeSighOpenStatus(row.id!, row.sighOpen!).then(() => {
proxy?.$modal.msgSuccess(text + '成功');
});
})
.catch(() => {
row.sighOpen = row.sighOpen === 'Y' ? 'N' : 'Y';
});
};
/** 活动管理按钮操作 */
const handleActivityManagement = (row: StglClubVO) => {
router.push({
path: '/stgl/activity/info',
query: {
relatedClubId: row.id,
relatedClubName: row.clubName,
from: 'club'
}
});
};
/** 成员管理按钮操作 */
const handleMemberManagement = (row: StglClubVO) => {
console.log('handleMemberManagement: 打开成员管理弹窗, row:', row);
currentClub.value = row;
memberDialog.visible = true;
memberDialog.title = `${row.clubName} - 成员管理`;
memberActiveTab.value = 'joined';
};
/** 处理成员数量变化 */
const handleMemberCountChanged = (newCount: number) => {
console.log('handleMemberCountChanged: 成员数量变化:', newCount, '当前社团:', currentClub.value);
if (currentClub.value) {
// 更新当前社团的成员数量
currentClub.value.memberCount = newCount;
// 同时更新列表中的对应社团数据
const clubIndex = clubList.value.findIndex(club => club.id === currentClub.value?.id);
if (clubIndex !== -1) {
clubList.value[clubIndex].memberCount = newCount;
console.log('handleMemberCountChanged: 已更新列表中的成员数量:', clubList.value[clubIndex]);
}
}
};
onMounted(() => {
getList();
});
@@ -1590,6 +1535,8 @@ onMounted(() => {
text-align: center;
}
.member-count {
font-weight: bold;
color: #409eff;
@@ -1662,7 +1609,6 @@ onMounted(() => {
}
}
.vice-president-item,
.advisor-item {
display: flex;
flex-wrap: wrap;
+495 -3
View File
@@ -121,7 +121,7 @@
<span v-else class="text-gray-400">暂无评分</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180" fixed="right">
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="240" fixed="right">
<template #default="scope">
<el-tooltip content="查看详情" placement="top">
<el-button
@@ -133,6 +133,16 @@
@click="handleView(scope.row)"
></el-button>
</el-tooltip>
<el-tooltip content="签到/签退" placement="top">
<el-button
link
type="success"
icon="Check"
size="default"
style="font-size: 16px; margin-right: 8px; color: green"
@click="handleAttendance(scope.row)"
></el-button>
</el-tooltip>
<el-tooltip content="活动评分" placement="top">
<el-button
link
@@ -267,14 +277,95 @@
</div>
</template>
</el-dialog>
<!-- 签到对话框 -->
<el-dialog title="活动签到" v-model="attendanceOpen" width="560px" append-to-body>
<div class="attendance-content">
<div class="attendance-actions">
<el-button
type="primary"
size="large"
:loading="signInLoading"
:disabled="!canSignIn"
@click="handleSignIn"
class="action-btn"
>
{{ signInButtonText }}
</el-button>
<el-button
type="warning"
size="large"
:loading="signOutLoading"
:disabled="!canSignOut"
@click="handleSignOut"
class="action-btn"
>
签 退
</el-button>
</div>
<!-- 签到记录 -->
<div class="attendance-records">
<div class="records-header">
<span class="records-title">签到记录</span>
<el-button
type="text"
size="small"
@click="refreshRecords"
:loading="recordsLoading"
>
刷新
</el-button>
</div>
<el-table
:data="attendanceRecords"
stripe
size="small"
class="records-table"
v-loading="recordsLoading"
>
<el-table-column prop="signInTime" label="签到时间" width="200">
<template #default="{ row }">
{{ formatDateTime(row.signInTime) }}
</template>
</el-table-column>
<el-table-column prop="signOutTime" label="签退时间" width="200">
<template #default="{ row }">
{{ row.signOutTime ? formatDateTime(row.signOutTime) : '-' }}
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag
:type="row.signOutTime ? 'success' : 'warning'"
size="small"
>
{{ row.signOutTime ? '已完成' : '进行中' }}
</el-tag>
</template>
</el-table-column>
</el-table>
<div v-if="attendanceRecords.length === 0" class="no-records">
<el-empty description="暂无签到记录" :image-size="60" />
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup name="MyActivity" lang="ts">
import { ref, reactive, toRefs, getCurrentInstance, ComponentInternalInstance, computed, nextTick } from 'vue';
import { getMyActivities, rateActivity } from '@/api/stgl/my';
import type { MyActivityVO, ActivityRatingForm, MyActivityQuery } from '@/api/stgl/my/types';
import { Calendar } from '@element-plus/icons-vue';
import { Calendar, Location, Clock, Check, Close } from '@element-plus/icons-vue';
import { getInfo } from '@/api/login'; // 添加获取用户详细信息的API
import request from '@/utils/request';
import type { FormInstance } from 'element-plus';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { stgl_activity_type, stgl_activity_status, stgl_registration_status, stgl_my_registration_status } = toRefs<any>(
@@ -294,7 +385,7 @@ const detailOpen = ref(false);
const detailForm = ref<MyActivityVO>({} as MyActivityVO);
const ratingOpen = ref(false);
const ratingLoading = ref(false);
const ratingFormRef = ref<ElFormInstance>();
const ratingFormRef = ref<FormInstance>();
const ratingForm = ref<ActivityRatingForm & { activityName?: string; studentName?: string; studentNo?: string }>({
activityId: '',
@@ -429,6 +520,359 @@ const formatDate = (dateStr: string): string => {
});
};
// 签到相关API调用
const createSignInRecord = async (signInData: any) => {
try {
const response = await request({
url: '/stgl/activityAttendance/signIn',
method: 'post',
data: signInData
});
return response;
} catch (error) {
console.error('签到失败:', error);
throw error;
}
};
const updateSignOutRecord = async (signOutData: any) => {
try {
const response = await request({
url: '/stgl/activityAttendance/signOut',
method: 'post',
data: signOutData
});
return response;
} catch (error) {
console.error('签退失败:', error);
throw error;
}
};
const getAllAttendanceRecords = async (activityId: number, studentNo: string) => {
try {
const response = await request({
url: `/stgl/activityAttendance/getAllByActivityAndUser?activityId=${activityId}&studentNo=${studentNo}`,
method: 'get'
});
return response;
} catch (error) {
console.error('获取所有签到记录失败:', error);
throw error;
}
};
// 签到相关变量
const attendanceOpen = ref(false);
const attendanceForm = ref<any>({
activityId: '',
activityName: '',
activityType: '',
location: '',
startTime: '',
endTime: '',
studentName: '',
studentNo: '',
department: '',
major: '',
className: '',
phone: '',
email: '',
signInStatus: 0,
signOutStatus: 0,
signInTime: '',
signOutTime: ''
});
const signInLoading = ref(false);
const signOutLoading = ref(false);
const attendanceFormRef = ref<FormInstance>();
// 签到表单验证规则
const attendanceRules = reactive({
// 可以添加验证规则
});
// 新增签到相关变量
const attendanceHistory = ref<any[]>([]);
const currentAttendance = ref<any>(null);
// 签到记录相关数据
const attendanceRecords = ref<any[]>([])
const recordsLoading = ref(false)
// 计算属性 - 简化状态判断
const canSignIn = computed(() => {
// 如果没有记录,或者最新记录已签到已签退,则可以签到
if (!currentAttendance.value) {
return true; // 没有记录时可以签到
}
// 如果最新记录已签到已签退,则可以重新签到
if (currentAttendance.value.signInStatus === 1 && currentAttendance.value.signOutStatus === 1) {
return true;
}
// 如果最新记录已签到未签退,则不能签到
if (currentAttendance.value.signInStatus === 1 && currentAttendance.value.signOutStatus === 0) {
return false;
}
// 其他情况允许签到
return true;
});
const canSignOut = computed(() => {
// 如果有记录且已签到未签退,则可以签退
return currentAttendance.value &&
currentAttendance.value.signInStatus === 1 &&
currentAttendance.value.signOutStatus === 0;
});
const signInButtonText = computed(() => {
if (!currentAttendance.value) {
return '签到';
}
if (currentAttendance.value.signInStatus === 1 && currentAttendance.value.signOutStatus === 0) {
return '已签到';
}
return '签到';
});
/** 打开签到对话框 */
const handleAttendance = async (row: MyActivityVO) => {
try {
const userInfo = await getCurrentUserDetailInfo();
// 填充活动信息
attendanceForm.value = {
activityId: row.id,
activityName: row.activityName,
activityType: row.activityType,
location: row.location,
startTime: row.startTime,
endTime: row.endTime,
studentName: userInfo?.nickName || userInfo?.userName || '',
studentNo: userInfo?.userName || '',
department: userInfo?.deptName || '',
major: '',
className: '',
phone: userInfo?.phonenumber || '',
email: userInfo?.email || '',
signInStatus: 0,
signOutStatus: 0,
signInTime: '',
signOutTime: ''
};
// 加载签到历史记录
await refreshAttendanceData();
attendanceOpen.value = true;
} catch (error) {
console.error('获取用户信息失败:', error);
proxy?.$modal.msgError('获取用户信息失败,请稍后重试');
}
};
/** 签到操作 */
const handleSignIn = async () => {
if (!attendanceForm.value.activityId) {
proxy?.$modal.msgError('请先选择活动');
return;
}
// 检查是否可以签到
if (!canSignIn.value) {
proxy?.$modal.msgError('当前状态不允许签到');
return;
}
signInLoading.value = true;
try {
const signInData = {
activityId: attendanceForm.value.activityId,
studentName: attendanceForm.value.studentName,
studentNo: attendanceForm.value.studentNo,
department: attendanceForm.value.department,
major: attendanceForm.value.major,
className: attendanceForm.value.className,
phone: attendanceForm.value.phone,
email: attendanceForm.value.email,
signInLocation: '前端签到',
signInDevice: navigator.userAgent.substring(0, 50)
};
await createSignInRecord(signInData);
// 签到成功消息
proxy?.$modal.msgSuccess('签到成功');
// 刷新签到状态和历史记录
await refreshAttendanceData();
// 强制更新计算属性
nextTick(() => {
console.log('签到后状态更新:', {
currentAttendance: currentAttendance.value,
canSignIn: canSignIn.value,
canSignOut: canSignOut.value,
signInButtonText: signInButtonText.value
});
});
} catch (error) {
console.error('签到失败:', error);
proxy?.$modal.msgError('签到失败');
} finally {
signInLoading.value = false;
}
};
/** 签退操作 */
const handleSignOut = async () => {
if (!attendanceForm.value.activityId) {
proxy?.$modal.msgError('请先选择活动');
return;
}
if (!canSignOut.value) {
proxy?.$modal.msgError('当前状态不允许签退');
return;
}
signOutLoading.value = true;
try {
const signOutData = {
id: currentAttendance.value.id, // 使用当前记录的ID进行更新
activityId: attendanceForm.value.activityId,
studentNo: attendanceForm.value.studentNo,
signOutLocation: '前端签退',
signOutDevice: navigator.userAgent.substring(0, 50)
};
await updateSignOutRecord(signOutData);
proxy?.$modal.msgSuccess('签退成功');
// 刷新签到状态和历史记录
await refreshAttendanceData();
// 强制更新计算属性
nextTick(() => {
console.log('签退后状态更新:', {
currentAttendance: currentAttendance.value,
canSignIn: canSignIn.value,
canSignOut: canSignOut.value,
signInButtonText: signInButtonText.value
});
});
} catch (error) {
console.error('签退失败:', error);
proxy?.$modal.msgError('签退失败');
} finally {
signOutLoading.value = false;
}
};
/** 刷新签到数据 */
const refreshAttendanceData = async () => {
try {
// 获取最新的签到记录
const recordRes = await getAllAttendanceRecords(attendanceForm.value.activityId, attendanceForm.value.studentNo);
if (recordRes.data && recordRes.data.length > 0) {
// 按创建时间排序,取最新的一条作为当前记录
const sortedRecords = recordRes.data.sort((a: any, b: any) =>
new Date(b.createTime).getTime() - new Date(a.createTime).getTime()
);
currentAttendance.value = sortedRecords[0];
attendanceHistory.value = sortedRecords;
// 同时更新签到记录表格数据
attendanceRecords.value = sortedRecords;
console.log('获取到签到记录:', {
total: sortedRecords.length,
current: currentAttendance.value,
history: attendanceHistory.value
});
} else {
currentAttendance.value = null;
attendanceHistory.value = [];
attendanceRecords.value = [];
console.log('没有找到签到记录');
}
// 强制触发计算属性重新计算
await nextTick();
console.log('刷新签到数据完成:', {
history: attendanceHistory.value,
current: currentAttendance.value,
canSignIn: canSignIn.value,
canSignOut: canSignOut.value,
signInButtonText: signInButtonText.value
});
} catch (error) {
console.error('刷新签到数据失败:', error);
// 如果获取失败,重置状态
currentAttendance.value = null;
attendanceHistory.value = [];
attendanceRecords.value = [];
}
};
/** 刷新签到记录 */
const refreshRecords = async () => {
if (!attendanceForm.value.activityId || !attendanceForm.value.studentNo) return;
recordsLoading.value = true;
try {
await refreshAttendanceData();
proxy?.$modal.msgSuccess('签到记录已刷新');
} catch (error) {
console.error('刷新签到记录失败:', error);
proxy?.$modal.msgError('刷新签到记录失败');
} finally {
recordsLoading.value = false;
}
};
/** 格式化日期时间 */
const formatDateTime = (dateStr: string): string => {
if (!dateStr) return '';
const date = new Date(dateStr);
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
};
/** 计算参与时长 */
const calculateDuration = (signInTime: string, signOutTime: string): string => {
if (!signInTime) return '-';
if (!signOutTime) return '进行中';
const start = new Date(signInTime);
const end = new Date(signOutTime);
const diffMs = end.getTime() - start.getTime();
const hours = Math.floor(diffMs / (1000 * 60 * 60));
const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
if (hours > 0) {
return `${hours}小时${minutes}分钟`;
} else {
return `${minutes}分钟`;
}
};
onMounted(() => {
getList();
});
@@ -521,4 +965,52 @@ onMounted(() => {
white-space: pre-wrap;
word-break: break-word;
}
/* 签到弹窗样式 */
.attendance-content {
padding: 20px 0;
}
.attendance-actions {
display: flex;
justify-content: center;
gap: 16px;
}
.action-btn {
min-width: 120px;
height: 44px;
font-size: 16px;
font-weight: 500;
}
/* 签到记录样式 */
.attendance-records {
margin-top: 24px;
border-top: 1px solid #e4e7ed;
padding-top: 20px;
}
.records-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.records-title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.records-table {
margin-bottom: 16px;
}
.no-records {
text-align: center;
padding: 40px 0;
color: #909399;
}
</style>
+4 -14
View File
@@ -12,23 +12,13 @@
</el-col>
<el-col :span="6">
<el-form-item label="社团类型" prop="clubType">
<el-select
v-model="queryParams.clubType"
placeholder="请选择社团类型"
clearable
style="width: 100%"
>
<el-option
v-for="dict in stgl_club_type"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
<el-select v-model="queryParams.clubType" placeholder="请选择社团类型" clearable style="width: 100%">
<el-option v-for="dict in stgl_club_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
@@ -339,4 +329,4 @@ onMounted(() => {
background-color: #fff;
}
}
</style>
</style>
+15 -49
View File
@@ -7,12 +7,7 @@
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="报名类型" prop="registrationType">
<el-select
v-model="queryParams.registrationType"
placeholder="请选择报名类型"
clearable
style="width: 100%"
>
<el-select v-model="queryParams.registrationType" placeholder="请选择报名类型" clearable style="width: 100%">
<el-option label="全部" value="" />
<el-option label="社团" value="1" />
<el-option label="志愿团队" value="2" />
@@ -22,12 +17,7 @@
</el-col>
<el-col :span="6">
<el-form-item label="审核状态" prop="approvalStatus">
<el-select
v-model="queryParams.approvalStatus"
placeholder="请选择审核状态"
clearable
style="width: 100%"
>
<el-select v-model="queryParams.approvalStatus" placeholder="请选择审核状态" clearable style="width: 100%">
<el-option label="全部" value="" />
<el-option label="待审核" value="待审核" />
<el-option label="已通过" value="已通过" />
@@ -36,7 +26,7 @@
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
@@ -65,10 +55,7 @@
<el-table-column label="序号" type="index" align="center" width="60" />
<el-table-column label="报名类型" align="center" prop="registrationType" width="100">
<template #default="scope">
<el-tag
:type="getRegistrationTypeTag(scope.row.registrationType)"
size="small"
>
<el-tag :type="getRegistrationTypeTag(scope.row.registrationType)" size="small">
{{ getRegistrationTypeText(scope.row.registrationType) }}
</el-tag>
</template>
@@ -84,20 +71,14 @@
</el-table-column>
<el-table-column label="审核状态" align="center" prop="approvalStatus" width="100">
<template #default="scope">
<el-tag
:type="getApprovalStatusTag(scope.row.approvalStatus)"
size="small"
>
<el-tag :type="getApprovalStatusTag(scope.row.approvalStatus)" size="small">
{{ scope.row.approvalStatus }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="当前状态" align="center" prop="currentStatus" width="100">
<template #default="scope">
<el-tag
:type="getCurrentStatusTag(scope.row.currentStatus)"
size="small"
>
<el-tag :type="getCurrentStatusTag(scope.row.currentStatus)" size="small">
{{ scope.row.currentStatus }}
</el-tag>
</template>
@@ -132,23 +113,14 @@
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 查看详情对话框 -->
<el-dialog title="报名记录详情" v-model="detailOpen" width="800px" append-to-body>
<el-descriptions :column="2" border>
<el-descriptions-item label="报名类型">
<el-tag
:type="getRegistrationTypeTag(detailForm.registrationType)"
size="small"
>
<el-tag :type="getRegistrationTypeTag(detailForm.registrationType)" size="small">
{{ getRegistrationTypeText(detailForm.registrationType) }}
</el-tag>
</el-descriptions-item>
@@ -158,18 +130,12 @@
<el-descriptions-item label="组织者">{{ detailForm.organizer || '暂无' }}</el-descriptions-item>
<el-descriptions-item label="报名时间">{{ parseTime(detailForm.registrationTime, '{y}-{m}-{d} {h}:{i}') }}</el-descriptions-item>
<el-descriptions-item label="审核状态">
<el-tag
:type="getApprovalStatusTag(detailForm.approvalStatus)"
size="small"
>
<el-tag :type="getApprovalStatusTag(detailForm.approvalStatus)" size="small">
{{ detailForm.approvalStatus }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="当前状态">
<el-tag
:type="getCurrentStatusTag(detailForm.currentStatus)"
size="small"
>
<el-tag :type="getCurrentStatusTag(detailForm.currentStatus)" size="small">
{{ detailForm.currentStatus }}
</el-tag>
</el-descriptions-item>
@@ -254,7 +220,7 @@ const handleView = (row: MyRegistrationVO) => {
const getRegistrationTypeText = (type: string): string => {
const typeMap: Record<string, string> = {
'1': '社团',
'2': '志愿团队',
'2': '志愿团队',
'3': '活动'
};
return typeMap[type] || '未知';
@@ -267,7 +233,7 @@ const getRegistrationTypeTag = (type: string) => {
'2': 'success' as const,
'3': 'warning' as const
};
return tagMap[type as keyof typeof tagMap] || 'info' as const;
return tagMap[type as keyof typeof tagMap] || ('info' as const);
};
/** 获取审核状态标签样式 */
@@ -277,7 +243,7 @@ const getApprovalStatusTag = (status: string) => {
'已拒绝': 'danger' as const,
'待审核': 'warning' as const
};
return tagMap[status as keyof typeof tagMap] || 'info' as const;
return tagMap[status as keyof typeof tagMap] || ('info' as const);
};
/** 获取当前状态标签样式 */
@@ -287,7 +253,7 @@ const getCurrentStatusTag = (status: string) => {
'已报名': 'primary' as const,
'待审核': 'warning' as const
};
return tagMap[status as keyof typeof tagMap] || 'info' as const;
return tagMap[status as keyof typeof tagMap] || ('info' as const);
};
onMounted(() => {
@@ -359,4 +325,4 @@ onMounted(() => {
font-size: 14px;
line-height: 1.6;
}
</style>
</style>
+2 -2
View File
@@ -11,7 +11,7 @@
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24" class="search-btn-container">
<el-form-item>
@@ -291,4 +291,4 @@ onMounted(() => {
background-color: #fff;
}
}
</style>
</style>
@@ -125,7 +125,7 @@
v-hasPermi="['stgl:volunteerMember:query']"
></el-button>
</el-tooltip>
<!-- 待审核状态显示审核按钮 -->
<template v-if="scope.row.status === 0">
<el-tooltip content="通过" placement="top">
@@ -149,7 +149,7 @@
></el-button>
</el-tooltip>
</template>
<!-- 非待审核状态显示常规操作按钮 -->
<template v-else>
<el-tooltip content="修改" placement="top">
@@ -679,7 +679,7 @@ function handleDelete(row) {
function handleApprove(row, approved) {
const action = approved ? '通过' : '驳回';
const memberName = row.studentName || '该成员';
proxy.$modal
.confirm(`是否确认${action}${memberName}的申请?`)
.then(function () {
@@ -18,6 +18,7 @@
<el-option label="已满员" value="full" />
<el-option label="已申请" value="applied" />
<el-option label="已加入" value="joined" />
<el-option label="报名关闭" value="closed" />
</el-select>
</el-form-item>
</el-col>
@@ -114,7 +115,10 @@
<!-- 3. 已满员 - 红色 -->
<el-tag v-else-if="calculateRegisterStatus(scope.row) === 'full'" type="danger" size="small">已满员</el-tag>
<!-- 4. 可报名 - 绿色(默认状态) -->
<!-- 4. 报名关闭 - 灰色 -->
<el-tag v-else-if="calculateRegisterStatus(scope.row) === 'closed'" type="info" size="small">报名关闭</el-tag>
<!-- 5. 可报名 - 绿色(默认状态) -->
<el-tag v-else type="success" size="small">可报名</el-tag>
</template>
</el-table-column>
@@ -141,6 +145,11 @@
<el-button link type="danger" icon="Close" disabled />
</el-tooltip>
<!-- 报名关闭状态 - 灰色禁用按钮 -->
<el-tooltip content="报名已关闭" placement="top" v-else-if="calculateRegisterStatus(scope.row) === 'closed'">
<el-button link type="info" icon="Lock" disabled />
</el-tooltip>
<!-- 查看详情按钮 - 始终显示 -->
<el-tooltip content="查看详情" placement="top">
<el-button link type="primary" icon="View" @click="handleViewDetail(scope.row)" />
@@ -171,6 +180,7 @@
<el-tag v-if="calculateRegisterStatus(detailForm) === 'joined'" type="primary">已加入队伍</el-tag>
<el-tag v-else-if="calculateRegisterStatus(detailForm) === 'applied'" type="warning">申请审核中</el-tag>
<el-tag v-else-if="calculateRegisterStatus(detailForm) === 'full'" type="danger">队伍已满员</el-tag>
<el-tag v-else-if="calculateRegisterStatus(detailForm) === 'closed'" type="info">报名已关闭</el-tag>
<el-tag v-else type="success">可申请报名</el-tag>
</el-descriptions-item>
<el-descriptions-item label="剩余名额">
@@ -362,6 +372,7 @@ interface TeamInfo {
advisorEmail?: string;
email?: string;
status?: number | string; // 队伍状态(sys_normal_disable字典:0-正常 1-停用)
sighOpen?: string; // 报名开启状态(Y-开启 N-关闭)
// 用户申请状态字段(后端已支持)
hasApplied?: boolean; // 是否已申请
@@ -448,19 +459,24 @@ function calculateRegisterStatus(row: TeamInfo): string {
if (row.hasJoined) {
return 'joined'; // 已加入
}
if (row.hasApplied) {
return 'applied'; // 已申请
}
// 优先级2:队伍成员数量检查
const currentCount = row.currentMemberCount || 0;
const limit = row.memberLimit || 0;
if (limit > 0 && currentCount >= limit) {
return 'full'; // 已满员
}
// 优先级3:报名开关检查
if (row.sighOpen === 'N') {
return 'closed'; // 报名已关闭
}
return 'available'; // 可报名
}
@@ -559,7 +575,7 @@ async function loadUserInfoToForm() {
// 获取当前用户的学生信息
const response = await getPersonalInfo();
const studentInfo: StudentMsgForm = response.data;
if (studentInfo) {
// 自动填充用户基本信息
applyForm.value.studentName = studentInfo.name || '';
@@ -584,16 +600,18 @@ function submitApply() {
(proxy!.$refs['applyRef'] as any).validate((valid: boolean) => {
if (valid) {
applyForm.value.teamId = selectedTeam.value.id;
applyJoinVolunteerTeam(applyForm.value).then((response) => {
proxy!.$modal.msgSuccess('申请提交成功,请等待审核');
applyOpen.value = false;
getList(); // 刷新列表
}).catch((error) => {
console.error('申请提交失败:', error);
// 显示具体的错误信息
const errorMsg = error?.response?.data?.msg || error?.message || '申请提交失败,请稍后重试';
proxy!.$modal.msgError(errorMsg);
});
applyJoinVolunteerTeam(applyForm.value)
.then((response) => {
proxy!.$modal.msgSuccess('申请提交成功,请等待审核');
applyOpen.value = false;
getList(); // 刷新列表
})
.catch((error) => {
console.error('申请提交失败:', error);
// 显示具体的错误信息
const errorMsg = error?.response?.data?.msg || error?.message || '申请提交失败,请稍后重试';
proxy!.$modal.msgError(errorMsg);
});
}
});
}
+131 -8
View File
@@ -43,7 +43,7 @@
type="warning"
icon="Switch"
:disabled="single || isTransferDisabled(teamList.find((t) => ids.includes(t.id)))"
@click="handleTransfer()"
@click="handleTransfer"
v-hasPermi="['stgl:volunteerTeam:transfer']"
>转让</el-button
>
@@ -78,6 +78,17 @@
<span v-if="scope.row.memberLimit" class="member-limit">/ {{ scope.row.memberLimit }}</span>
</template>
</el-table-column>
<el-table-column label="报名开启" align="center" prop="sighOpen" width="90">
<template #default="scope">
<el-switch
v-model="scope.row.sighOpen"
active-value="Y"
inactive-value="N"
@change="handleSighOpenStatusChange(scope.row)"
v-hasPermi="['stgl:volunteerTeam:edit']"
></el-switch>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="80">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
@@ -91,7 +102,7 @@
</div>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="240" fixed="right">
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="380" fixed="right">
<template #default="scope">
<el-tooltip content="查看" placement="top">
<el-button
@@ -115,6 +126,28 @@
v-hasPermi="['stgl:volunteerTeam: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="handleMemberManagement(scope.row)"
v-hasPermi="['stgl:volunteermember:list']"
></el-button>
</el-tooltip>
<el-tooltip content="活动管理" placement="top">
<el-button
link
type="success"
icon="Calendar"
size="default"
style="font-size: 16px; margin-right: 8px"
@click="handleActivityManagement(scope.row)"
v-hasPermi="['stgl:activity:list']"
></el-button>
</el-tooltip>
<el-tooltip content="转让" placement="top">
<el-button
link
@@ -197,7 +230,7 @@
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">
@@ -206,6 +239,15 @@
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="开启报名" prop="sighOpen">
<el-radio-group v-model="form.sighOpen">
<el-radio v-for="dict in sys_yes_no" :key="dict.value" :label="dict.value">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-tab-pane>
@@ -296,6 +338,9 @@
<el-descriptions-item label="状态">
<dict-tag :options="sys_normal_disable" :value="detailForm.status" />
</el-descriptions-item>
<el-descriptions-item label="报名开启">
<dict-tag :options="sys_yes_no" :value="detailForm.sighOpen" />
</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ detailForm.phone }}</el-descriptions-item>
<el-descriptions-item label="邮箱">{{ detailForm.email }}</el-descriptions-item>
<el-descriptions-item label="队伍简介" :span="2">{{ detailForm.description }}</el-descriptions-item>
@@ -413,6 +458,16 @@
</div>
</template>
</el-dialog>
<!-- 成员管理弹窗 -->
<MemberManagementDialog
v-model:visible="memberDialog.visible"
:title="memberDialog.title"
:team-id="currentTeam?.id"
:team-name="currentTeam?.teamName"
:member-limit="currentTeam?.memberLimit"
:current-member-count="currentTeam?.currentMemberCount"
/>
</div>
</template>
@@ -429,14 +484,18 @@ import {
searchTeacherByName,
getTeacherById,
checkTeamLeader,
transferVolunteerTeam
transferVolunteerTeam,
changeSighOpenStatus
} from '@/api/stgl/volunteerteam';
import StudentSelector from '@/components/StudentSelector/index.vue';
import TeacherSelector from '@/components/TeacherSelector/index.vue';
import { onMounted, getCurrentInstance, ref, reactive, watch, toRefs } from 'vue';
import MemberManagementDialog from './components/MemberManagementDialog.vue';
import { onMounted, getCurrentInstance, ref, reactive, watch, toRefs, nextTick } from 'vue';
import { useRouter } from 'vue-router';
const { proxy } = getCurrentInstance();
const { sys_normal_disable } = toRefs(proxy.useDict('sys_normal_disable'));
const { sys_normal_disable, sys_yes_no } = toRefs(proxy.useDict('sys_normal_disable', 'sys_yes_no'));
const router = useRouter();
const teamList = ref([]);
const open = ref(false);
@@ -480,6 +539,14 @@ const transferForm = reactive({
newLeaderPhone: ''
});
// 成员管理相关
const memberDialog = reactive({
visible: false,
title: ''
});
const currentTeam = ref(null);
const transferRules = {
newLeaderStudent: [{ required: true, message: '请选择新队长', trigger: 'change' }]
};
@@ -520,7 +587,7 @@ const checkIsTeamLeader = async (teamId) => {
const result = response.data === true;
console.log(`✅ 队长检查结果: ${result} (response.data: ${response.data}, type: ${typeof response.data})`);
return result;
} catch (error) {
} catch (error: any) {
console.error('❌ 检查队长权限失败:', error);
console.error('❌ 错误详情:', {
message: error.message,
@@ -703,6 +770,7 @@ const data = reactive({
advisorEmail: null,
advisorUserId: null,
status: '0',
sighOpen: 'Y',
remark: null
} as any,
detailForm: {} as any,
@@ -747,6 +815,9 @@ async function getList() {
if (item.status !== undefined && item.status !== null) {
item.status = String(item.status);
}
if (item.sighOpen !== undefined && item.sighOpen !== null) {
item.sighOpen = String(item.sighOpen);
}
});
}
@@ -793,6 +864,7 @@ function reset() {
advisorEmail: null,
advisorUserId: null,
status: '0',
sighOpen: 'Y',
remark: null
};
@@ -861,7 +933,9 @@ function handleUpdate(row) {
// 指导老师信息字段映射
advisorDepartment: data.advisorDept, // advisorDept -> advisorDepartment
// 状态字段类型转换:确保与字典组件匹配(Integer -> String)
status: data.status !== undefined && data.status !== null ? String(data.status) : '0'
status: data.status !== undefined && data.status !== null ? String(data.status) : '0',
// 报名开关字段类型转换:确保与字典组件匹配
sighOpen: data.sighOpen !== undefined && data.sighOpen !== null ? String(data.sighOpen) : 'Y'
};
// 设置队长选择器的值 - 先尝试用延迟设置的方式
@@ -1109,6 +1183,48 @@ const getAdvisorNames = (row: any): string[] => {
return [];
};
/** 成员管理按钮操作 */
const handleMemberManagement = (row) => {
console.log('handleMemberManagement: 打开成员管理弹窗, row:', row);
currentTeam.value = row;
memberDialog.visible = true;
memberDialog.title = `${row.teamName} - 成员管理`;
};
/** 活动管理按钮操作 */
const handleActivityManagement = (row) => {
console.log('handleActivityManagement: 跳转到活动管理页面, row:', row);
console.log('handleActivityManagement: 传递参数:', {
relatedTeamId: row.id,
relatedTeamName: row.teamName,
from: 'team'
});
router.push({
path: '/stgl/activity/info',
query: {
relatedTeamId: row.id,
relatedTeamName: row.teamName,
from: 'team'
}
});
};
/** 报名开启状态修改 */
const handleSighOpenStatusChange = async (row) => {
const text = row.sighOpen === 'Y' ? '开启' : '关闭';
await proxy?.$modal
.confirm('确认要"' + text + '""' + row.teamName + '"志愿者团队报名吗?')
.then(async () => {
await changeSighOpenStatus(row.id, row.sighOpen).then(() => {
proxy?.$modal.msgSuccess(text + '成功');
});
})
.catch(() => {
row.sighOpen = row.sighOpen === 'Y' ? 'N' : 'Y';
});
};
onMounted(() => {
getList();
});
@@ -1172,4 +1288,11 @@ onMounted(() => {
min-width: 200px !important;
}
}
/* 操作列自适应样式 - 只确保不换行 */
:deep(.el-table) {
.el-table__body-wrapper .el-table__body td:has(.el-button) {
white-space: nowrap !important;
}
}
</style>
+23 -2
View File
@@ -180,7 +180,7 @@ const { evaluation_status, sys_user_sex } = toRefs<any>(proxy?.useDict('evaluati
const route = useRoute();
const router = useRouter();
const evaluationId = ref(route.params.id as string);
const evaluationId = ref(route.query.id as string);
const loading = ref(false);
const statisticsLoading = ref(false);
@@ -219,7 +219,7 @@ let pieChart: echarts.ECharts | null = null;
const queryParams = ref<XljkEvaluationDetailQuery>({
pageNum: 1,
pageSize: 10,
evaluationId: Number(evaluationId.value),
evaluationId: Number(evaluationId.value) || 0,
mentalStatus: '',
studentName: '',
studentNo: '',
@@ -260,7 +260,15 @@ const getStatistics = async () => {
const getDetailList = async () => {
try {
loading.value = true;
// 确保有有效的evaluationId
if (!evaluationId.value) {
console.error('evaluationId为空,无法查询详情');
return;
}
console.log('查询参数:', {
evaluationId: evaluationId.value,
filterStatus: filterStatus.value,
searchKeyword: searchKeyword.value,
studentNo: queryParams.value.studentNo,
@@ -441,6 +449,19 @@ const resetFilter = () => {
};
onMounted(() => {
console.log('详情页面路由参数:', {
params: route.params,
query: route.query,
evaluationId: evaluationId.value
});
// 验证ID是否存在
if (!evaluationId.value) {
proxy?.$modal.msgError('缺少测评ID参数');
router.push('/xljk/evaluation');
return;
}
getEvaluationInfo();
getStatistics();
getDetailList();
+29 -6
View File
@@ -134,13 +134,15 @@ import {
import { XljkEvaluationForm, StudentInfo, XljkEvaluationDetailForm } from '@/api/xljk/evaluation/types';
import { useRoute, useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import { useTagsViewStore } from '@/store/modules/tagsView';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_user_sex } = toRefs<any>(proxy?.useDict('sys_user_sex'));
const route = useRoute();
const router = useRouter();
const evaluationId = ref(route.params.id as string);
const tagsViewStore = useTagsViewStore();
const evaluationId = ref(route.query.id as string);
const formRef = ref<ElFormInstance>();
const submitLoading = ref(false);
const studentsLoading = ref(false);
@@ -166,6 +168,13 @@ const rules = {
evaluationName: [{ required: true, message: '测评名称不能为空', trigger: 'blur' }]
};
// 心理状态字典映射
const mentalStatusMap: Record<string, string> = {
'1': '健康',
'2': '良好',
'3': '重点关注'
};
// 学生列表
const studentList = ref<StudentInfo[]>([]);
// 测评明细列表
@@ -273,7 +282,7 @@ const applyBatchMentalStatus = async () => {
} as XljkEvaluationDetailForm;
});
await batchUpdateXljkMentalStatus(detailsToUpdate);
ElMessage.success(`已批量设置 ${selectedStudents.value.length} 名学生的心理状态为 ${status}`);
ElMessage.success(`已批量设置 ${selectedStudents.value.length} 名学生的心理状态为 ${mentalStatusMap[status]}`);
// 刷新学生列表以更新状态
await loadStudentList();
await getEvaluationDetails();
@@ -297,7 +306,7 @@ const applyBatchMentalStatusToAll = async () => {
} as XljkEvaluationDetailForm;
});
await batchUpdateXljkMentalStatus(detailsToUpdate);
ElMessage.success(`已批量设置 ${studentList.value.length} 名学生的心理状态为 ${status}`);
ElMessage.success(`已批量设置 ${studentList.value.length} 名学生的心理状态为 ${mentalStatusMap[status]}`);
// 刷新学生列表以更新状态
await loadStudentList();
await getEvaluationDetails();
@@ -366,7 +375,11 @@ const submitForm = () => {
await batchUpdateXljkMentalStatus(detailsToUpdate);
ElMessage.success('修改成功');
router.replace('/xljk/evaluation');
// 关闭当前标签页并跳转到列表页面
setTimeout(async () => {
await tagsViewStore.delView(route);
router.push('/xljk/evaluation');
}, 1000);
} catch (error) {
console.error('修改测评失败', error);
} finally {
@@ -377,11 +390,21 @@ const submitForm = () => {
};
/** 取消按钮 */
const cancel = () => {
router.replace('/xljk/evaluation');
const cancel = async () => {
await tagsViewStore.delView(route);
router.push('/xljk/evaluation');
};
onMounted(() => {
console.log('Edit页面路由参数:', route.query);
console.log('获取到的测评ID:', evaluationId.value);
if (!evaluationId.value) {
proxy?.$modal.msgError('缺少测评ID参数');
router.push('/xljk/evaluation');
return;
}
getEvaluationInfo();
});
</script>
+20 -3
View File
@@ -343,18 +343,35 @@ const handleSelectionChange = (selection: XljkEvaluationVO[]) => {
/** 新增按钮操作 */
const handleAdd = async () => {
proxy?.$router.push({ path: '/xljk/evaluation/add' });
proxy.$router.push({
path: '/xljk/evaluation/add',
query: {
type: 'add'
}
});
};
/** 查看按钮操作 */
const handleView = (row: XljkEvaluationVO) => {
proxy?.$router.push({ path: `/xljk/evaluation/detail/${row.id}` });
proxy.$router.push({
path: `/xljk/evaluation/detail`,
query: {
id: row.id,
type: 'detail'
}
});
};
/** 修改按钮操作 */
const handleUpdate = (row?: XljkEvaluationVO) => {
const id = row?.id || ids.value[0];
proxy?.$router.push({ path: `/xljk/evaluation/edit/${id}` });
proxy.$router.push({
path: `/xljk/evaluation/edit`,
query: {
id: row.id,
type: 'edit'
}
});
};
/** 删除按钮操作 */