223 lines
6.5 KiB
Vue
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>
|