271 lines
6.4 KiB
Vue
271 lines
6.4 KiB
Vue
<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>
|