Files
hzdyjs/web/src/components/ClubSelector/index.vue
T
2025-08-25 15:53:02 +08:00

223 lines
6.5 KiB
Vue

<template>
<div class="club-selector">
<el-select
:model-value="modelValue"
@update:model-value="handleChange"
:placeholder="placeholder"
:clearable="clearable"
:disabled="disabled"
:size="size"
filterable
remote
:remote-method="remoteMethod"
:loading="loading"
value-key="uniqueKey"
style="width: 100%"
>
<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>
</div>
</el-option>
</el-select>
<!-- 详细选择对话框 -->
<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-column type="selection" width="55" align="center" />
<el-table-column label="社团名称" prop="relatedClubName" />
<el-table-column label="类型" width="80" align="center">
<template #default>
<el-tag type="primary" size="small">社团</el-tag>
</template>
</el-table-column>
</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-column type="selection" width="55" align="center" />
<el-table-column label="队伍名称" prop="relatedClubName" />
<el-table-column label="类型" width="100" align="center">
<template #default>
<el-tag type="success" size="small">志愿者队伍</el-tag>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
</el-tabs>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmSelection">确定</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { getClubOptions, getVolunteerTeamOptions } from '@/api/stgl/activity';
import { ClubOption } from '@/api/stgl/activity/types';
interface Props {
modelValue?: ClubOption | null;
placeholder?: string;
clearable?: boolean;
disabled?: boolean;
size?: 'large' | 'default' | 'small';
showDialog?: boolean; // 是否显示详细选择对话框
}
interface Emits {
(e: 'update:modelValue', value: ClubOption | null): void;
(e: 'change', value: ClubOption | null): void;
}
const props = withDefaults(defineProps<Props>(), {
placeholder: '请选择社团/志愿者队伍',
clearable: true,
disabled: false,
size: 'default',
showDialog: false
});
const emit = defineEmits<Emits>();
const loading = ref(false);
const dialogVisible = ref(false);
const activeTab = ref('club');
const options = ref<ClubOption[]>([]);
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 ? '社团' : '志愿者队伍';
return `${option.relatedClubName} (${typeText})`;
};
// 获取类型标签
const getTypeLabel = (type: number): string => {
return type === 1 ? '社团' : '志愿者队伍';
};
// 处理值变化
const handleChange = (value: ClubOption | null) => {
console.log('ClubSelector - handleChange:', value);
emit('update:modelValue', value);
emit('change', value);
};
// 远程搜索方法
const remoteMethod = async (query: string) => {
if (query) {
loading.value = true;
try {
// 根据查询条件过滤选项
const filteredOptions = [...clubOptions.value, ...volunteerOptions.value].filter((option) =>
option.relatedClubName.toLowerCase().includes(query.toLowerCase())
);
options.value = filteredOptions.map((item) => ({
...item,
uniqueKey: item.uniqueKey || `${item.relatedType}-${item.relatedClubId}`
}));
} catch (error) {
console.error('搜索失败:', error);
} finally {
loading.value = false;
}
} else {
options.value = [...clubOptions.value, ...volunteerOptions.value];
}
};
// 处理行点击
const handleRowClick = (row: ClubOption) => {
selectedOption.value = row;
};
// 确认选择
const confirmSelection = () => {
if (selectedOption.value) {
handleChange(selectedOption.value);
dialogVisible.value = false;
}
};
// 加载选项数据
const loadOptions = async () => {
loading.value = true;
try {
const [clubRes, volunteerRes] = await Promise.all([getClubOptions(), getVolunteerTeamOptions()]);
// 为每个选项添加唯一标识
clubOptions.value = (clubRes.data || []).map((item) => ({
...item,
uniqueKey: `${item.relatedType}-${item.relatedClubId}`
}));
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 {
loading.value = false;
}
};
onMounted(() => {
loadOptions();
});
</script>
<style scoped>
.club-selector {
width: 100%;
}
.option-content {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.option-name {
flex: 1;
font-weight: 500;
}
.option-type {
font-size: 12px;
color: #909399;
margin-left: 8px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>