library-picturebook-activity/java-frontend/src/views/contests/components/AddParticipantDrawer.vue

342 lines
7.7 KiB
Vue
Raw Normal View History

<template>
<a-drawer
v-model:open="visible"
title="添加参赛人"
placement="right"
width="850px"
:footer-style="{ textAlign: 'right', padding: '16px 24px' }"
@close="handleCancel"
>
<template #title>
<div class="drawer-title">
<span>添加参赛人</span>
</div>
</template>
<!-- 搜索/筛选区域 -->
<div class="search-section">
<div class="search-item">
<span class="search-label">账号</span>
<a-input
v-model:value="searchParams.username"
placeholder="请输入"
allow-clear
style="width: 200px"
@press-enter="handleSearch"
>
<template #suffix>
<SearchOutlined />
</template>
</a-input>
</div>
<div class="search-item">
<span class="search-label">姓名</span>
<a-input
v-model:value="searchParams.nickname"
placeholder="请输入"
allow-clear
style="width: 200px"
@press-enter="handleSearch"
>
<template #suffix>
<SearchOutlined />
</template>
</a-input>
</div>
<div class="search-item">
<span class="search-label">机构信息</span>
<a-select
v-model:value="searchParams.classId"
placeholder="年份+班级"
allow-clear
style="width: 200px"
@change="handleSearch"
>
<a-select-option
v-for="cls in classOptions"
:key="cls.id"
:value="cls.id"
>
{{ cls.grade?.name }} - {{ cls.name }}
</a-select-option>
</a-select>
</div>
</div>
<!-- 数据表格 -->
<div class="table-section">
<a-table
:columns="columns"
:data-source="dataSource"
:loading="loading"
:pagination="pagination"
:row-selection="{
type: 'checkbox',
selectedRowKeys: selectedKeys,
onChange: handleSelectionChange,
getCheckboxProps: getCheckboxProps,
}"
row-key="id"
@change="handleTableChange"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'name'">
{{ record.user?.nickname || "-" }}
</template>
<template v-else-if="column.key === 'gender'">
<a-tag v-if="record.gender === 1" color="blue"></a-tag>
<a-tag v-else-if="record.gender === 2" color="pink"></a-tag>
<span v-else>-</span>
</template>
<template v-else-if="column.key === 'account'">
{{ record.user?.username || "-" }}
</template>
<template v-else-if="column.key === 'contact'">
{{ record.user?.phone || record.phone || "-" }}
</template>
<template v-else-if="column.key === 'organization'">
{{ record.class?.grade?.name }} - {{ record.class?.name }}
</template>
</template>
</a-table>
</div>
<template #footer>
<a-space>
<a-button @click="handleCancel">取消</a-button>
<a-button type="primary" :loading="submitLoading" @click="handleSubmit">
确定
</a-button>
</a-space>
</template>
</a-drawer>
</template>
<script setup lang="ts">
import { ref, reactive, watch, onMounted } from "vue"
import { message } from "ant-design-vue"
import { SearchOutlined } from "@ant-design/icons-vue"
import type { TableColumnsType } from "ant-design-vue"
import { studentsApi, type Student } from "@/api/students"
import { classesApi, type Class } from "@/api/classes"
import { useListRequest } from "@/composables/useListRequest"
interface Props {
open: boolean
registeredUserIds?: number[] // 已报名的用户ID列表
}
interface Emits {
(e: "update:open", value: boolean): void
(e: "confirm", students: Student[]): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const visible = ref(false)
const submitLoading = ref(false)
const selectedKeys = ref<number[]>([])
const classOptions = ref<Class[]>([])
// 搜索参数
const searchParams = reactive<{
username?: string
nickname?: string
classId?: number
}>({})
// 列表请求
const {
loading,
dataSource,
pagination,
handleTableChange,
search,
fetchList,
} = useListRequest<Student, { username?: string; nickname?: string; classId?: number }>({
requestFn: studentsApi.getList,
defaultSearchParams: {},
defaultPageSize: 10,
errorMessage: "获取学生列表失败",
immediate: false,
})
// 表格列定义
const columns: TableColumnsType = [
{
title: "姓名",
key: "name",
width: 120,
},
{
title: "性别",
key: "gender",
width: 80,
align: "center",
},
{
title: "账号",
key: "account",
width: 120,
},
{
title: "联系方式",
key: "contact",
width: 120,
},
{
title: "机构信息",
key: "organization",
width: 200,
},
]
// 监听 open 变化
watch(
() => props.open,
(newVal) => {
visible.value = newVal
if (newVal) {
// 打开时重置状态
selectedKeys.value = []
searchParams.username = ""
searchParams.nickname = ""
searchParams.classId = undefined
fetchClasses()
fetchList()
}
},
{ immediate: true }
)
// 监听 visible 变化,同步到父组件
watch(visible, (newVal) => {
emit("update:open", newVal)
})
// 搜索处理
const handleSearch = () => {
search({
username: searchParams.username || undefined,
nickname: searchParams.nickname || undefined,
classId: searchParams.classId || undefined,
})
}
// 选择变化处理
const handleSelectionChange = (
selectedRowKeys: number[],
selectedRows: Student[]
) => {
selectedKeys.value = selectedRowKeys
}
// 获取复选框属性,禁用已报名的学生
const getCheckboxProps = (record: Student) => {
const isRegistered = props.registeredUserIds?.includes(record.userId) ?? false
return {
disabled: isRegistered,
}
}
// 加载班级列表
const fetchClasses = async () => {
try {
const response = await classesApi.getList({
page: 1,
pageSize: 100,
type: 1,
})
classOptions.value = response.list
} catch (error) {
console.error("获取班级列表失败:", error)
}
}
// 提交
const handleSubmit = () => {
if (selectedKeys.value.length === 0) {
message.warning("请至少选择一个参赛人")
return
}
// 获取选中的学生
const selectedStudents = dataSource.value.filter((student) =>
selectedKeys.value.includes(student.id)
)
emit("confirm", selectedStudents)
visible.value = false
selectedKeys.value = []
}
// 取消
const handleCancel = () => {
visible.value = false
selectedKeys.value = []
searchParams.username = ""
searchParams.nickname = ""
searchParams.classId = undefined
}
onMounted(() => {
fetchClasses()
})
</script>
<style scoped lang="scss">
.drawer-title {
font-size: 16px;
font-weight: 500;
color: #303133;
}
:deep(.ant-drawer-body) {
padding: 24px;
}
.search-section {
margin-bottom: 24px;
display: flex;
flex-direction: row;
gap: 16px;
align-items: center;
flex-wrap: nowrap;
.search-item {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
.search-label {
font-size: 14px;
color: #303133;
font-weight: 500;
white-space: nowrap;
flex-shrink: 0;
}
}
}
.table-section {
margin-bottom: 24px;
:deep(.ant-table-thead > tr > th) {
background-color: #f5f7fa;
font-weight: 500;
color: #303133;
}
:deep(.ant-table-tbody > tr:hover > td) {
background-color: #f5f7fa;
}
:deep(.ant-table-tbody > tr:nth-child(even) > td) {
background-color: #fafafa;
}
}
</style>