library-picturebook-activity/frontend/src/views/contests/registrations/Records.vue

1057 lines
28 KiB
Vue
Raw Normal View History

2026-01-09 18:14:35 +08:00
<template>
<div class="registration-records-page">
<a-card class="mb-4">
<template #title>
<a-breadcrumb>
<a-breadcrumb-item>
2026-01-13 16:41:12 +08:00
<router-link :to="{ name: 'ContestsRegistrations', params: { tenantCode } }">报名管理</router-link>
2026-01-09 18:14:35 +08:00
</a-breadcrumb-item>
<a-breadcrumb-item>{{ contestName || '报名记录' }}</a-breadcrumb-item>
</a-breadcrumb>
</template>
<template #extra>
<a-space>
<a-button
v-permission="'contest:update'"
type="primary"
:disabled="selectedRowKeys.length === 0"
@click="handleBatchReview"
>
批量审核
</a-button>
<a-button @click="handleExport" :loading="exportLoading">
<template #icon><DownloadOutlined /></template>
导出
</a-button>
</a-space>
</template>
</a-card>
<!-- 个人赛搜索表单 -->
<a-form
v-if="contestType === 'individual'"
:model="searchParams"
layout="inline"
class="search-form"
@finish="handleSearch"
>
<a-form-item label="机构">
<a-input
v-model:value="searchParams.tenantName"
placeholder="请输入机构名称"
allow-clear
style="width: 150px"
/>
</a-form-item>
<a-form-item label="姓名">
<a-input
v-model:value="searchParams.nickname"
placeholder="请输入姓名"
allow-clear
style="width: 120px"
/>
</a-form-item>
<a-form-item label="报名账号">
<a-input
v-model:value="searchParams.accountNo"
placeholder="请输入账号"
allow-clear
style="width: 150px"
/>
</a-form-item>
<a-form-item label="审核状态">
<a-select
v-model:value="searchParams.registrationState"
placeholder="请选择状态"
allow-clear
style="width: 120px"
>
<a-select-option value="pending">待审核</a-select-option>
<a-select-option value="passed">已通过</a-select-option>
<a-select-option value="rejected">已拒绝</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="报名时间">
<a-range-picker
v-model:value="dateRange"
style="width: 240px"
@change="handleDateChange"
/>
</a-form-item>
<a-form-item>
<a-button type="primary" html-type="submit">
<template #icon><SearchOutlined /></template>
搜索
</a-button>
<a-button style="margin-left: 8px" @click="handleReset">
<template #icon><ReloadOutlined /></template>
重置
</a-button>
</a-form-item>
</a-form>
<!-- 团队赛搜索表单 -->
<a-form
v-else
:model="searchParams"
layout="inline"
class="search-form"
@finish="handleSearch"
>
<a-form-item label="机构">
<a-input
v-model:value="searchParams.tenantName"
placeholder="请输入机构名称"
allow-clear
style="width: 150px"
/>
</a-form-item>
<a-form-item label="队伍名称">
<a-input
v-model:value="searchParams.teamName"
placeholder="请输入队伍名称"
allow-clear
style="width: 120px"
/>
</a-form-item>
<a-form-item label="报名账号">
<a-input
v-model:value="searchParams.accountNo"
placeholder="请输入账号"
allow-clear
style="width: 150px"
/>
</a-form-item>
<a-form-item label="审核状态">
<a-select
v-model:value="searchParams.registrationState"
placeholder="请选择状态"
allow-clear
style="width: 120px"
>
<a-select-option value="pending">待审核</a-select-option>
<a-select-option value="passed">已通过</a-select-option>
<a-select-option value="rejected">已拒绝</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="报名时间">
<a-range-picker
v-model:value="dateRange"
style="width: 240px"
@change="handleDateChange"
/>
</a-form-item>
<a-form-item>
<a-button type="primary" html-type="submit">
<template #icon><SearchOutlined /></template>
搜索
</a-button>
<a-button style="margin-left: 8px" @click="handleReset">
<template #icon><ReloadOutlined /></template>
重置
</a-button>
</a-form-item>
</a-form>
<!-- 个人赛表格 -->
<a-table
v-if="contestType === 'individual'"
:columns="individualColumns"
:data-source="dataSource"
:loading="loading"
:pagination="pagination"
:row-selection="rowSelection"
row-key="id"
@change="handleTableChange"
>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'index'">
{{ (pagination.current - 1) * pagination.pageSize + index + 1 }}
</template>
<template v-else-if="column.key === 'tenant'">
2026-01-15 16:35:00 +08:00
<div>
<div>{{ record.user?.tenant?.name || record.tenant?.name || "-" }}</div>
<div v-if="record.user?.student?.class" class="org-detail">
{{ record.user.student.class.grade?.name || "" }}
{{ record.user.student.class.name || "" }}
</div>
</div>
2026-01-09 18:14:35 +08:00
</template>
<template v-else-if="column.key === 'nickname'">
{{ record.user?.nickname || record.accountName || "-" }}
</template>
<template v-else-if="column.key === 'accountNo'">
{{ record.accountNo || record.user?.username || "-" }}
</template>
<template v-else-if="column.key === 'registrationState'">
<a-tag :color="getStateColor(record.registrationState)">
{{ getStateText(record.registrationState) }}
</a-tag>
</template>
<template v-else-if="column.key === 'registrationTime'">
{{ formatDateTime(record.registrationTime) }}
</template>
<template v-else-if="column.key === 'action'">
<a-space>
<a-button
type="link"
size="small"
@click="handleViewDetail(record)"
>
详细信息
</a-button>
<a-button
v-if="record.registrationState !== 'passed'"
v-permission="'contest:update'"
type="link"
size="small"
@click="handlePass(record)"
>
通过
</a-button>
<a-button
v-if="record.registrationState !== 'rejected'"
v-permission="'contest:update'"
type="link"
size="small"
@click="handleReject(record)"
>
拒绝
</a-button>
<a-popconfirm
v-permission="'contest:delete'"
title="确定要删除该报名记录吗?"
@confirm="handleDelete(record.id)"
>
<a-button type="link" danger size="small">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
<!-- 团队赛表格 -->
<a-table
v-else
:columns="teamColumns"
:data-source="dataSource"
:loading="loading"
:pagination="pagination"
:row-selection="rowSelection"
row-key="id"
@change="handleTableChange"
>
<template #bodyCell="{ column, record, index }">
<template v-if="column.key === 'index'">
{{ (pagination.current - 1) * pagination.pageSize + index + 1 }}
</template>
<template v-else-if="column.key === 'tenant'">
2026-01-15 16:35:00 +08:00
<div>
<div>{{ record.user?.tenant?.name || record.tenant?.name || "-" }}</div>
<div v-if="record.user?.student?.class" class="org-detail">
{{ record.user.student.class.grade?.name || "" }}
{{ record.user.student.class.name || "" }}
</div>
</div>
2026-01-09 18:14:35 +08:00
</template>
<template v-else-if="column.key === 'teamName'">
{{ record.team?.teamName || "-" }}
</template>
<template v-else-if="column.key === 'accountNo'">
{{ record.accountNo || record.user?.username || "-" }}
</template>
<template v-else-if="column.key === 'registrationState'">
<a-tag :color="getStateColor(record.registrationState)">
{{ getStateText(record.registrationState) }}
</a-tag>
</template>
<template v-else-if="column.key === 'registrationTime'">
{{ formatDateTime(record.registrationTime) }}
</template>
<template v-else-if="column.key === 'action'">
<a-space>
<a-button
type="link"
size="small"
@click="handleViewMembers(record)"
>
成员信息
</a-button>
<a-button
v-if="record.registrationState !== 'passed'"
v-permission="'contest:update'"
type="link"
size="small"
@click="handlePass(record)"
>
通过
</a-button>
<a-button
v-if="record.registrationState !== 'rejected'"
v-permission="'contest:update'"
type="link"
size="small"
@click="handleReject(record)"
>
拒绝
</a-button>
<a-popconfirm
v-permission="'contest:delete'"
title="确定要删除该报名记录吗?"
@confirm="handleDelete(record.id)"
>
<a-button type="link" danger size="small">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
<!-- 详细信息抽屉 -->
<a-drawer
v-model:open="detailDrawerVisible"
title="详细信息"
width="600"
placement="right"
>
<template v-if="currentDetail">
<a-descriptions :column="2" bordered>
<a-descriptions-item label="姓名">
{{ currentDetail.user?.nickname || currentDetail.accountName || "-" }}
</a-descriptions-item>
<a-descriptions-item label="账号">
{{ currentDetail.accountNo || currentDetail.user?.username || "-" }}
</a-descriptions-item>
<a-descriptions-item label="机构">
2026-01-15 16:35:00 +08:00
<div>{{ currentDetail.user?.tenant?.name || "-" }}</div>
<div v-if="currentDetail.user?.student?.class" class="org-detail">
{{ currentDetail.user.student.class.grade?.name || "" }}
{{ currentDetail.user.student.class.name || "" }}
</div>
2026-01-09 18:14:35 +08:00
</a-descriptions-item>
<a-descriptions-item label="审核状态">
<a-tag :color="getStateColor(currentDetail.registrationState)">
{{ getStateText(currentDetail.registrationState) }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="报名时间" :span="2">
{{ formatDateTime(currentDetail.registrationTime) }}
</a-descriptions-item>
<a-descriptions-item v-if="currentDetail.reason" label="审核理由" :span="2">
{{ currentDetail.reason }}
</a-descriptions-item>
</a-descriptions>
</template>
</a-drawer>
<!-- 成员信息弹窗 -->
<a-modal
v-model:open="membersModalVisible"
:title="`团队成员 - ${currentTeam?.teamName || ''}`"
:footer="null"
width="700px"
>
<template v-if="currentTeam">
<a-descriptions :column="3" bordered style="margin-bottom: 16px">
<a-descriptions-item label="团队名称">{{ currentTeam.teamName }}</a-descriptions-item>
<a-descriptions-item label="队长">{{ currentTeam.leader?.nickname || "-" }}</a-descriptions-item>
<a-descriptions-item label="成员数">{{ currentTeam._count?.members || 0 }}</a-descriptions-item>
</a-descriptions>
<a-table
:columns="memberColumns"
:data-source="teamMembers"
:loading="membersLoading"
:pagination="false"
row-key="id"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'role'">
<a-tag :color="getMemberRoleColor(record.role)">
{{ getMemberRoleText(record.role) }}
</a-tag>
</template>
</template>
</a-table>
</template>
</a-modal>
<!-- 批量审核弹窗 -->
<a-modal
v-model:open="batchReviewModalVisible"
title="批量审核"
:confirm-loading="batchReviewLoading"
@ok="handleBatchReviewSubmit"
>
<a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
<a-form-item label="审核结果" required>
<a-radio-group v-model:value="batchReviewForm.registrationState">
<a-radio value="passed">通过</a-radio>
<a-radio value="rejected">拒绝</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="审核理由">
<a-textarea
v-model:value="batchReviewForm.reason"
placeholder="请输入审核理由"
:rows="4"
/>
</a-form-item>
<a-form-item label="已选择">
<span>{{ selectedRowKeys.length }} 条记录</span>
</a-form-item>
</a-form>
</a-modal>
<!-- 拒绝理由弹窗 -->
<a-modal
v-model:open="rejectModalVisible"
title="拒绝理由"
:confirm-loading="rejectLoading"
@ok="handleRejectSubmit"
>
<a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 18 }">
<a-form-item label="拒绝理由">
<a-textarea
v-model:value="rejectReason"
placeholder="请输入拒绝理由"
:rows="4"
/>
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, computed } from "vue"
import { useRouter, useRoute } from "vue-router"
import { message } from "ant-design-vue"
import type { TableProps } from "ant-design-vue"
import {
SearchOutlined,
ReloadOutlined,
DownloadOutlined,
} from "@ant-design/icons-vue"
import {
registrationsApi,
contestsApi,
teamsApi,
type ContestRegistration,
type QueryRegistrationParams,
type ContestTeam,
type ContestTeamMember,
} from "@/api/contests"
import dayjs from "dayjs"
import type { Dayjs } from "dayjs"
const router = useRouter()
const route = useRoute()
const tenantCode = route.params.tenantCode as string
const contestId = Number(route.params.id)
// 比赛信息
const contestName = ref("")
const contestType = ref<"individual" | "team">("individual")
// 日期范围
const dateRange = ref<[Dayjs, Dayjs] | null>(null)
// 列表状态
const loading = ref(false)
const dataSource = ref<ContestRegistration[]>([])
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
})
// 搜索参数
const searchParams = reactive<QueryRegistrationParams & { teamName?: string }>({
contestId,
tenantName: "",
nickname: "",
teamName: "",
accountNo: "",
registrationState: undefined,
startTime: undefined,
endTime: undefined,
})
// 选中的行
const selectedRowKeys = ref<number[]>([])
const rowSelection = computed<TableProps["rowSelection"]>(() => ({
selectedRowKeys: selectedRowKeys.value,
onChange: (keys: any) => {
selectedRowKeys.value = keys
},
}))
// 导出loading
const exportLoading = ref(false)
// 详情抽屉
const detailDrawerVisible = ref(false)
const currentDetail = ref<ContestRegistration | null>(null)
// 成员信息弹窗
const membersModalVisible = ref(false)
const currentTeam = ref<ContestTeam | null>(null)
const teamMembers = ref<ContestTeamMember[]>([])
const membersLoading = ref(false)
// 批量审核弹窗
const batchReviewModalVisible = ref(false)
const batchReviewLoading = ref(false)
const batchReviewForm = reactive({
registrationState: "passed" as "passed" | "rejected",
reason: "",
})
// 拒绝弹窗
const rejectModalVisible = ref(false)
const rejectLoading = ref(false)
const rejectReason = ref("")
const currentRejectId = ref<number | null>(null)
// 个人赛表格列
const individualColumns = [
{
title: "序号",
key: "index",
width: 70,
},
{
title: "机构",
key: "tenant",
width: 150,
},
{
title: "姓名",
key: "nickname",
width: 120,
},
{
title: "报名账号",
key: "accountNo",
width: 150,
},
{
title: "审核状态",
key: "registrationState",
width: 100,
},
{
title: "报名时间",
key: "registrationTime",
width: 160,
},
{
title: "操作",
key: "action",
width: 220,
fixed: "right" as const,
},
]
// 团队赛表格列
const teamColumns = [
{
title: "序号",
key: "index",
width: 70,
},
{
title: "机构",
key: "tenant",
width: 150,
},
{
title: "队伍名称",
key: "teamName",
width: 150,
},
{
title: "报名账号",
key: "accountNo",
width: 150,
},
{
title: "审核状态",
key: "registrationState",
width: 100,
},
{
title: "报名时间",
key: "registrationTime",
width: 160,
},
{
title: "操作",
key: "action",
width: 220,
fixed: "right" as const,
},
]
// 团队成员表格列
const memberColumns = [
{
title: "姓名",
dataIndex: ["user", "nickname"],
key: "nickname",
width: 120,
},
{
title: "账号",
dataIndex: ["user", "username"],
key: "username",
width: 150,
},
{
title: "角色",
dataIndex: "role",
key: "role",
width: 100,
},
{
title: "加入时间",
dataIndex: "createTime",
key: "createTime",
width: 160,
customRender: ({ record }: { record: ContestTeamMember }) => {
return record.createTime
? dayjs(record.createTime).format("YYYY-MM-DD HH:mm")
: "-"
},
},
]
// 格式化日期时间
const formatDateTime = (dateStr?: string) => {
if (!dateStr) return "-"
return dayjs(dateStr).format("YYYY-MM-DD HH:mm")
}
// 获取状态颜色
const getStateColor = (state?: string) => {
switch (state) {
case "passed":
return "success"
case "rejected":
return "error"
default:
return "processing"
}
}
// 获取状态文本
const getStateText = (state?: string) => {
switch (state) {
case "passed":
return "已通过"
case "rejected":
return "已拒绝"
default:
return "待审核"
}
}
// 获取成员角色颜色
const getMemberRoleColor = (role?: string) => {
switch (role) {
case "leader":
return "gold"
case "mentor":
return "purple"
default:
return "blue"
}
}
// 获取成员角色文本
const getMemberRoleText = (role?: string) => {
switch (role) {
case "leader":
return "队长"
case "mentor":
return "指导老师"
default:
return "成员"
}
}
// 加载比赛信息
const fetchContestInfo = async () => {
try {
const contest = await contestsApi.getDetail(contestId)
contestName.value = contest.contestName
contestType.value = contest.contestType as "individual" | "team"
} catch (error) {
console.error("获取比赛信息失败", error)
}
}
// 获取列表数据
const fetchList = async () => {
loading.value = true
try {
const params: QueryRegistrationParams = {
contestId,
page: pagination.current,
pageSize: pagination.pageSize,
}
// 添加搜索条件
if (searchParams.tenantName) params.tenantName = searchParams.tenantName
if (searchParams.accountNo) params.accountNo = searchParams.accountNo
if (searchParams.registrationState) params.registrationState = searchParams.registrationState
if (searchParams.startTime) params.startTime = searchParams.startTime
if (searchParams.endTime) params.endTime = searchParams.endTime
if (contestType.value === 'individual') {
if (searchParams.nickname) params.nickname = searchParams.nickname
} else {
if (searchParams.teamName) params.teamName = searchParams.teamName
}
const response = await registrationsApi.getList(params)
dataSource.value = response.list
pagination.total = response.total
} catch (error) {
message.error("获取报名记录失败")
} finally {
loading.value = false
}
}
// 日期变化
const handleDateChange = (dates: [Dayjs, Dayjs] | null) => {
if (dates) {
searchParams.startTime = dates[0].format("YYYY-MM-DD")
searchParams.endTime = dates[1].format("YYYY-MM-DD")
} else {
searchParams.startTime = undefined
searchParams.endTime = undefined
}
}
// 搜索
const handleSearch = () => {
pagination.current = 1
fetchList()
}
// 重置搜索
const handleReset = () => {
searchParams.tenantName = ""
searchParams.nickname = ""
searchParams.teamName = ""
searchParams.accountNo = ""
searchParams.registrationState = undefined
searchParams.startTime = undefined
searchParams.endTime = undefined
dateRange.value = null
pagination.current = 1
fetchList()
}
// 表格变化
const handleTableChange = (pag: any) => {
pagination.current = pag.current
pagination.pageSize = pag.pageSize
fetchList()
}
// 查看详细信息
const handleViewDetail = async (record: ContestRegistration) => {
try {
const detail = await registrationsApi.getDetail(record.id)
currentDetail.value = detail
detailDrawerVisible.value = true
} catch (error: any) {
message.error(error?.response?.data?.message || "获取详情失败")
}
}
// 查看成员信息
const handleViewMembers = async (record: ContestRegistration) => {
if (!record.team) {
message.warning("暂无团队信息")
return
}
currentTeam.value = record.team
membersModalVisible.value = true
membersLoading.value = true
try {
const teamDetail = await teamsApi.getDetail(record.team.id)
teamMembers.value = teamDetail.members || []
} catch (error: any) {
message.error("获取团队成员失败")
teamMembers.value = []
} finally {
membersLoading.value = false
}
}
// 通过
const handlePass = async (record: ContestRegistration) => {
try {
await registrationsApi.review(record.id, {
registrationState: "passed",
})
message.success("已通过")
fetchList()
} catch (error: any) {
message.error(error?.response?.data?.message || "操作失败")
}
}
// 拒绝
const handleReject = (record: ContestRegistration) => {
currentRejectId.value = record.id
rejectReason.value = ""
rejectModalVisible.value = true
}
// 提交拒绝
const handleRejectSubmit = async () => {
if (!currentRejectId.value) return
try {
rejectLoading.value = true
await registrationsApi.review(currentRejectId.value, {
registrationState: "rejected",
reason: rejectReason.value,
})
message.success("已拒绝")
rejectModalVisible.value = false
fetchList()
} catch (error: any) {
message.error(error?.response?.data?.message || "操作失败")
} finally {
rejectLoading.value = false
}
}
// 删除
const handleDelete = async (id: number) => {
try {
await registrationsApi.delete(id)
message.success("删除成功")
fetchList()
} catch (error: any) {
message.error(error?.response?.data?.message || "删除失败")
}
}
// 批量审核
const handleBatchReview = () => {
batchReviewForm.registrationState = "passed"
batchReviewForm.reason = ""
batchReviewModalVisible.value = true
}
// 提交批量审核
const handleBatchReviewSubmit = async () => {
if (selectedRowKeys.value.length === 0) {
message.warning("请先选择要审核的记录")
return
}
try {
batchReviewLoading.value = true
await Promise.all(
selectedRowKeys.value.map((id) =>
registrationsApi.review(id, {
registrationState: batchReviewForm.registrationState,
reason: batchReviewForm.reason,
})
)
)
message.success("批量审核成功")
batchReviewModalVisible.value = false
selectedRowKeys.value = []
fetchList()
} catch (error: any) {
message.error(error?.response?.data?.message || "批量审核失败")
} finally {
batchReviewLoading.value = false
}
}
// 导出
const handleExport = async () => {
exportLoading.value = true
try {
const response = await registrationsApi.getList({
contestId,
page: 1,
pageSize: 100,
})
if (response.list.length === 0) {
message.warning("暂无数据")
return
}
let headers: string[]
let rows: any[][]
if (contestType.value === 'individual') {
headers = ["序号", "机构", "姓名", "报名账号", "审核状态", "报名时间"]
rows = response.list.map((item, index) => [
index + 1,
item.user?.tenant?.name || "",
item.user?.nickname || item.accountName || "",
item.accountNo || item.user?.username || "",
getStateText(item.registrationState),
formatDateTime(item.registrationTime),
])
} else {
headers = ["序号", "机构", "队伍名称", "报名账号", "审核状态", "报名时间"]
rows = response.list.map((item, index) => [
index + 1,
item.user?.tenant?.name || "",
item.team?.teamName || "",
item.accountNo || item.user?.username || "",
getStateText(item.registrationState),
formatDateTime(item.registrationTime),
])
}
const csvContent = [
headers.join(","),
...rows.map((row) => row.map((cell) => `"${cell}"`).join(",")),
].join("\n")
const blob = new Blob(["\ufeff" + csvContent], {
type: "text/csv;charset=utf-8",
})
const url = URL.createObjectURL(blob)
const link = document.createElement("a")
link.href = url
link.download = `报名记录_${contestName.value}_${dayjs().format("YYYYMMDD_HHmmss")}.csv`
link.click()
URL.revokeObjectURL(url)
message.success("导出成功")
} catch (error: any) {
message.error(error?.response?.data?.message || "导出失败")
} finally {
exportLoading.value = false
}
}
onMounted(async () => {
await fetchContestInfo()
fetchList()
})
</script>
2026-01-16 16:35:43 +08:00
<style scoped lang="scss">
// 主色调
$primary: #1890ff;
$primary-dark: #0958d9;
$gradient-primary: linear-gradient(135deg, $primary 0%, $primary-dark 100%);
2026-01-09 18:14:35 +08:00
.registration-records-page {
2026-01-16 16:35:43 +08:00
// 标题卡片样式
:deep(.ant-card) {
border: none;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
2026-01-09 18:14:35 +08:00
margin-bottom: 16px;
2026-01-16 16:35:43 +08:00
.ant-card-head {
border-bottom: none;
padding: 16px 24px;
.ant-card-head-title {
font-size: 18px;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
}
}
.ant-card-body {
padding: 0;
}
}
// 渐变主按钮样式
:deep(.ant-btn-primary) {
background: $gradient-primary;
border: none;
box-shadow: 0 4px 12px rgba($primary, 0.35);
transition: all 0.3s ease;
&:hover {
background: linear-gradient(135deg, $primary-dark 0%, darken($primary-dark, 8%) 100%);
box-shadow: 0 6px 16px rgba($primary, 0.45);
transform: translateY(-1px);
}
&:active {
transform: translateY(0);
}
}
// 表格样式
:deep(.ant-table-wrapper) {
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
overflow: hidden;
.ant-table {
.ant-table-thead > tr > th {
background: #fafafa;
font-weight: 600;
color: rgba(0, 0, 0, 0.85);
border-bottom: 1px solid #f0f0f0;
}
.ant-table-tbody > tr {
transition: all 0.2s ease;
&:hover > td {
background: rgba($primary, 0.04);
}
> td {
border-bottom: 1px solid #f5f5f5;
}
}
}
.ant-table-pagination {
padding: 16px;
margin: 0;
}
}
}
.search-form {
margin-bottom: 16px;
padding: 20px 24px;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
// 自适应换行 - 使用 flex wrap
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 16px 24px;
:deep(.ant-form-item) {
margin-bottom: 0;
margin-right: 0;
2026-01-09 18:14:35 +08:00
}
}
2026-01-15 16:35:00 +08:00
.org-detail {
font-size: 12px;
color: #666;
margin-top: 2px;
}
2026-01-09 18:14:35 +08:00
</style>