社团管理1.0
This commit is contained in:
1 parent
4d8267e561
commit
d256028f8a
83 files changed
+2040
-1096
No files matched your search
@@ -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'
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -118,4 +118,4 @@ export const signOut = (data: StglActivityAttendanceForm) => {
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -329,4 +329,4 @@ export interface StglActivityAttendanceQuery {
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
}
|
||||
@@ -299,4 +299,4 @@ export function batchReviewSubmissions(ids: string, reviewStatus: number, review
|
||||
method: 'put',
|
||||
params: params
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -533,4 +533,4 @@ export interface MaterialTypeOption {
|
||||
* 是否选中
|
||||
*/
|
||||
checked?: boolean;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -189,6 +189,11 @@ export interface StglClubVO {
|
||||
*/
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* 是否开启报名
|
||||
*/
|
||||
sighOpen: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@@ -381,6 +386,11 @@ export interface StglClubForm {
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 是否开启报名
|
||||
*/
|
||||
sighOpen?: string;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
|
||||
@@ -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'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -211,4 +211,4 @@ export interface MyRegistrationQuery {
|
||||
registrationType?: string;
|
||||
/** 审核状态 */
|
||||
approvalStatus?: string;
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,11 @@ export interface StglClubRegisterVO {
|
||||
*/
|
||||
canRegister: boolean;
|
||||
|
||||
/**
|
||||
* 报名开启状态
|
||||
*/
|
||||
sighOpen?: string;
|
||||
|
||||
/**
|
||||
* 招新周期
|
||||
*/
|
||||
@@ -242,6 +247,11 @@ export interface StglClubRegisterQuery extends PageQuery {
|
||||
*/
|
||||
canRegister?: boolean;
|
||||
|
||||
/**
|
||||
* 报名开启状态
|
||||
*/
|
||||
sighOpen?: string;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
|
||||
Reference in new issue
Block a user