library-picturebook-activity/java-frontend/src/views/contests/components/AddParticipantDrawer.vue
2026-04-02 14:22:56 +08:00

271 lines
6.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>
<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.nickname || "-" }}
</template>
<template v-else-if="column.key === 'gender'">
<a-tag v-if="record.gender === 'male'" color="blue">男</a-tag>
<a-tag v-else-if="record.gender === 'female'" color="pink">女</a-tag>
<span v-else>-</span>
</template>
<template v-else-if="column.key === 'account'">
{{ record.username || "-" }}
</template>
<template v-else-if="column.key === 'contact'">
{{ record.phone || "-" }}
</template>
<template v-else-if="column.key === 'organization'">
{{ record.roleNames?.join("、") || "-" }}
</template>
</template>
</a-table>
</div>
<template #footer>
<a-space>
<a-button @click="handleCancel">取消</a-button>
<a-button type="primary" @click="handleSubmit">
确定
</a-button>
</a-space>
</template>
</a-drawer>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from "vue"
import { message } from "ant-design-vue"
import { SearchOutlined } from "@ant-design/icons-vue"
import type { TableColumnsType } from "ant-design-vue"
import {
registrationsApi,
type RegistrationCandidateUser,
} from "@/api/contests"
import { useListRequest } from "@/composables/useListRequest"
interface Props {
open: boolean
registeredUserIds?: number[]
}
interface Emits {
(e: "update:open", value: boolean): void
(e: "confirm", students: RegistrationCandidateUser[]): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const visible = ref(false)
const selectedKeys = ref<number[]>([])
const searchParams = reactive<{
username?: string
nickname?: string
}>({})
const {
loading,
dataSource,
pagination,
handleTableChange,
search,
fetchList,
} = useListRequest<RegistrationCandidateUser, { username?: string; nickname?: string }>({
requestFn: async (params) => {
const kw = [params.username, params.nickname].filter(Boolean).join(" ").trim()
return registrationsApi.getCandidateUsers({
roleCode: "student",
keyword: kw || undefined,
page: params.page,
pageSize: params.pageSize,
})
},
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 },
]
watch(
() => props.open,
(newVal) => {
visible.value = newVal
if (newVal) {
selectedKeys.value = []
searchParams.username = ""
searchParams.nickname = ""
fetchList()
}
},
{ immediate: true }
)
watch(visible, (newVal) => {
emit("update:open", newVal)
})
const handleSearch = () => {
search({
username: searchParams.username || undefined,
nickname: searchParams.nickname || undefined,
})
}
const handleSelectionChange = (
selectedRowKeys: number[],
_selectedRows: RegistrationCandidateUser[]
) => {
selectedKeys.value = selectedRowKeys
}
const getCheckboxProps = (record: RegistrationCandidateUser) => {
const isRegistered = props.registeredUserIds?.includes(record.id) ?? false
return { disabled: isRegistered }
}
const handleSubmit = () => {
if (selectedKeys.value.length === 0) {
message.warning("请至少选择一个参赛人")
return
}
const selectedStudents = dataSource.value.filter((row) =>
selectedKeys.value.includes(row.id)
)
emit("confirm", selectedStudents)
visible.value = false
selectedKeys.value = []
}
const handleCancel = () => {
visible.value = false
selectedKeys.value = []
searchParams.username = ""
searchParams.nickname = ""
}
</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>