diff --git a/.env.development b/.env.development index 9cf069a..1a992dd 100644 --- a/.env.development +++ b/.env.development @@ -1,4 +1,3 @@ # 开发环境 VITE_PORT = 5173 -VITE_API_BASE_URL = http://localhost:3000 -VITE_API_PREFIX = /api +VITE_API_BASE_URL = http://localhost:3000/admin diff --git a/.env.production b/.env.production index dc54b4f..34d7624 100644 --- a/.env.production +++ b/.env.production @@ -1,3 +1,2 @@ # 生产环境 -VITE_API_BASE_URL = https://api.cpms.example.com -VITE_API_PREFIX = /api +VITE_API_BASE_URL = https://api.cpms.example.com/admin diff --git a/.env.test b/.env.test index b3621a6..7ca21d6 100644 --- a/.env.test +++ b/.env.test @@ -1,3 +1,2 @@ # 测试环境 -VITE_API_BASE_URL = https://test-api.cpms.example.com -VITE_API_PREFIX = /api +VITE_API_BASE_URL = https://test-api.cpms.example.com/admin diff --git a/src/api/events/index.ts b/src/api/events/index.ts new file mode 100644 index 0000000..4c62196 --- /dev/null +++ b/src/api/events/index.ts @@ -0,0 +1,100 @@ +/** + * 赛事管理 API + * 注意:/admin 前缀已通过 VITE_API_BASE_URL 统一注入(见 .env.*),此文件中只写业务路径。 + */ +import { get, post } from '@/utils/request'; +import { USE_MOCK, MOCK_DELAY } from '@/config/mock'; +import { MOCK_EVENT_DETAIL } from '@/config/mock/eventDetail'; +import { buildMockEventListPage } from '@/config/mock/eventList'; +import { buildMockSignupListPage } from '@/config/mock/eventSignup'; +import type { + EventListQueryParams, + SignupListQueryParams, + ToggleOnlineParams, + ApiResult, + PageData, + TournamentAdminVO, + TournamentAdminInfoVO, + TournamentAdminInfoSignupVO, +} from './types'; + +export type { + TournamentAdminVO, + TournamentAdminInfoVO, + TournamentAdminInfoSignupVO, + InnerCategoryVO, + EventListQueryParams, + SignupListQueryParams, + ToggleOnlineParams, + PageData, + ApiResult, +} from './types'; + +/** 模拟延时 */ +const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +/** + * 赛事列表 - 列表分页 + * GET /admin/manager/tm/page → /manager/tm/page + */ +export async function getEventList( + params: EventListQueryParams, +): Promise>> { + if (USE_MOCK) { + await delay(MOCK_DELAY); + const page = Number(params.page) || 1; + const limit = Number(params.limit) || 10; + return { + code: 200, + msg: 'success', + data: buildMockEventListPage(page, limit), + }; + } + return get('/manager/tm/page', params as Record); +} + +/** + * 赛事列表 - 赛事详情 + * GET /admin/manager/tm/info → /manager/tm/info + */ +export async function getEventDetail(id: string): Promise> { + if (USE_MOCK) { + await delay(MOCK_DELAY); + return { + code: 200, + msg: 'success', + data: { ...MOCK_EVENT_DETAIL }, + }; + } + return get('/manager/tm/info', { id }); +} + +/** + * 赛事列表 - 赛事详情 - 人员列表 + * GET /admin/manager/tm/info/signupPage → /manager/tm/info/signupPage + */ +export async function getSignupList( + params: SignupListQueryParams, +): Promise>> { + if (USE_MOCK) { + await delay(MOCK_DELAY); + return { + code: 200, + msg: 'success', + data: buildMockSignupListPage(params), + }; + } + return get('/manager/tm/info/signupPage', params as Record); +} + +/** + * 赛事上架 / 下架 + * POST /admin/manager/tm/online → /manager/tm/online + * @param params.id 赛事ID + * @param params.online 0=下架,1=上架 + */ +export function toggleEventOnline( + params: ToggleOnlineParams, +): Promise>> { + return post('/manager/tm/online', params); +} diff --git a/src/api/events/types.ts b/src/api/events/types.ts new file mode 100644 index 0000000..0a562fc --- /dev/null +++ b/src/api/events/types.ts @@ -0,0 +1,199 @@ +/** + * 赛事管理 — 类型定义 + * 对应 OpenAPI: /admin/manager/tm/page + */ + +/** 赛事列表项 */ +export interface TournamentAdminVO { + /** 赛事ID */ + id: string; + /** 赛事名称 */ + name: string; + /** 赛事开始时间 */ + startTimeBegin: string; + /** 赛事结束时间 */ + startTimeEnd: string; + /** 赛事省市区,场地区划名称 */ + venueArea: string; + /** 赛事地点名称 */ + venue: string; + /** 是否公开;0=非公开,1=公开 */ + isPublic: string; + /** 是否要求提供证件图片;0=无需,1=需要 */ + isRequireIdCardImg: string; + /** 是否要求提供证件号码;0=无需,1=需要 */ + isRequireIdCardNo: string; + /** 创建人昵称 */ + createNickname: string; + /** 创建人手机号 */ + createPhone: string; + /** 创建时间 */ + createTime: string; + /** + * 状态 + * 优先级:del_flag==1 → 3(已删除);online==0 → 4(已下架);否则取 status + * 0=报名中,1=进行中,2=已结束,3=已删除,4=已下架 + */ + status: string; + /** 报名人数 */ + signupCount: string; + /** 退赛人数 */ + dropoutCount: string; + /** 赛事规程 */ + rulesDescr: string; + /** 赛事规程图片 */ + rulesDescrImg: string; + /** 赛事规程图片数组 */ + rulesDescrImgList: string[]; +} + +/** 赛事列表查询参数 */ +export interface EventListQueryParams { + page?: string; + limit?: string; + /** 创建时间开始 yyyy-MM-dd */ + createDate?: string; + /** 创建时间结束 yyyy-MM-dd */ + createDateEnd?: string; + /** 赛事时间开始 yyyy-MM-dd */ + startDate?: string; + /** 赛事时间结束 yyyy-MM-dd */ + startDateEnd?: string; + /** 赛事名称 */ + name?: string; + /** 创建人昵称或手机号 */ + createName?: string; + /** + * 赛事状态 + * 不传=全部、0=报名中,1=进行中,2=已结束,3=已删除,4=已下架 + */ + status?: string; + /** 市级别城市名称 */ + areaName?: string; + /** 是否公开;0=非公开,1=公开 */ + isPublic?: string; + /** 是否要求提供证件图片;0=无需,1=需要 */ + idCardImg?: string; + /** 是否要求提供证件号码;0=无需,1=需要 */ + idCardNo?: string; +} + +/** 分页数据 */ +export interface PageData { + total: number; + list: T[]; +} + +/** 统一响应包裹 */ +export interface ApiResult { + /** 编码:200表示成功,其他值表示失败 */ + code: number; + /** 消息内容 */ + msg: string; + /** 响应数据 */ + data: T; +} + +// ============================================================ +// 赛事详情(对应 /manager/tm/info) +// ============================================================ + +/** 组别信息 */ +export interface InnerCategoryVO { + /** 组别ID */ + id: string; + /** 组别名称 */ + name: string; + /** 组别类型;1=单打,2=双打 */ + type: string; +} + +/** 赛事详情 */ +export interface TournamentAdminInfoVO { + /** 赛事名称 */ + name: string; + /** 赛事开始时间 */ + startTimeBegin: string; + /** 赛事结束时间 */ + startTimeEnd: string; + /** 赛事说明 */ + descr: string; + /** 报名开始时间 */ + signupTimeBegin: string; + /** 报名结束时间 */ + signupTimeEnd: string; + /** 赛事省市区 */ + venueArea: string; + /** 赛事地点名称 */ + venue: string; + /** 场地号 */ + venueNo: string; + /** 是否公开;0=非公开,1=公开 */ + isPublic: string; + /** 是否要求提供证件图片;0=无需,1=需要 */ + isRequireIdCardImg: string; + /** 是否要求提供证件号码;0=无需,1=需要 */ + isRequireIdCardNo: string; + /** 创建时间 */ + createTime: string; + /** 创建人昵称 */ + createNickname: string; + /** 报名人数 */ + signupCount: string; + /** 退赛人数 */ + dropoutCount: string; + /** 允许报名最大人数 */ + dtotalLimit: string; + /** 总对阵数 */ + matchTotalCount: string; + /** 已完成对阵数 */ + matchCount: string; + /** 赛事封面url */ + img: string; + /** 活动公告 */ + notice: string; + /** 组别列表,创建时间倒序 */ + categoryList: InnerCategoryVO[]; + /** 分组名称列表,格式 "小组名称(淘汰赛或循环赛)" */ + groupInfoList: string[]; +} + +// ============================================================ +// 人员列表(对应 /manager/tm/info/signupPage) +// ============================================================ + +/** 报名人员 */ +export interface TournamentAdminInfoSignupVO { + /** 选手名称 */ + realName: string; + /** 选手性别;1=男,2=女 */ + gender: string; + /** 选手手机号 */ + phone: string; + /** 选手身份证 */ + idCard: string; + /** 选手身份证图片 */ + idCardImg: string; + /** 选手身份证图片列表 */ + idCardImgList: string[]; +} + +/** 人员列表查询参数 */ +export interface SignupListQueryParams { + page: string; + limit: string; + /** 组别ID(必填) */ + categoryId: string; +} + +// ============================================================ +// 上下架(对应 /manager/tm/online) +// ============================================================ + +/** 上下架请求参数 */ +export interface ToggleOnlineParams { + /** 赛事ID */ + id: string; + /** 0=下架,1=上架 */ + online: string; +} diff --git a/src/config/mock.ts b/src/config/mock.ts new file mode 100644 index 0000000..c2a4655 --- /dev/null +++ b/src/config/mock.ts @@ -0,0 +1,12 @@ +/** + * 全局假数据开关 + * + * true = 所有 API 使用假数据(模拟真实网络延时 2s 后返回假数据) + * false = 走真实后端接口 + * + * 对接后端接口后将此值改为 false 即可切换。 + */ +export const USE_MOCK = true; + +/** 模拟网络延时(毫秒) */ +export const MOCK_DELAY = 850; diff --git a/src/config/mock/eventDetail.ts b/src/config/mock/eventDetail.ts new file mode 100644 index 0000000..87b8329 --- /dev/null +++ b/src/config/mock/eventDetail.ts @@ -0,0 +1,39 @@ +/** + * 赛事详情弹窗假数据 + * + * 使用页面:`src/pages/events/list/components/EventDetailModal.tsx` + * 对应接口:`GET /manager/tm/info` + * 类型:`TournamentAdminInfoVO` + */ +import type { TournamentAdminInfoVO } from '@/api/events'; + +export const MOCK_EVENT_DETAIL: TournamentAdminInfoVO = { + name: '第二届全国地学羽毛球邀请赛', + startTimeBegin: '2026-05-26 08:00', + startTimeEnd: '2026-05-31 22:00', + descr: + '赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍。', + signupTimeBegin: '2026-05-24 08:00', + signupTimeEnd: '2026-05-26 22:00', + venueArea: '北京市 北京市 东城区', + venue: '李宁体育馆', + venueNo: '李宁体育馆', + isPublic: '1', + isRequireIdCardImg: '0', + isRequireIdCardNo: '0', + createTime: '2026-05-26 08:00', + createNickname: '张三', + signupCount: '100', + dropoutCount: '1', + dtotalLimit: '1000', + matchTotalCount: '50', + matchCount: '50', + img: 'https://picsum.photos/seed/eventcover/600/300', + notice: + '赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告。', + categoryList: [ + { id: '1', name: '男子单打', type: '1' }, + { id: '2', name: '女子双打', type: '2' }, + ], + groupInfoList: ['男子小组1(循环赛)', '女子小组1(淘汰赛)'], +}; diff --git a/src/config/mock/eventList.ts b/src/config/mock/eventList.ts new file mode 100644 index 0000000..6681d3c --- /dev/null +++ b/src/config/mock/eventList.ts @@ -0,0 +1,268 @@ +/** + * 赛事列表页假数据 + * + * 使用页面:`src/pages/events/list/index.tsx` + * 对应接口:`GET /manager/tm/page` + * 类型:`TournamentAdminVO[]` + */ +import type { TournamentAdminVO, PageData } from '@/api/events'; + +export const MOCK_EVENT_LIST: TournamentAdminVO[] = [ + { + id: 'EV202607010001', + name: '2026全国青少年羽毛球锦标赛暨体育文化交流大会', + startTimeBegin: '2026-08-12 09:00', + startTimeEnd: '2026-08-15 18:00', + venueArea: '广东省 深圳市 南山区', + venue: '深圳湾体育中心', + isPublic: '1', + isRequireIdCardImg: '1', + isRequireIdCardNo: '1', + createNickname: '张运营', + createPhone: '13800138001', + createTime: '2026-07-10 08:11:07', + status: '0', + signupCount: '128', + dropoutCount: '5', + rulesDescr: '一、赛事目的:推动羽毛球运动发展。\n二、参赛资格:身体健康、年龄符合要求。', + rulesDescrImg: 'https://picsum.photos/seed/rules/300/200', + rulesDescrImgList: [ + 'https://picsum.photos/seed/rule1/300/200', + 'https://picsum.photos/seed/rule2/300/200', + ], + }, + { + id: 'EV202607010002', + name: '国际马拉松城市联赛', + startTimeBegin: '2026-08-12 09:00', + startTimeEnd: '2026-08-15 18:00', + venueArea: '广东省 深圳市 南山区', + venue: '深圳湾体育中心', + isPublic: '0', + isRequireIdCardImg: '0', + isRequireIdCardNo: '0', + createNickname: '李管理', + createPhone: '13900139002', + createTime: '2026-07-09 11:28:20', + status: '1', + signupCount: '256', + dropoutCount: '12', + rulesDescr: '一、赛事目的:推动马拉松运动发展。', + rulesDescrImg: 'https://picsum.photos/seed/rules/300/200', + rulesDescrImgList: ['https://picsum.photos/seed/rule1/300/200'], + }, + { + id: 'EV202607010003', + name: '全民健身运动会', + startTimeBegin: '2026-08-12 09:00', + startTimeEnd: '2026-08-15 18:00', + venueArea: '广东省 深圳市 南山区', + venue: '深圳湾体育中心', + isPublic: '1', + isRequireIdCardImg: '1', + isRequireIdCardNo: '1', + createNickname: '王策划', + createPhone: '13700137003', + createTime: '2026-07-08 14:45:33', + status: '2', + signupCount: '512', + dropoutCount: '8', + rulesDescr: '一、赛事目的:促进全民健身。', + rulesDescrImg: 'https://picsum.photos/seed/rules/300/200', + rulesDescrImgList: ['https://picsum.photos/seed/rule1/300/200'], + }, + { + id: 'EV202607010004', + name: '2026全国青少年羽毛球锦标赛暨体育文化交流大会', + startTimeBegin: '2026-08-12 09:00', + startTimeEnd: '2026-08-15 18:00', + venueArea: '广东省 深圳市 南山区', + venue: '深圳湾体育中心', + isPublic: '0', + isRequireIdCardImg: '0', + isRequireIdCardNo: '0', + createNickname: '赵赛事', + createPhone: '13600136004', + createTime: '2026-07-07 18:02:46', + status: '3', + signupCount: '64', + dropoutCount: '3', + rulesDescr: '一、赛事目的:交流体育文化。', + rulesDescrImg: 'https://picsum.photos/seed/rules/300/200', + rulesDescrImgList: [], + }, + { + id: 'EV202607010005', + name: '国际马拉松城市联赛', + startTimeBegin: '2026-08-12 09:00', + startTimeEnd: '2026-08-15 18:00', + venueArea: '广东省 深圳市 南山区', + venue: '深圳湾体育中心', + isPublic: '1', + isRequireIdCardImg: '1', + isRequireIdCardNo: '1', + createNickname: '张运营', + createPhone: '13800138001', + createTime: '2026-07-06 21:19:59', + status: '4', + signupCount: '0', + dropoutCount: '0', + rulesDescr: '一、赛事目的:推动城市体育交流。', + rulesDescrImg: 'https://picsum.photos/seed/rules/300/200', + rulesDescrImgList: ['https://picsum.photos/seed/rule1/300/200'], + }, + { + id: 'EV202607010006', + name: '全民健身运动会', + startTimeBegin: '2026-08-12 09:00', + startTimeEnd: '2026-08-15 18:00', + venueArea: '广东省 深圳市 南山区', + venue: '深圳湾体育中心', + isPublic: '0', + isRequireIdCardImg: '0', + isRequireIdCardNo: '0', + createNickname: '李管理', + createPhone: '13900139002', + createTime: '2026-07-05 00:36:12', + status: '0', + signupCount: '128', + dropoutCount: '5', + rulesDescr: '一、赛事目的:增强全民体质。', + rulesDescrImg: 'https://picsum.photos/seed/rules/300/200', + rulesDescrImgList: [], + }, + { + id: 'EV202607010007', + name: '2026全国青少年羽毛球锦标赛暨体育文化交流大会', + startTimeBegin: '2026-08-12 09:00', + startTimeEnd: '2026-08-15 18:00', + venueArea: '广东省 深圳市 南山区', + venue: '深圳湾体育中心', + isPublic: '1', + isRequireIdCardImg: '1', + isRequireIdCardNo: '1', + createNickname: '王策划', + createPhone: '13700137003', + createTime: '2026-07-04 03:53:25', + status: '1', + signupCount: '256', + dropoutCount: '12', + rulesDescr: '一、赛事目的:选拔青少年人才。', + rulesDescrImg: 'https://picsum.photos/seed/rules/300/200', + rulesDescrImgList: [ + 'https://picsum.photos/seed/rule1/300/200', + 'https://picsum.photos/seed/rule2/300/200', + ], + }, + { + id: 'EV202607010008', + name: '国际马拉松城市联赛', + startTimeBegin: '2026-08-12 09:00', + startTimeEnd: '2026-08-15 18:00', + venueArea: '广东省 深圳市 南山区', + venue: '深圳湾体育中心', + isPublic: '0', + isRequireIdCardImg: '0', + isRequireIdCardNo: '0', + createNickname: '赵赛事', + createPhone: '13600136004', + createTime: '2026-07-03 07:10:38', + status: '2', + signupCount: '512', + dropoutCount: '8', + rulesDescr: '一、赛事目的:连接城市与运动。', + rulesDescrImg: 'https://picsum.photos/seed/rules/300/200', + rulesDescrImgList: [], + }, + { + id: 'EV202607010009', + name: '全民健身运动会', + startTimeBegin: '2026-08-12 09:00', + startTimeEnd: '2026-08-15 18:00', + venueArea: '广东省 深圳市 南山区', + venue: '深圳湾体育中心', + isPublic: '1', + isRequireIdCardImg: '1', + isRequireIdCardNo: '1', + createNickname: '张运营', + createPhone: '13800138001', + createTime: '2026-07-02 10:27:51', + status: '3', + signupCount: '64', + dropoutCount: '3', + rulesDescr: '一、赛事目的:全民参与、共享健康。', + rulesDescrImg: 'https://picsum.photos/seed/rules/300/200', + rulesDescrImgList: ['https://picsum.photos/seed/rule1/300/200'], + }, + { + id: 'EV202607010010', + name: '2026全国青少年羽毛球锦标赛暨体育文化交流大会', + startTimeBegin: '2026-08-12 09:00', + startTimeEnd: '2026-08-15 18:00', + venueArea: '广东省 深圳市 南山区', + venue: '深圳湾体育中心', + isPublic: '0', + isRequireIdCardImg: '0', + isRequireIdCardNo: '0', + createNickname: '李管理', + createPhone: '13900139002', + createTime: '2026-07-01 13:44:04', + status: '4', + signupCount: '0', + dropoutCount: '0', + rulesDescr: '一、赛事目的:培养后备力量。', + rulesDescrImg: 'https://picsum.photos/seed/rules/300/200', + rulesDescrImgList: [], + }, + { + id: 'EV202607010011', + name: '国际马拉松城市联赛', + startTimeBegin: '2026-08-12 09:00', + startTimeEnd: '2026-08-15 18:00', + venueArea: '广东省 深圳市 南山区', + venue: '深圳湾体育中心', + isPublic: '1', + isRequireIdCardImg: '1', + isRequireIdCardNo: '1', + createNickname: '王策划', + createPhone: '13700137003', + createTime: '2026-06-30 17:01:17', + status: '0', + signupCount: '128', + dropoutCount: '5', + rulesDescr: '一、赛事目的:推广城市品牌。', + rulesDescrImg: 'https://picsum.photos/seed/rules/300/200', + rulesDescrImgList: [ + 'https://picsum.photos/seed/rule1/300/200', + 'https://picsum.photos/seed/rule2/300/200', + 'https://picsum.photos/seed/rule3/300/200', + ], + }, + { + id: 'EV202607010012', + name: '全民健身运动会', + startTimeBegin: '2026-08-12 09:00', + startTimeEnd: '2026-08-15 18:00', + venueArea: '广东省 深圳市 南山区', + venue: '深圳湾体育中心', + isPublic: '0', + isRequireIdCardImg: '0', + isRequireIdCardNo: '0', + createNickname: '赵赛事', + createPhone: '13600136004', + createTime: '2026-06-29 20:18:30', + status: '1', + signupCount: '256', + dropoutCount: '12', + rulesDescr: '一、赛事目的:丰富群众体育生活。', + rulesDescrImg: 'https://picsum.photos/seed/rules/300/200', + rulesDescrImgList: [], + }, +]; + +/** 构建分页响应 */ +export function buildMockEventListPage(page: number, limit: number): PageData { + const start = (page - 1) * limit; + const list = MOCK_EVENT_LIST.slice(start, start + limit); + return { total: MOCK_EVENT_LIST.length, list }; +} diff --git a/src/config/mock/eventSignup.ts b/src/config/mock/eventSignup.ts new file mode 100644 index 0000000..4b8940c --- /dev/null +++ b/src/config/mock/eventSignup.ts @@ -0,0 +1,96 @@ +/** + * 赛事详情 — 人员列表假数据 + * + * 使用页面:`src/pages/events/list/components/EventDetailModal.tsx` + * 对应接口:`GET /manager/tm/info/signupPage` + * 类型:`TournamentAdminInfoSignupVO[]` + */ +import type { TournamentAdminInfoSignupVO, PageData, SignupListQueryParams } from '@/api/events'; + +export const MOCK_SIGNUP_LIST: TournamentAdminInfoSignupVO[] = [ + { + realName: '张三', + gender: '1', + phone: '13800138001', + idCard: '110101199001011234', + idCardImg: 'https://picsum.photos/seed/idcard1/96/64', + idCardImgList: [ + 'https://picsum.photos/seed/idcard1a/96/64', + 'https://picsum.photos/seed/idcard1b/96/64', + ], + }, + { + realName: '李四', + gender: '1', + phone: '13900139002', + idCard: '110101199002022345', + idCardImg: 'https://picsum.photos/seed/idcard2/96/64', + idCardImgList: [ + 'https://picsum.photos/seed/idcard2a/96/64', + 'https://picsum.photos/seed/idcard2b/96/64', + ], + }, + { + realName: '王芳', + gender: '2', + phone: '13700137003', + idCard: '110101199103033456', + idCardImg: '', + idCardImgList: [], + }, + { + realName: '赵六', + gender: '1', + phone: '13600136004', + idCard: '110101199204044567', + idCardImg: 'https://picsum.photos/seed/idcard4/96/64', + idCardImgList: ['https://picsum.photos/seed/idcard4a/96/64'], + }, + { + realName: '孙七', + gender: '2', + phone: '13500135005', + idCard: '110101199305055678', + idCardImg: '', + idCardImgList: [], + }, + { + realName: '周八', + gender: '1', + phone: '13400134006', + idCard: '110101199406066789', + idCardImg: 'https://picsum.photos/seed/idcard6/96/64', + idCardImgList: [ + 'https://picsum.photos/seed/idcard6a/96/64', + 'https://picsum.photos/seed/idcard6b/96/64', + ], + }, + { + realName: '吴九', + gender: '2', + phone: '13300133007', + idCard: '110101199507077890', + idCardImg: '', + idCardImgList: [], + }, + { + realName: '郑十', + gender: '1', + phone: '13200132008', + idCard: '110101199608088901', + idCardImg: 'https://picsum.photos/seed/idcard8/96/64', + idCardImgList: ['https://picsum.photos/seed/idcard8a/96/64'], + }, +]; + +/** 构建分页响应 */ +export function buildMockSignupListPage( + params: SignupListQueryParams, +): PageData { + // mock 模式下忽略 categoryId 过滤,直接分页返回 + const page = Number(params.page) || 1; + const limit = Number(params.limit) || 10; + const start = (page - 1) * limit; + const list = MOCK_SIGNUP_LIST.slice(start, start + limit); + return { total: MOCK_SIGNUP_LIST.length, list }; +} diff --git a/src/pages/events/list/components/EventDetailModal.tsx b/src/pages/events/list/components/EventDetailModal.tsx index dfa1457..6e3edc4 100644 --- a/src/pages/events/list/components/EventDetailModal.tsx +++ b/src/pages/events/list/components/EventDetailModal.tsx @@ -1,6 +1,15 @@ -import { defineComponent } from 'vue'; -import { Modal, Descriptions, Image, Table, Pagination, Space } from 'ant-design-vue'; +import { defineComponent, reactive, onMounted } from 'vue'; +import { Modal, Descriptions, Image, Table, Pagination, Space, Spin } from 'ant-design-vue'; import { EyeOutlined } from '@ant-design/icons-vue'; +import { message } from 'ant-design-vue'; +import { + getEventDetail, + getSignupList, + type TournamentAdminInfoVO, + type InnerCategoryVO, + type TournamentAdminInfoSignupVO, +} from '@/api/events'; +import { useState } from '@/hooks'; import styles from './EventDetailModal.module.less'; interface EventDetailModalProps { @@ -9,97 +18,57 @@ interface EventDetailModalProps { onClose: () => void; } -// ===== Mock 数据 ===== -const MOCK_DETAIL = { - name: '第二届全国地学羽毛球邀请赛', - eventTime: '2026.05.26 08:00 ~ 2026.05.31 22:00', - descr: - '赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍赛事介绍。', - registerTime: '2026.05.24 08:00 ~ 2026.05.26 22:00', - cancelDeadline: '2026.05.25 22:00', - region: '北京市 北京市 东城区', - address: '李宁体育馆', - venueNo: '李宁体育馆', - publicEvent: '在首页展示赛事', - needIdCard: '无需提供', - needIdCardImage: '无需提供', - createTime: '2026.05.26 08:00', - creator: '张三', - status: '报名中', - enrolledCount: 100, - cancelledCount: 1, - maxCount: 1000, - crossedCount: 50, - completedCount: 50, - cover: 'https://picsum.photos/seed/eventcover/600/300', - announcement: - '赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告赛事公告。', +// ===== 0/1 显示映射 ===== +const YES_NO_MAP: Record = { '0': '否', '1': '是' }; +const PUBLIC_MAP: Record = { '0': '非公开', '1': '公开' }; +const GENDER_MAP: Record = { '1': '男', '2': '女' }; +const CATEGORY_TYPE_MAP: Record = { '1': '单打', '2': '双打' }; + +const formatYN = (v: string) => YES_NO_MAP[v] ?? v; +const formatPublic = (v: string) => PUBLIC_MAP[v] ?? v; +const formatGender = (v: string) => GENDER_MAP[v] ?? v; + +const PAGE_SIZE = 10; + +/** 渲染身份证图片 */ +const renderIdImages = (imgList: string[], singleImg: string) => { + const urls = imgList.length > 0 ? imgList : singleImg ? [singleImg] : []; + if (urls.length === 0) return -; + return ( + + + {urls.map((url) => ( + }} + /> + ))} + + + ); }; -// 假组别数据 -const MOCK_GROUPS = [ - { - key: 'group1', - title: '组别1:男子单打(单打)', - players: [ - { - key: '1', - name: '张三', - gender: '男', - phone: '12345678995', - idNo: 'xxxxxxxxxxxxxxxx', - idImg: '', - }, - { key: '2', name: '李四', gender: '男', phone: '', idNo: '', idImg: '' }, - ], - }, - { - key: 'group2', - title: '组别2:女子双打(双打)', - players: [ - { - key: '1', - name: '张三', - gender: '女', - phone: '12345678995', - idNo: 'xxxxxxxxxxxxxxxx', - idImg: '', - }, - { key: '2', name: '李四', gender: '女', phone: '', idNo: '', idImg: '' }, - ], - }, -]; - -// 假分组数据 -const MOCK_DIVISIONS = ['分组1:男子小组1(循环赛)', '分组2:女子小组1(淘汰赛)']; - -const renderIdImage = (src: string) => ( - , - }} - /> -); - +/** 选手表格列 */ const PLAYER_COLUMNS = [ - { title: '选手', dataIndex: 'name', key: 'name', width: 100 }, - { title: '性别', dataIndex: 'gender', key: 'gender', width: 80 }, - { title: '手机号', dataIndex: 'phone', key: 'phone', width: 150 }, - { title: '证件号', dataIndex: 'idNo', key: 'idNo', width: 180 }, + { title: '选手', dataIndex: 'realName', key: 'realName', width: 100 }, + { + title: '性别', + dataIndex: 'gender', + key: 'gender', + width: 60, + customRender: ({ text }: { text: string }) => formatGender(text), + }, + { title: '手机号', dataIndex: 'phone', key: 'phone', width: 130 }, + { title: '证件号', dataIndex: 'idCard', key: 'idCard', width: 180 }, { title: '证件图片', - dataIndex: 'idImg', - key: 'idImg', + key: 'idCardImgs', width: 180, - customRender: () => ( - - {renderIdImage('https://picsum.photos/seed/idimg1/96/64')} - {renderIdImage('https://picsum.photos/seed/idimg2/96/64')} - - ), + customRender: ({ record }: { record: TournamentAdminInfoSignupVO }) => + renderIdImages(record.idCardImgList, record.idCardImg), }, ]; @@ -111,7 +80,71 @@ export default defineComponent({ onClose: { type: Function, required: true }, }, setup(props: EventDetailModalProps) { - const detail = MOCK_DETAIL; + // ===== 详情数据 ===== + const [detail, setDetail] = useState(null); + const [detailLoading, setDetailLoading] = useState(true); + + // ===== 每个组别的选手数据(key = categoryId) ===== + const categoryPlayers = reactive>({}); + const categoryPlayerTotal = reactive>({}); + const categoryPlayerLoading = reactive>({}); + const categoryPlayerPage = reactive>({}); + + /** 获取某个组别的选手列表(分页) */ + const fetchPlayers = async (categoryId: string, page: number) => { + categoryPlayerLoading[categoryId] = true; + try { + const res = await getSignupList({ + categoryId, + page: String(page), + limit: String(PAGE_SIZE), + }); + if (res.code === 200) { + categoryPlayers[categoryId] = res.data.list; + categoryPlayerTotal[categoryId] = res.data.total; + categoryPlayerPage[categoryId] = page; + } + } catch { + // mock 模式不会到这里,真实模式下由 API 层处理 + } finally { + categoryPlayerLoading[categoryId] = false; + } + }; + + /** 选手分页变更 */ + const handlePlayerPageChange = (categoryId: string, page: number) => { + fetchPlayers(categoryId, page); + }; + + // ===== 初始化 ===== + onMounted(async () => { + setDetailLoading(true); + try { + const id = props.record?.id; + if (!id) { + message.error('缺少赛事ID'); + return; + } + + const res = await getEventDetail(String(id)); + if (res.code === 200) { + setDetail(res.data); + // 为每个组别加载第一页选手 + for (const cat of res.data.categoryList) { + categoryPlayers[cat.id] = []; + categoryPlayerTotal[cat.id] = 0; + categoryPlayerPage[cat.id] = 1; + fetchPlayers(cat.id, 1); + } + } else { + message.error(res.msg || '获取详情失败'); + } + } catch (e: any) { + message.error(e.msg || '获取详情失败'); + } finally { + setDetailLoading(false); + } + }); return () => ( - {/* ===== 基础信息 ===== */} -
-
基础信息
- - {/* 第一行:赛事名称(1 列) + 赛事时间(占 2 列) */} - {detail.name} - - {detail.eventTime} - + {detailLoading.value ? ( +
+ +
+ ) : detail.value ? ( + <> + {/* ===== 基础信息 ===== */} +
+
基础信息
+ + {detail.value.name} + + {detail.value.startTimeBegin} 至 {detail.value.startTimeEnd} + - {/* 第二行:赛事说明(占 3 列,独占整行) */} - - {detail.descr} - + + {detail.value.descr} + - {detail.registerTime} - - {detail.cancelDeadline} - + + {detail.value.signupTimeBegin} 至 {detail.value.signupTimeEnd} + - {detail.region} - {detail.address} - {detail.venueNo} + {detail.value.venueArea} + {detail.value.venue} + {detail.value.venueNo} - {detail.publicEvent} - {detail.needIdCard} - {detail.needIdCardImage} + + {formatPublic(detail.value.isPublic)} + + + {formatYN(detail.value.isRequireIdCardNo)} + + + {formatYN(detail.value.isRequireIdCardImg)} + - {detail.createTime} - {detail.creator} - {detail.status} - {detail.enrolledCount} - - {detail.cancelledCount} - + {detail.value.createTime} + + {detail.value.createNickname} + - {detail.maxCount} - {detail.crossedCount} - {detail.completedCount} - -
+ {detail.value.signupCount} + {detail.value.dropoutCount} + {detail.value.dtotalLimit} - {/* ===== 封面 ===== */} -
-
封面
- -
+ + {detail.value.matchTotalCount} + + + {detail.value.matchCount} + +
+
- {/* ===== 赛事公告 ===== */} -
-
赛事公告
-
{detail.announcement}
-
+ {/* ===== 封面 ===== */} +
+
封面
+ +
- {/* ===== 组别信息 ===== */} -
-
组别信息
- {MOCK_GROUPS.map((group) => ( -
-
{group.title}
- -
- + {/* ===== 赛事公告 ===== */} +
+
赛事公告
+
{detail.value.notice}
+
+ + {/* ===== 组别信息 ===== */} +
+
组别信息
+ {detail.value.categoryList.map((cat: InnerCategoryVO) => ( +
+
+ {cat.name}({CATEGORY_TYPE_MAP[cat.type] ?? cat.type}) +
+
+
+ handlePlayerPageChange(cat.id, page)} + /> +
+ + ))} + + + {/* ===== 分组信息 ===== */} +
+
分组信息
+
+ {detail.value.groupInfoList.map((d: string) => ( +
+ {d} +
+ ))}
- ))} - - - {/* ===== 分组信息 ===== */} -
-
分组信息
-
- {MOCK_DIVISIONS.map((d) => ( -
- {d} -
- ))} -
-
+ + ) : null} ); }, diff --git a/src/pages/events/list/index.tsx b/src/pages/events/list/index.tsx index ff861e8..ab8b1cb 100644 --- a/src/pages/events/list/index.tsx +++ b/src/pages/events/list/index.tsx @@ -1,4 +1,4 @@ -import { defineComponent } from 'vue'; +import { defineComponent, onMounted } from 'vue'; import { Button, Input, @@ -20,6 +20,7 @@ import { ID_CARD_OPTIONS, ID_CARD_IMAGE_OPTIONS, } from './model/useEventListModel'; +import { toggleEventOnline } from '@/api/events'; import { regionOptions } from '@/utils/areaData'; import { useContainerSize, useState } from '@/hooks'; import EventRegulationModal from './components/EventRegulationModal'; @@ -49,7 +50,7 @@ function renderBodyCell({ onAudit: (record: any) => void; onToggleShelf: (record: any) => void; }) { - if (column.dataIndex === 'eventName') { + if (column.dataIndex === 'name') { const maxLen = 15; const raw = text || ''; const display = raw.length > maxLen ? raw.slice(0, maxLen) + '...' : raw; @@ -74,19 +75,8 @@ function renderBodyCell({ const status = record.status; return ( - // 待审核:查看 + 审核 - (status === 'pending' && ( - - - - - )) || - // 报名中:查看 + 下架 - (status === 'enrolling' && ( + // 报名中(0) / 进行中(1):查看 + 下架 + ((status === '0' || status === '1') && ( )) || - // 已下架:查看 + 上架 - (status === 'removed' && ( + // 已下架(4):查看 + 上架 + (status === '4' && ( )) || ( - // 审核不通过 / 已删除:仅查看 + // 已结束(2) / 已删除(3):仅查看 @@ -144,49 +134,62 @@ export default defineComponent({ const [currentRecord, setCurrentRecord] = useState({}); const handleView = (record: any) => { - console.log('查看赛事:', record.eventId); + console.log('查看赛事:', record.id); setCurrentRecord(record); setDetailModalVisible(true); }; /** 上架 / 下架 通用 */ const handleToggleShelf = (record: any) => { - const isRemoved = record.status === 'removed'; + const isRemoved = record.status === '4'; const actionText = isRemoved ? '上架' : '下架'; Modal.confirm({ title: `${actionText}确认`, - content: `确认${actionText}"${record.eventName}"该赛事吗,下架后用户将无法搜索和报名该赛事,已有报名和订单不受影响。`, + content: `确认${actionText}"${record.name}"该赛事吗,下架后用户将无法搜索和报名该赛事,已有报名和订单不受影响。`, okText: '确认', cancelText: '取消', - onOk: () => { - console.log(`${actionText}赛事:`, record.eventId); - // TODO: 替换为真实 API 调用 - message.success(`${actionText}成功`); + onOk: async () => { + const online = isRemoved ? '1' : '0'; + try { + const res = await toggleEventOnline({ id: record.id, online }); + if (res.code === 200) { + message.success(`${actionText}成功`); + handleSearch(); + } else { + message.error(res.msg || `${actionText}失败`); + } + } catch (e: any) { + message.error(e.msg || `${actionText}失败`); + } }, }); }; /** 打开审核弹窗 */ const handleAudit = (record: any) => { - console.log('审核赛事:', record.eventId); + console.log('审核赛事:', record.id); setCurrentRecord(record); setAuditModalVisible(true); }; /** 提交审核 */ const handleAuditSubmit = (payload: { approved: boolean; comment: string }) => { - console.log('提交审核:', currentRecord.value.eventId, payload); + console.log('提交审核:', currentRecord.value.id, payload); // TODO: 替换为真实 API 调用 message.success('审核提交成功'); setAuditModalVisible(false); }; const handleViewRegulation = (record: any) => { - console.log('查看规程:', record.eventId); + console.log('查看规程:', record.id); setCurrentRecord(record); setRegulationModalVisible(true); }; + onMounted(() => { + handleSearch(); + }); + /** 最终表格列:模型列 + 赛事规程 + 操作 */ const tableColumns = [ ...columns, @@ -205,23 +208,27 @@ export default defineComponent({ {/* ===== 筛选区 ===== */}
- + (filterForm.createTimeRange = val)} + onUpdate:value={(val: any) => (filterForm.createDateRange = val)} /> - + (filterForm.eventTimeRange = val)} + onUpdate:value={(val: any) => (filterForm.startDateRange = val)} /> - + - + (filterForm.status = val || '')} /> - + (filterForm.needIdCard = val || '')} + onUpdate:value={(val: any) => (filterForm.idCardNo = val || '')} /> - +