3 Commits

Author SHA1 Message Date
ZhuRui 7f16ccdb30 fix: 赛事列表详情弹窗修复 2026-08-06 09:54:11 +08:00
ZhuRui 8c7e8f807c fix: 优化表单检测逻辑 2026-08-06 09:23:18 +08:00
ZhuRui e3bd35e154 fix: 去除已经实现过了的接口的假数据相关逻辑 2026-08-06 09:02:49 +08:00
42 changed files with 483 additions and 2492 deletions
-8
View File
@@ -2,8 +2,6 @@
* Banner 管理 API * Banner 管理 API
*/ */
import { get, post } from '@/utils/request'; import { get, post } from '@/utils/request';
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
import { MOCK_BANNER_LIST } from '@/config/mock/bannerList';
import type { import type {
BannerListQueryParams, BannerListQueryParams,
BannerSaveParams, BannerSaveParams,
@@ -22,16 +20,10 @@ export type {
ApiResult, ApiResult,
} from './types'; } from './types';
const delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
/** GET /manager/banner/list — Banner 列表 */ /** GET /manager/banner/list — Banner 列表 */
export async function getBannerList( export async function getBannerList(
params: BannerListQueryParams, params: BannerListQueryParams,
): Promise<ApiResult<TournamentAdminBannerListVO[]>> { ): Promise<ApiResult<TournamentAdminBannerListVO[]>> {
if (USE_MOCK) {
await delay(MOCK_DELAY);
return { code: 200, msg: 'success', data: MOCK_BANNER_LIST };
}
return get('/manager/banner/list', params as Record<string, any>); return get('/manager/banner/list', params as Record<string, any>);
} }
+1 -1
View File
@@ -143,7 +143,7 @@ export interface TournamentAdminInfoVO {
/** 退赛人数 */ /** 退赛人数 */
dropoutCount: string; dropoutCount: string;
/** 允许报名最大人数 */ /** 允许报名最大人数 */
dtotalLimit: string; totalLimit: string;
/** 总对阵数 */ /** 总对阵数 */
matchTotalCount: string; matchTotalCount: string;
/** 已完成对阵数 */ /** 已完成对阵数 */
-19
View File
@@ -2,8 +2,6 @@
* 操作日志 API * 操作日志 API
*/ */
import { get } from '@/utils/request'; import { get } from '@/utils/request';
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
import { buildMockOperationLogPage } from '@/config/mock/operationLogs';
import type { import type {
OperationLogQueryParams, OperationLogQueryParams,
SysOperationLogVO, SysOperationLogVO,
@@ -20,27 +18,10 @@ export type {
ApiResult, ApiResult,
} from './types'; } from './types';
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
function pickFilters(params: OperationLogQueryParams): OperationLogQueryParams {
const { page: _page, limit: _limit, ...filters } = params;
return filters;
}
/** GET /admin/sys/operation/page — 系统操作日志分页 */ /** GET /admin/sys/operation/page — 系统操作日志分页 */
export async function getOperationLogList( export async function getOperationLogList(
params: OperationLogQueryParams, params: OperationLogQueryParams,
): Promise<ApiResult<PageData<SysOperationLogVO>>> { ): Promise<ApiResult<PageData<SysOperationLogVO>>> {
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: buildMockOperationLogPage(page, limit, pickFilters(params)),
};
}
return get('/sys/operation/page', params as Record<string, any>); return get('/sys/operation/page', params as Record<string, any>);
} }
-37
View File
@@ -3,13 +3,6 @@
* 注意:/admin 前缀已通过 VITE_API_BASE_URL 统一注入(见 .env.*),此文件中只写业务路径。 * 注意:/admin 前缀已通过 VITE_API_BASE_URL 统一注入(见 .env.*),此文件中只写业务路径。
*/ */
import { get, post } from '@/utils/request'; import { get, post } from '@/utils/request';
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
import { buildMockOrderListPage } from '@/config/mock/orderList';
import { MOCK_ORDER_DETAIL } from '@/config/mock/orderDetail';
import {
MOCK_ORDER_PLAYER_LIST,
buildMockOrderPlayerListPage,
} from '@/config/mock/orderPlayerList';
import type { import type {
OrderListQueryParams, OrderListQueryParams,
OrderDetailQueryParams, OrderDetailQueryParams,
@@ -35,8 +28,6 @@ export type {
ApiResult, ApiResult,
} from './types'; } from './types';
const delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
/** /**
* 订单管理 - 订单分页 * 订单管理 - 订单分页
* GET /admin/manager/order/page → /manager/order/page * GET /admin/manager/order/page → /manager/order/page
@@ -44,16 +35,6 @@ const delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve,
export async function getOrderList( export async function getOrderList(
params: OrderListQueryParams, params: OrderListQueryParams,
): Promise<ApiResult<PageData<TournamentAdminOrderPageVO>>> { ): Promise<ApiResult<PageData<TournamentAdminOrderPageVO>>> {
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: buildMockOrderListPage(page, limit),
};
}
return get('/manager/order/page', params as Record<string, any>); return get('/manager/order/page', params as Record<string, any>);
} }
@@ -64,14 +45,6 @@ export async function getOrderList(
export async function getOrderDetail( export async function getOrderDetail(
params: OrderDetailQueryParams, params: OrderDetailQueryParams,
): Promise<ApiResult<TournamentAdminOrderInfoVO>> { ): Promise<ApiResult<TournamentAdminOrderInfoVO>> {
if (USE_MOCK) {
await delay(MOCK_DELAY);
return {
code: 200,
msg: 'success',
data: { ...MOCK_ORDER_DETAIL },
};
}
return get('/manager/order/info', params as Record<string, any>); return get('/manager/order/info', params as Record<string, any>);
} }
@@ -82,16 +55,6 @@ export async function getOrderDetail(
export async function getOrderPlayerList( export async function getOrderPlayerList(
params: OrderPlayerListQueryParams, params: OrderPlayerListQueryParams,
): Promise<ApiResult<PageData<TournamentAdminOrderInfoPageVO>>> { ): Promise<ApiResult<PageData<TournamentAdminOrderInfoPageVO>>> {
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: buildMockOrderPlayerListPage(page, limit),
};
}
return get('/manager/order/info/page', params as Record<string, any>); return get('/manager/order/info/page', params as Record<string, any>);
} }
-10
View File
@@ -3,8 +3,6 @@
* 对应 OpenAPI: GET /admin/finance/paymentFlow/page * 对应 OpenAPI: GET /admin/finance/paymentFlow/page
*/ */
import { get } from '@/utils/request'; import { get } from '@/utils/request';
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
import { buildMockPaymentFlowPage } from '@/config/mock/paymentFlow';
import type { import type {
PaymentFlowQueryParams, PaymentFlowQueryParams,
PaymentFlowVO, PaymentFlowVO,
@@ -20,17 +18,9 @@ export type {
ApiResult, ApiResult,
} from './types'; } from './types';
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
/** GET /admin/finance/paymentFlow/page — 支付流水分页 */ /** GET /admin/finance/paymentFlow/page — 支付流水分页 */
export async function getPaymentFlowList( export async function getPaymentFlowList(
params: PaymentFlowQueryParams, params: PaymentFlowQueryParams,
): Promise<ApiResult<PaymentFlowPageData>> { ): Promise<ApiResult<PaymentFlowPageData>> {
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: buildMockPaymentFlowPage(page, limit, params) };
}
return get('/finance/paymentFlow/page', params as Record<string, any>); return get('/finance/paymentFlow/page', params as Record<string, any>);
} }
-23
View File
@@ -2,10 +2,6 @@
* 角色管理 API * 角色管理 API
*/ */
import { get, post } from '@/utils/request'; import { get, post } from '@/utils/request';
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
import { buildMockRoleListPage } from '@/config/mock/roleList';
import { buildMockRoleUsageUserPage } from '@/config/mock/roleUsageUser';
import { buildMockPermissionTree } from '@/config/mock/permissionTree';
import type { import type {
RoleListQueryParams, RoleListQueryParams,
RoleUsageUserQueryParams, RoleUsageUserQueryParams,
@@ -26,18 +22,10 @@ export type {
ApiResult, ApiResult,
} from './types'; } from './types';
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
/** GET /sys/role/page — 角色列表分页 */ /** GET /sys/role/page — 角色列表分页 */
export async function getRoleList( export async function getRoleList(
params: RoleListQueryParams, params: RoleListQueryParams,
): Promise<ApiResult<PageData<TournamentAdminRolePageVO>>> { ): Promise<ApiResult<PageData<TournamentAdminRolePageVO>>> {
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: buildMockRoleListPage(page, limit) };
}
return get('/admin/sys/role/page', params as Record<string, any>); return get('/admin/sys/role/page', params as Record<string, any>);
} }
@@ -45,12 +33,6 @@ export async function getRoleList(
export async function getRoleUsageUserList( export async function getRoleUsageUserList(
params: RoleUsageUserQueryParams, params: RoleUsageUserQueryParams,
): Promise<ApiResult<PageData<RoleUsageUserVO>>> { ): Promise<ApiResult<PageData<RoleUsageUserVO>>> {
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: buildMockRoleUsageUserPage(page, limit) };
}
return get('/sys/role/usageUserPage', params as Record<string, any>); return get('/sys/role/usageUserPage', params as Record<string, any>);
} }
@@ -58,11 +40,6 @@ export async function getRoleUsageUserList(
export async function getPermissionTree( export async function getPermissionTree(
roleId: number | string, roleId: number | string,
): Promise<ApiResult<PermissionTreeNode[]>> { ): Promise<ApiResult<PermissionTreeNode[]>> {
if (USE_MOCK) {
await delay(MOCK_DELAY);
const id = Number(roleId) || 0;
return { code: 200, msg: 'success', data: buildMockPermissionTree(id) };
}
return get('/sys/role/permissionList', { roleId: String(roleId) }); return get('/sys/role/permissionList', { roleId: String(roleId) });
} }
-12
View File
@@ -2,8 +2,6 @@
* 用户管理 API * 用户管理 API
*/ */
import { get, post } from '@/utils/request'; import { get, post } from '@/utils/request';
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
import { buildMockUserListPage } from '@/config/mock/userList';
import type { import type {
UserListQueryParams, UserListQueryParams,
UserSaveParams, UserSaveParams,
@@ -26,30 +24,20 @@ export type {
ApiResult, ApiResult,
} from './types'; } from './types';
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
/** GET /admin/sys/user/page — 用户分页 */ /** GET /admin/sys/user/page — 用户分页 */
export async function getUserList( export async function getUserList(
params: UserListQueryParams, params: UserListQueryParams,
): Promise<ApiResult<PageData<TournamentAdminUserVO>>> { ): Promise<ApiResult<PageData<TournamentAdminUserVO>>> {
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: buildMockUserListPage(page, limit) };
}
return get('/admin/sys/user/page', params as Record<string, any>); return get('/admin/sys/user/page', params as Record<string, any>);
} }
/** POST /admin/sys/user/save — 新增用户 */ /** POST /admin/sys/user/save — 新增用户 */
export function saveUser(params: UserSaveParams): Promise<ApiResult<Record<string, never>>> { export function saveUser(params: UserSaveParams): Promise<ApiResult<Record<string, never>>> {
if (USE_MOCK) return delay(MOCK_DELAY).then(() => ({ code: 200, msg: 'success', data: {} }));
return post('/admin/sys/user/save', params); return post('/admin/sys/user/save', params);
} }
/** POST /admin/sys/user/update — 更新用户 */ /** POST /admin/sys/user/update — 更新用户 */
export function updateUser(params: UserUpdateParams): Promise<ApiResult<Record<string, never>>> { export function updateUser(params: UserUpdateParams): Promise<ApiResult<Record<string, never>>> {
if (USE_MOCK) return delay(MOCK_DELAY).then(() => ({ code: 200, msg: 'success', data: {} }));
return post('/admin/sys/user/update', params); return post('/admin/sys/user/update', params);
} }
-17
View File
@@ -3,9 +3,6 @@
* OpenAPI: GET /admin/finance/wallet/summary, GET /admin/finance/wallet/page * OpenAPI: GET /admin/finance/wallet/summary, GET /admin/finance/wallet/page
*/ */
import { get } from '@/utils/request'; import { get } from '@/utils/request';
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
import { buildMockWalletPage } from '@/config/mock/walletList';
import { buildMockWalletTransactionPage } from '@/config/mock/walletTransactions';
import type { import type {
WalletQueryParams, WalletQueryParams,
WalletSummaryVO, WalletSummaryVO,
@@ -26,18 +23,10 @@ export type {
ApiResult, ApiResult,
} from './types'; } from './types';
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
/** GET /admin/finance/wallet/summary — 钱包列表分页 */ /** GET /admin/finance/wallet/summary — 钱包列表分页 */
export async function getWalletList( export async function getWalletList(
params: WalletQueryParams, params: WalletQueryParams,
): Promise<ApiResult<WalletSummaryPageData>> { ): Promise<ApiResult<WalletSummaryPageData>> {
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: buildMockWalletPage(page, limit, params) };
}
return get('/finance/wallet/summary', params as Record<string, any>); return get('/finance/wallet/summary', params as Record<string, any>);
} }
@@ -45,11 +34,5 @@ export async function getWalletList(
export async function getWalletTransactionList( export async function getWalletTransactionList(
params: WalletTransactionQueryParams, params: WalletTransactionQueryParams,
): Promise<ApiResult<PageData<WalletTransactionVO>>> { ): Promise<ApiResult<PageData<WalletTransactionVO>>> {
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: buildMockWalletTransactionPage(page, limit, params) };
}
return get('/finance/wallet/page', params as Record<string, any>); return get('/finance/wallet/page', params as Record<string, any>);
} }
-19
View File
@@ -3,38 +3,19 @@
* 对应 OpenAPI: GET /admin/finance/withdraw/page, GET /admin/finance/withdraw/info * 对应 OpenAPI: GET /admin/finance/withdraw/page, GET /admin/finance/withdraw/info
*/ */
import { get, post } from '@/utils/request'; import { get, post } from '@/utils/request';
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
import { buildMockWithdrawPage } from '@/config/mock/withdrawList';
import { buildMockWithdrawInfo } from '@/config/mock/withdrawInfo';
import type { WithdrawQueryParams, WithdrawVO, WithdrawInfoVO, PageData, ApiResult } from './types'; import type { WithdrawQueryParams, WithdrawVO, WithdrawInfoVO, PageData, ApiResult } from './types';
export type { WithdrawVO, WithdrawInfoVO, WithdrawQueryParams, PageData, ApiResult } from './types'; export type { WithdrawVO, WithdrawInfoVO, WithdrawQueryParams, PageData, ApiResult } from './types';
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
/** GET /admin/finance/withdraw/page — 提现列表分页 */ /** GET /admin/finance/withdraw/page — 提现列表分页 */
export async function getWithdrawList( export async function getWithdrawList(
params: WithdrawQueryParams, params: WithdrawQueryParams,
): Promise<ApiResult<PageData<WithdrawVO>>> { ): Promise<ApiResult<PageData<WithdrawVO>>> {
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: buildMockWithdrawPage(page, limit, params),
};
}
return get('/admin/finance/withdraw/page', params as Record<string, any>); return get('/admin/finance/withdraw/page', params as Record<string, any>);
} }
/** GET /admin/finance/withdraw/info?id=xxx — 提现详情 */ /** GET /admin/finance/withdraw/info?id=xxx — 提现详情 */
export async function getWithdrawInfo(id: number): Promise<ApiResult<WithdrawInfoVO>> { export async function getWithdrawInfo(id: number): Promise<ApiResult<WithdrawInfoVO>> {
if (USE_MOCK) {
await delay(MOCK_DELAY);
return { code: 200, msg: 'success', data: buildMockWithdrawInfo(id) };
}
return get('/admin/finance/withdraw/info', { id: String(id) }); return get('/admin/finance/withdraw/info', { id: String(id) });
} }
-169
View File
@@ -1,169 +0,0 @@
/**
* Banner 列表页假数据
*
* 使用页面:`src/pages/events/banner/index.tsx`
* 对应接口:`GET /manager/banner/list`
* 类型:`TournamentAdminBannerListVO[]`
*
* 注意:类型内联定义,避免循环引用。
*/
export interface MockBannerVO {
id: string;
img: string;
title: string;
type: string;
params: string;
area: string;
showTimeBegin: string;
showTimeEnd: string;
status: string;
createDate: string;
}
export const MOCK_BANNER_LIST: MockBannerVO[] = [
{
id: '1',
img: 'https://picsum.photos/seed/banner1/80/44',
title: '2026全国青少年羽毛球锦标赛火热报名中',
type: '1',
params: 'EV20260701001',
area: '深圳市',
showTimeBegin: '2026-08-01 00:00',
showTimeEnd: '2026-08-31 23:59',
status: '1',
createDate: '2026-07-20 08:00:00',
},
{
id: '2',
img: 'https://picsum.photos/seed/banner2/80/44',
title: '马拉松城市联赛限时优惠',
type: '2',
params: 'https://example.com/marathon',
area: '杭州市',
showTimeBegin: '2026-08-15 00:00',
showTimeEnd: '2026-09-15 23:59',
status: '1',
createDate: '2026-07-19 11:30:00',
},
{
id: '3',
img: 'https://picsum.photos/seed/banner3/80/44',
title: '健身运动会专属福利',
type: '1',
params: 'EV20260701003',
area: '北京市',
showTimeBegin: '2026-07-01 00:00',
showTimeEnd: '2026-07-30 23:59',
status: '1',
createDate: '2026-07-18 14:20:00',
},
{
id: '4',
img: 'https://picsum.photos/seed/banner4/80/44',
title: '平台品牌宣传活动',
type: '2',
params: 'https://example.com/promo',
area: '上海市',
showTimeBegin: '2026-06-01 00:00',
showTimeEnd: '2026-06-30 23:59',
status: '0',
createDate: '2026-07-17 18:05:00',
},
{
id: '5',
img: 'https://picsum.photos/seed/banner5/80/44',
title: '2026全国青少年羽毛球锦标赛火热报名中',
type: '1',
params: 'EV20260701005',
area: '深圳市',
showTimeBegin: '2026-09-01 00:00',
showTimeEnd: '2026-09-30 23:59',
status: '1',
createDate: '2026-07-16 21:15:00',
},
{
id: '6',
img: 'https://picsum.photos/seed/banner6/80/44',
title: '马拉松城市联赛限时优惠',
type: '3',
params: '/pages/activity/detail',
area: '杭州市',
showTimeBegin: '2026-10-01 00:00',
showTimeEnd: '2026-10-31 23:59',
status: '1',
createDate: '2026-07-15 00:30:00',
},
{
id: '7',
img: 'https://picsum.photos/seed/banner7/80/44',
title: '全民健身日特别活动',
type: '1',
params: 'EV20260701007',
area: '北京市',
showTimeBegin: '2026-08-08 00:00',
showTimeEnd: '2026-08-08 23:59',
status: '1',
createDate: '2026-07-14 03:50:00',
},
{
id: '8',
img: 'https://picsum.photos/seed/banner8/80/44',
title: '新春运动季大促销',
type: '2',
params: 'https://example.com/spring',
area: '上海市',
showTimeBegin: '2026-01-15 00:00',
showTimeEnd: '2026-02-15 23:59',
status: '0',
createDate: '2026-07-13 07:05:00',
},
{
id: '9',
img: 'https://picsum.photos/seed/banner9/80/44',
title: '2026全国青少年羽毛球锦标赛火热报名中',
type: '0',
params: '',
area: '全国',
showTimeBegin: '2026-08-01 00:00',
showTimeEnd: '2026-08-31 23:59',
status: '1',
createDate: '2026-07-12 10:25:00',
},
{
id: '10',
img: 'https://picsum.photos/seed/banner10/80/44',
title: '平台品牌宣传活动',
type: '2',
params: 'https://example.com/brand',
area: '杭州市',
showTimeBegin: '2026-11-01 00:00',
showTimeEnd: '2026-11-30 23:59',
status: '1',
createDate: '2026-07-11 13:40:00',
},
{
id: '11',
img: 'https://picsum.photos/seed/banner11/80/44',
title: '秋季健身挑战赛',
type: '1',
params: 'EV20260701011',
area: '北京市',
showTimeBegin: '2026-09-01 00:00',
showTimeEnd: '2026-09-30 23:59',
status: '1',
createDate: '2026-07-10 17:00:00',
},
{
id: '12',
img: 'https://picsum.photos/seed/banner12/80/44',
title: '双十一运动装备大促',
type: '2',
params: 'https://example.com/double11',
area: '上海市',
showTimeBegin: '2026-11-01 00:00',
showTimeEnd: '2026-11-11 23:59',
status: '1',
createDate: '2026-07-09 20:15:00',
},
];
-182
View File
@@ -1,182 +0,0 @@
/**
* 操作日志假数据
* 对应接口:GET /admin/sys/operation/page
*
* 类型内联,避免与 @/api/logs 循环引用。
*/
interface MockSysOperationLogVO {
type: number;
nickname: string;
createDate: string;
content: string;
}
interface MockOperationLogQueryParams {
type?: string;
nickname?: string;
dateBegin?: string;
dateEnd?: string;
}
interface MockPageData<T> {
total: number;
list: T[];
}
const MOCK_LOGS: MockSysOperationLogVO[] = [
{
type: 1,
nickname: '张三',
createDate: '2026-07-30 08:00:00',
content: '下架赛事"第二十一届贝格林羽毛球大赛"',
},
{
type: 2,
nickname: '张三',
createDate: '2026-07-30 09:30:00',
content: '上架赛事"第二十一届贝格林羽毛球大赛"',
},
{
type: 3,
nickname: '李四',
createDate: '2026-07-29 10:12:00',
content: '禁用用户"王五(1003"',
},
{
type: 4,
nickname: '李四',
createDate: '2026-07-29 10:20:00',
content: '启用用户"王五(1003"',
},
{
type: 1,
nickname: '赵六',
createDate: '2026-07-28 14:00:00',
content: '下架赛事"春季马拉松大赛"',
},
{
type: 2,
nickname: '赵六',
createDate: '2026-07-28 15:00:00',
content: '上架赛事"春季马拉松大赛"',
},
{
type: 3,
nickname: '张三',
createDate: '2026-07-27 09:00:00',
content: '禁用用户"钱七(1005"',
},
{
type: 4,
nickname: '张三',
createDate: '2026-07-27 09:15:00',
content: '启用用户"钱七(1005"',
},
{
type: 1,
nickname: '孙八',
createDate: '2026-07-26 16:30:00',
content: '下架赛事"夏季游泳锦标赛"',
},
{
type: 2,
nickname: '孙八',
createDate: '2026-07-26 17:00:00',
content: '上架赛事"夏季游泳锦标赛"',
},
{
type: 1,
nickname: '李四',
createDate: '2026-07-25 11:00:00',
content: '下架赛事"秋季篮球邀请赛"',
},
{
type: 2,
nickname: '李四',
createDate: '2026-07-25 12:00:00',
content: '上架赛事"秋季篮球邀请赛"',
},
{
type: 3,
nickname: '周九',
createDate: '2026-07-24 08:30:00',
content: '禁用用户"吴十(1008"',
},
{
type: 4,
nickname: '周九',
createDate: '2026-07-24 08:45:00',
content: '启用用户"吴十(1008"',
},
{
type: 1,
nickname: '张三',
createDate: '2026-07-23 13:00:00',
content: '下架赛事"冬季滑雪挑战赛"',
},
{
type: 2,
nickname: '张三',
createDate: '2026-07-23 14:00:00',
content: '上架赛事"冬季滑雪挑战赛"',
},
{
type: 3,
nickname: '郑十一',
createDate: '2026-07-22 10:00:00',
content: '禁用用户"冯十二(1012"',
},
{
type: 4,
nickname: '郑十一',
createDate: '2026-07-22 10:10:00',
content: '启用用户"冯十二(1012"',
},
{
type: 1,
nickname: '孙八',
createDate: '2026-07-21 15:30:00',
content: '下架赛事"元旦全民健身跑"',
},
{
type: 2,
nickname: '孙八',
createDate: '2026-07-21 16:00:00',
content: '上架赛事"元旦全民健身跑"',
},
];
/** 按日期范围过滤(dateBegin / dateEnd 格式 yyyy-MM-dd */
function inDateRange(dateStr: string, begin?: string, end?: string): boolean {
if (!begin && !end) return true;
const d = dateStr.slice(0, 10);
if (begin && d < begin) return false;
if (end && d > end) return false;
return true;
}
export function buildMockOperationLogPage(
page: number,
limit: number,
params: MockOperationLogQueryParams,
): MockPageData<MockSysOperationLogVO> {
let filtered = [...MOCK_LOGS];
if (params.type) {
filtered = filtered.filter((item) => item.type === Number(params.type));
}
if (params.nickname) {
filtered = filtered.filter((item) => item.nickname.includes(params.nickname!));
}
filtered = filtered.filter((item) =>
inDateRange(item.createDate, params.dateBegin, params.dateEnd),
);
const total = filtered.length;
const start = (page - 1) * limit;
const list = filtered.slice(start, start + limit);
return { total, list };
}
-41
View File
@@ -1,41 +0,0 @@
/**
* 订单详情弹窗假数据
*
* 使用页面:`src/pages/events/orders/components/OrderDetailModal.tsx`
* 对应接口:`GET /manager/order/info`
* 类型:`TournamentAdminOrderInfoVO`
*/
import type { TournamentAdminOrderInfoVO } from '@/api/orders/types';
export const MOCK_ORDER_DETAIL: TournamentAdminOrderInfoVO = {
tournamentName: '2026全国青少年羽毛球锦标赛暨体育文化交流大会',
orderNumber: 'ORD20260700001',
orderTime: '2026.07.12 08:11:07',
payTime: '2026.07.12 08:12:30',
orderAmount: '188.00',
discountAmount: '20.00',
payAmount: '168.00',
refundAmount: '50.00',
refundInfoList: [
{
refundedAmount: '30.00',
totalRefundCount: '2',
refundAmount: '30.00',
isFullRefund: '0',
refundMethod: '原路退回(微信支付)',
refundApplyTime: '2026.07.15 09:30:00',
refundOutTradeNo: '5030000789202607150930000123',
refundTime: '2026.07.15 09:35:00',
},
{
refundedAmount: '20.00',
totalRefundCount: '2',
refundAmount: '20.00',
isFullRefund: '1',
refundMethod: '原路退回(微信支付)',
refundApplyTime: '2026.07.16 14:00:00',
refundOutTradeNo: '5030000789202607161400000456',
refundTime: '',
},
],
};
-256
View File
@@ -1,256 +0,0 @@
/**
* 订单列表页假数据
*
* 使用页面:`src/pages/events/orders/index.tsx`
* 对应接口:`GET /manager/order/page`
* 类型:`TournamentAdminOrderPageVO[]`
*/
import type { TournamentAdminOrderPageVO, PageData } from '@/api/orders/types';
export const MOCK_ORDER_LIST: TournamentAdminOrderPageVO[] = [
{
orderNo: 'ORD20260700001',
uniqueId: 101,
name: '2026全国青少年羽毛球锦标赛暨体育文化交流大会',
realName: '张选手',
phone: '13800138001',
categoryName: '男子青年组',
creatorName: '张报名',
creatorPhone: '13800138001',
creatorSignupCount: 2,
creatorSignupFee: '188.00',
creatorSignupFeeDiscount: '20.00',
creatorSignupFeeActual: '168.00',
creatorSignupFeeRefund: '0.00',
orderTime: '2026.07.12 08:11:07',
payTime: '2026.07.12 08:12:30',
orderStatus: 1,
refundStatus: 1,
},
{
orderNo: 'ORD20260700002',
uniqueId: 102,
name: '国际马拉松城市联赛',
realName: '李选手',
phone: '13900139002',
categoryName: '女子青年组',
creatorName: '李报名',
creatorPhone: '13900139002',
creatorSignupCount: 1,
creatorSignupFee: '256.50',
creatorSignupFeeDiscount: '10.00',
creatorSignupFeeActual: '246.50',
creatorSignupFeeRefund: '50.00',
orderTime: '2026.07.11 11:28:20',
payTime: '2026.07.11 11:30:05',
orderStatus: 2,
refundStatus: 2,
},
{
orderNo: 'ORD20260700003',
uniqueId: 103,
name: '全民健身运动会',
realName: '王选手',
phone: '13700137003',
categoryName: '男子中年组',
creatorName: '王报名',
creatorPhone: '13700137003',
creatorSignupCount: 1,
creatorSignupFee: '399.00',
creatorSignupFeeDiscount: '0.00',
creatorSignupFeeActual: '399.00',
creatorSignupFeeRefund: '399.00',
orderTime: '2026.07.10 14:45:33',
payTime: '2026.07.10 14:46:10',
orderStatus: 3,
refundStatus: 2,
},
{
orderNo: 'ORD20260700004',
uniqueId: 104,
name: '2026全国青少年羽毛球锦标赛暨体育文化交流大会',
realName: '赵选手',
phone: '13600136004',
categoryName: '女子中年组',
creatorName: '赵报名',
creatorPhone: '13600136004',
creatorSignupCount: 3,
creatorSignupFee: '128.00',
creatorSignupFeeDiscount: '20.00',
creatorSignupFeeActual: '108.00',
creatorSignupFeeRefund: '0.00',
orderTime: '2026.07.09 18:02:46',
payTime: '2026.07.09 18:03:15',
orderStatus: 4,
refundStatus: 1,
},
{
orderNo: 'ORD20260700005',
uniqueId: 105,
name: '国际马拉松城市联赛',
realName: '张选手',
phone: '13800138001',
categoryName: '男子青年组',
creatorName: '张报名',
creatorPhone: '13800138001',
creatorSignupCount: 2,
creatorSignupFee: '520.00',
creatorSignupFeeDiscount: '10.00',
creatorSignupFeeActual: '510.00',
creatorSignupFeeRefund: '0.00',
orderTime: '2026.07.08 21:19:59',
payTime: '2026.07.08 21:21:20',
orderStatus: 1,
refundStatus: 1,
},
{
orderNo: 'ORD20260700006',
uniqueId: 106,
name: '全民健身运动会',
realName: '李选手',
phone: '13900139002',
categoryName: '女子青年组',
creatorName: '李报名',
creatorPhone: '13900139002',
creatorSignupCount: 1,
creatorSignupFee: '188.00',
creatorSignupFeeDiscount: '0.00',
creatorSignupFeeActual: '188.00',
creatorSignupFeeRefund: '0.00',
orderTime: '2026.07.07 00:36:12',
payTime: '2026.07.07 00:37:00',
orderStatus: 1,
refundStatus: 1,
},
{
orderNo: 'ORD20260700007',
uniqueId: 107,
name: '2026全国青少年羽毛球锦标赛暨体育文化交流大会',
realName: '王选手',
phone: '13700137003',
categoryName: '男子中年组',
creatorName: '王报名',
creatorPhone: '13700137003',
creatorSignupCount: 1,
creatorSignupFee: '256.50',
creatorSignupFeeDiscount: '20.00',
creatorSignupFeeActual: '236.50',
creatorSignupFeeRefund: '236.50',
orderTime: '2026.07.06 03:53:25',
payTime: '2026.07.06 03:54:00',
orderStatus: 3,
refundStatus: 3,
},
{
orderNo: 'ORD20260700008',
uniqueId: 108,
name: '国际马拉松城市联赛',
realName: '赵选手',
phone: '13600136004',
categoryName: '女子中年组',
creatorName: '赵报名',
creatorPhone: '13600136004',
creatorSignupCount: 3,
creatorSignupFee: '399.00',
creatorSignupFeeDiscount: '10.00',
creatorSignupFeeActual: '389.00',
creatorSignupFeeRefund: '30.00',
orderTime: '2026.07.05 07:10:38',
payTime: '2026.07.05 07:11:00',
orderStatus: 2,
refundStatus: 2,
},
{
orderNo: 'ORD20260700009',
uniqueId: 109,
name: '全民健身运动会',
realName: '张选手',
phone: '13800138001',
categoryName: '男子青年组',
creatorName: '张报名',
creatorPhone: '13800138001',
creatorSignupCount: 2,
creatorSignupFee: '128.00',
creatorSignupFeeDiscount: '0.00',
creatorSignupFeeActual: '128.00',
creatorSignupFeeRefund: '0.00',
orderTime: '2026.07.04 10:27:51',
payTime: '2026.07.04 10:28:30',
orderStatus: 4,
refundStatus: 1,
},
{
orderNo: 'ORD20260700010',
uniqueId: 110,
name: '2026全国青少年羽毛球锦标赛暨体育文化交流大会',
realName: '李选手',
phone: '13900139002',
categoryName: '女子青年组',
creatorName: '李报名',
creatorPhone: '13900139002',
creatorSignupCount: 1,
creatorSignupFee: '520.00',
creatorSignupFeeDiscount: '20.00',
creatorSignupFeeActual: '500.00',
creatorSignupFeeRefund: '0.00',
orderTime: '2026.07.03 13:44:04',
payTime: '2026.07.03 13:45:00',
orderStatus: 1,
refundStatus: 1,
},
{
orderNo: 'ORD20260700011',
uniqueId: 111,
name: '国际马拉松城市联赛',
realName: '王选手',
phone: '13700137003',
categoryName: '男子中年组',
creatorName: '王报名',
creatorPhone: '13700137003',
creatorSignupCount: 1,
creatorSignupFee: '188.00',
creatorSignupFeeDiscount: '10.00',
creatorSignupFeeActual: '178.00',
creatorSignupFeeRefund: '50.00',
orderTime: '2026.07.02 17:01:17',
payTime: '2026.07.02 17:02:00',
orderStatus: 2,
refundStatus: 3,
},
{
orderNo: 'ORD20260700012',
uniqueId: 112,
name: '全民健身运动会',
realName: '赵选手',
phone: '13600136004',
categoryName: '女子中年组',
creatorName: '赵报名',
creatorPhone: '13600136004',
creatorSignupCount: 3,
creatorSignupFee: '256.50',
creatorSignupFeeDiscount: '0.00',
creatorSignupFeeActual: '256.50',
creatorSignupFeeRefund: '0.00',
orderTime: '2026.07.01 20:18:30',
payTime: '2026.07.01 20:19:00',
orderStatus: 1,
refundStatus: 1,
},
];
/** 金额汇总假数据(汇总接口独立,此为 mock 占位) */
export const MOCK_ORDER_SUMMARY = {
totalOrderAmount: 113255.0,
totalPaidAmount: 113255.0,
totalRefundAmount: 1255.0,
};
/** 构建分页响应 */
export function buildMockOrderListPage(
page: number,
limit: number,
): PageData<TournamentAdminOrderPageVO> {
const start = (page - 1) * limit;
const list = MOCK_ORDER_LIST.slice(start, start + limit);
return { total: MOCK_ORDER_LIST.length, list };
}
-104
View File
@@ -1,104 +0,0 @@
/**
* 订单详情 — 选手列表假数据
*
* 使用页面:`src/pages/events/orders/components/OrderDetailModal.tsx`
* 对应接口:`GET /manager/order/info/page`
* 类型:`TournamentAdminOrderInfoPageVO[]`
*/
import type { TournamentAdminOrderInfoPageVO } from '@/api/orders/types';
export const MOCK_ORDER_PLAYER_LIST: TournamentAdminOrderInfoPageVO[] = [
{
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',
],
categoryName: '男子青年组',
orderAmount: '100.00',
discountAmount: '10.00',
payAmount: '90.00',
refundAmount: '0.00',
},
{
realName: '李四',
gender: '1',
phone: '13900139002',
idCard: '110101199002022345',
idCardImg: '',
idCardImgList: [],
categoryName: '男子青年组',
orderAmount: '88.00',
discountAmount: '10.00',
payAmount: '78.00',
refundAmount: '50.00',
},
{
realName: '王芳',
gender: '2',
phone: '13700137003',
idCard: '110101199103033456',
idCardImg: 'https://picsum.photos/seed/idcard3/96/64',
idCardImgList: ['https://picsum.photos/seed/idcard3a/96/64'],
categoryName: '女子双打',
orderAmount: '0.00',
discountAmount: '0.00',
payAmount: '0.00',
refundAmount: '-',
},
{
realName: '赵六',
gender: '1',
phone: '13600136004',
idCard: '110101199204044567',
idCardImg: '',
idCardImgList: [],
categoryName: '女子双打',
orderAmount: '50.00',
discountAmount: '0.00',
payAmount: '50.00',
refundAmount: '-',
},
{
realName: '孙七',
gender: '2',
phone: '13500135005',
idCard: '110101199305055678',
idCardImg: 'https://picsum.photos/seed/idcard5/96/64',
idCardImgList: [
'https://picsum.photos/seed/idcard5a/96/64',
'https://picsum.photos/seed/idcard5b/96/64',
],
categoryName: '男子青年组',
orderAmount: '60.00',
discountAmount: '5.00',
payAmount: '55.00',
refundAmount: '0.00',
},
{
realName: '周八',
gender: '1',
phone: '13400134006',
idCard: '110101199406066789',
idCardImg: 'https://picsum.photos/seed/idcard6/96/64',
idCardImgList: ['https://picsum.photos/seed/idcard6a/96/64'],
categoryName: '男子中年组',
orderAmount: '120.00',
discountAmount: '20.00',
payAmount: '100.00',
refundAmount: '-',
},
];
export function buildMockOrderPlayerListPage(
page: number,
limit: number,
): { total: number; list: TournamentAdminOrderInfoPageVO[] } {
const start = (page - 1) * limit;
const list = MOCK_ORDER_PLAYER_LIST.slice(start, start + limit);
return { total: MOCK_ORDER_PLAYER_LIST.length, list };
}
-164
View File
@@ -1,164 +0,0 @@
/**
* 支付流水假数据
* 对应接口:GET /admin/finance/paymentFlow/page
*/
import type { PaymentFlowVO, PaymentFlowQueryParams, PaymentFlowPageData } from '@/api/payments';
const MOCK_LIST: PaymentFlowVO[] = [
{
serialNo: 'FL20260730001',
orderNo: 'OD20260730001',
type: 1,
nickname: '张三',
phone: '12345678997',
amount: '99.00',
createDate: '2026-07-30 08:00:00',
},
{
serialNo: '',
orderNo: '',
type: 2,
nickname: '李四',
phone: '12345678998',
amount: '100.00',
createDate: '2026-07-30 07:00:00',
},
{
serialNo: 'FL20260729002',
orderNo: 'OD20260729002',
type: 1,
nickname: '王五',
phone: '12345678998',
amount: '199.00',
createDate: '2026-07-29 16:00:00',
},
{
serialNo: '',
orderNo: '',
type: 2,
nickname: '赵六',
phone: '13800138000',
amount: '50.00',
createDate: '2026-07-29 15:20:00',
},
{
serialNo: 'FL20260728003',
orderNo: 'OD20260728003',
type: 1,
nickname: '钱七',
phone: '13900139001',
amount: '350.00',
createDate: '2026-07-28 14:00:00',
},
{
serialNo: '',
orderNo: '',
type: 2,
nickname: '孙八',
phone: '18600186001',
amount: '120.00',
createDate: '2026-07-28 10:30:00',
},
{
serialNo: 'FL20260727004',
orderNo: 'OD20260727004',
type: 1,
nickname: '周九',
phone: '15200152000',
amount: '88.00',
createDate: '2026-07-27 09:00:00',
},
{
serialNo: 'FL20260727005',
orderNo: 'OD20260727005',
type: 1,
nickname: '吴十',
phone: '17000170001',
amount: '520.00',
createDate: '2026-07-27 08:00:00',
},
{
serialNo: '',
orderNo: '',
type: 2,
nickname: '李四',
phone: '12345678998',
amount: '200.00',
createDate: '2026-07-26 17:00:00',
},
{
serialNo: 'FL20260726006',
orderNo: 'OD20260726006',
type: 1,
nickname: '张三',
phone: '12345678997',
amount: '128.00',
createDate: '2026-07-26 11:00:00',
},
{
serialNo: '',
orderNo: '',
type: 2,
nickname: '王五',
phone: '12345678998',
amount: '80.00',
createDate: '2026-07-25 13:00:00',
},
{
serialNo: 'FL20260725007',
orderNo: 'OD20260725007',
type: 1,
nickname: '赵六',
phone: '13800138000',
amount: '680.00',
createDate: '2026-07-25 10:00:00',
},
];
function inDateRange(date: string, begin?: string, end?: string): boolean {
if (!begin && !end) return true;
const d = date.slice(0, 10);
if (begin && d < begin) return false;
if (end && d > end) return false;
return true;
}
export function buildMockPaymentFlowPage(
page: number,
limit: number,
params: PaymentFlowQueryParams,
): PaymentFlowPageData {
let filtered = [...MOCK_LIST];
if (params.nickname) {
filtered = filtered.filter((item) => item.nickname.includes(params.nickname!));
}
if (params.phone) {
filtered = filtered.filter((item) => item.phone.includes(params.phone!));
}
if (params.type) {
filtered = filtered.filter((item) => item.type === Number(params.type));
}
filtered = filtered.filter((item) =>
inDateRange(item.createDate, params.createDateBegin, params.createDateEnd),
);
let totalAmount = 0;
let totalRefundAmount = 0;
for (const item of filtered) {
const amt = Number(item.amount) || 0;
if (item.type === 1) totalAmount += amt;
else if (item.type === 2) totalRefundAmount += amt;
}
const total = filtered.length;
const start = (page - 1) * limit;
return {
total,
list: filtered.slice(start, start + limit),
exData: {
totalAmount: totalAmount.toFixed(2),
totalRefundAmount: totalRefundAmount.toFixed(2),
},
};
}
-213
View File
@@ -1,213 +0,0 @@
/**
* 角色权限树假数据
* 对应接口:GET /admin/sys/role/permissionList?roleId=xxx
*
* - 传 roleId=0 时返回全量未选中树(新增角色用)
* - 传已有角色ID时返回带 selected 的树,模拟后端已有权限
*
* 结构对齐 PermissionTreeNode: { id, pid, name, selected, children }
* selected 仅叶节点有值(true/false),父节点为 null
*/
import type { PermissionTreeNode } from '@/api/roles';
/**
* 全量权限树(所有节点的 selected=null,叶节点 selected=false
* 作为未授权角色的默认返回
*/
const FULL_UNSELECTED: PermissionTreeNode[] = [
{
id: 1,
pid: 0,
name: '赛事管理',
selected: null,
children: [
{
id: 11,
pid: 1,
name: '赛事列表',
selected: null,
children: [
{ id: 101, pid: 11, name: '赛事列表-查看', selected: false },
{ id: 102, pid: 11, name: '赛事列表-上架', selected: false },
{ id: 103, pid: 11, name: '赛事列表-下架', selected: false },
],
},
{
id: 12,
pid: 1,
name: '赛事规程',
selected: null,
children: [
{ id: 104, pid: 12, name: '赛事规程-查看', selected: false },
{ id: 105, pid: 12, name: '赛事规程-编辑', selected: false },
],
},
{
id: 13,
pid: 1,
name: '订单管理',
selected: null,
children: [
{ id: 106, pid: 13, name: '订单管理-查看', selected: false },
{ id: 107, pid: 13, name: '订单管理-退款', selected: false },
],
},
{
id: 14,
pid: 1,
name: '用户列表',
selected: null,
children: [{ id: 108, pid: 14, name: '用户列表-查看', selected: false }],
},
{
id: 15,
pid: 1,
name: 'Banner管理',
selected: null,
children: [
{ id: 109, pid: 15, name: 'Banner-查看', selected: false },
{ id: 110, pid: 15, name: 'Banner-新增', selected: false },
{ id: 111, pid: 15, name: 'Banner-编辑', selected: false },
{ id: 112, pid: 15, name: 'Banner-删除', selected: false },
],
},
{
id: 16,
pid: 1,
name: '赛事审核',
selected: null,
children: [
{ id: 113, pid: 16, name: '赛事审核-查看', selected: false },
{ id: 114, pid: 16, name: '赛事审核-通过', selected: false },
{ id: 115, pid: 16, name: '赛事审核-驳回', selected: false },
],
},
],
},
{
id: 2,
pid: 0,
name: '账务管理',
selected: null,
children: [
{
id: 21,
pid: 2,
name: '财务报表',
selected: null,
children: [{ id: 201, pid: 21, name: '财务报表-查看', selected: false }],
},
{
id: 22,
pid: 2,
name: '提现管理',
selected: null,
children: [
{ id: 202, pid: 22, name: '提现管理-查看', selected: false },
{ id: 203, pid: 22, name: '提现管理-审核', selected: false },
],
},
{
id: 23,
pid: 2,
name: '支付流水',
selected: null,
children: [{ id: 204, pid: 23, name: '支付流水-查看', selected: false }],
},
{
id: 24,
pid: 2,
name: '钱包管理',
selected: null,
children: [{ id: 205, pid: 24, name: '钱包管理-查看', selected: false }],
},
],
},
{
id: 3,
pid: 0,
name: '系统管理',
selected: null,
children: [
{
id: 31,
pid: 3,
name: '用户管理',
selected: null,
children: [
{ id: 301, pid: 31, name: '用户管理-查看', selected: false },
{ id: 302, pid: 31, name: '用户管理-新增', selected: false },
{ id: 303, pid: 31, name: '用户管理-编辑', selected: false },
{ id: 304, pid: 31, name: '用户管理-启用/禁用', selected: false },
{ id: 305, pid: 31, name: '用户管理-重置密码', selected: false },
],
},
{
id: 32,
pid: 3,
name: '角色权限',
selected: null,
children: [
{ id: 306, pid: 32, name: '角色权限-查看', selected: false },
{ id: 307, pid: 32, name: '角色权限-新增', selected: false },
{ id: 308, pid: 32, name: '角色权限-编辑', selected: false },
{ id: 309, pid: 32, name: '角色权限-删除', selected: false },
],
},
{
id: 33,
pid: 3,
name: '操作日志',
selected: null,
children: [{ id: 310, pid: 33, name: '操作日志-查看', selected: false }],
},
],
},
];
/**
* 根据角色已有权限ID集合,深拷贝全量树并标记叶节点 selected
*/
function applySelected(tree: PermissionTreeNode[], selectedIds: Set<number>): PermissionTreeNode[] {
return tree.map((node) => {
const isLeaf = !node.children || node.children.length === 0;
if (isLeaf) {
return { ...node, selected: selectedIds.has(node.id) };
}
// 父节点:递归处理子节点,自身 selected 保持 null
return {
...node,
selected: null,
children: applySelected(node.children!, selectedIds),
};
});
}
/**
* 构建权限树假数据
* @param roleId 角色ID,0 表示新增(返回全量未选中),其他值模拟该角色已有权限
*/
export function buildMockPermissionTree(roleId: number): PermissionTreeNode[] {
// roleId=0 → 新增角色,全量未选中
if (roleId === 0) {
return JSON.parse(JSON.stringify(FULL_UNSELECTED));
}
// 模拟不同角色拥有不同权限子集
const rolePermissions: Record<number, number[]> = {
// 超级管理员:全选(所有叶节点)
1: [
101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 201, 202, 203, 204,
205, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310,
],
// 赛事管理员:只有赛事管理模块
2: [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112],
// 财务:只有账务管理模块
4: [201, 202, 203, 204, 205],
// 其他角色默认仅查看权限
};
const selectedIds = new Set<number>(rolePermissions[roleId] || [101, 201, 301, 306, 310]);
return applySelected(FULL_UNSELECTED, selectedIds);
}
-31
View File
@@ -1,31 +0,0 @@
/**
* 角色列表页假数据(类型内联,避免循环引用)
* 对应接口:GET /sys/role/page
*/
interface MockRoleVO {
roleId: number;
roleName: string;
userCount: number;
createTime: string;
}
interface MockPageData<T> {
total: number;
list: T[];
}
const MOCK_ROLES: MockRoleVO[] = [
{ roleId: 1, roleName: '超级管理员', userCount: 2, createTime: '2025-06-01 09:00:00' },
{ roleId: 2, roleName: '赛事管理员', userCount: 5, createTime: '2025-08-15 14:30:00' },
{ roleId: 3, roleName: '裁判', userCount: 8, createTime: '2025-10-10 10:15:00' },
{ roleId: 4, roleName: '财务', userCount: 3, createTime: '2026-01-20 16:00:00' },
{ roleId: 5, roleName: '运营管理员', userCount: 4, createTime: '2026-03-05 11:00:00' },
{ roleId: 6, roleName: '客服', userCount: 6, createTime: '2026-04-10 08:30:00' },
{ roleId: 7, roleName: '审核员', userCount: 3, createTime: '2026-05-18 13:45:00' },
{ roleId: 8, roleName: '内容编辑', userCount: 2, createTime: '2026-06-22 09:20:00' },
];
export function buildMockRoleListPage(page: number, limit: number): MockPageData<MockRoleVO> {
const start = (page - 1) * limit;
return { total: MOCK_ROLES.length, list: MOCK_ROLES.slice(start, start + limit) };
}
-33
View File
@@ -1,33 +0,0 @@
/**
* 角色下用户列表假数据
* 对应接口:GET /sys/role/usageUserPage
*/
interface MockUserVO {
realName: string;
phone: string;
createTime: string;
status: number;
}
interface MockPageData<T> {
total: number;
list: T[];
}
const MOCK_USERS: MockUserVO[] = [
{ realName: '张超管', phone: '13900000001', createTime: '2025-06-01 09:00:00', status: 1 },
{ realName: '李运营', phone: '15900000002', createTime: '2026-01-10 16:00:00', status: 0 },
{ realName: '王运营', phone: '15900000001', createTime: '2025-10-20 10:00:00', status: 1 },
{ realName: '张运营', phone: '13900000002', createTime: '2025-08-15 14:30:00', status: 1 },
{ realName: '赵裁判', phone: '13800000005', createTime: '2025-10-10 10:15:00', status: 1 },
{ realName: '钱裁判', phone: '13800000006', createTime: '2025-10-12 11:00:00', status: 1 },
{ realName: '孙财务', phone: '13700000007', createTime: '2026-01-20 16:00:00', status: 1 },
{ realName: '周客服', phone: '13700000008', createTime: '2026-01-21 09:30:00', status: 1 },
{ realName: '吴客服', phone: '13700000009', createTime: '2026-01-22 10:00:00', status: 0 },
{ realName: '郑客服', phone: '13700000010', createTime: '2026-01-23 14:00:00', status: 1 },
];
export function buildMockRoleUsageUserPage(page: number, limit: number): MockPageData<MockUserVO> {
const start = (page - 1) * limit;
return { total: MOCK_USERS.length, list: MOCK_USERS.slice(start, start + limit) };
}
-134
View File
@@ -1,134 +0,0 @@
/**
* 用户列表页假数据(类型内联,避免循环引用)
* 对应接口:GET /sys/user/page
*/
interface MockUserVO {
id: string;
realName: string;
phone: string;
roleId: string;
roleName: string;
status: string;
createTime: string;
}
interface MockPageData<T> {
total: number;
list: T[];
}
const MOCK_USERS: MockUserVO[] = [
{
id: '1',
realName: '张三',
phone: '13800138001',
roleId: '1',
roleName: '超级管理员',
status: '1',
createTime: '2026-07-20 08:11:07',
},
{
id: '2',
realName: '李四',
phone: '13900139002',
roleId: '2',
roleName: '赛事管理员',
status: '1',
createTime: '2026-07-19 11:28:20',
},
{
id: '3',
realName: '王五',
phone: '13700137003',
roleId: '3',
roleName: '裁判',
status: '1',
createTime: '2026-07-18 14:45:33',
},
{
id: '4',
realName: '赵六',
phone: '13600136004',
roleId: '2',
roleName: '赛事管理员',
status: '0',
createTime: '2026-07-17 18:02:46',
},
{
id: '5',
realName: '孙七',
phone: '13800138005',
roleId: '1',
roleName: '超级管理员',
status: '1',
createTime: '2026-07-16 21:19:59',
},
{
id: '6',
realName: '周八',
phone: '13900139006',
roleId: '3',
roleName: '裁判',
status: '1',
createTime: '2026-07-15 00:36:12',
},
{
id: '7',
realName: '吴九',
phone: '13700137007',
roleId: '2',
roleName: '赛事管理员',
status: '1',
createTime: '2026-07-14 03:53:25',
},
{
id: '8',
realName: '郑十',
phone: '13600136008',
roleId: '4',
roleName: '财务',
status: '1',
createTime: '2026-07-13 07:10:38',
},
{
id: '9',
realName: '陈一',
phone: '13800138009',
roleId: '3',
roleName: '裁判',
status: '1',
createTime: '2026-07-12 10:27:51',
},
{
id: '10',
realName: '刘二',
phone: '13900139010',
roleId: '2',
roleName: '赛事管理员',
status: '0',
createTime: '2026-07-11 13:44:04',
},
{
id: '11',
realName: '黄三',
phone: '13700137011',
roleId: '1',
roleName: '超级管理员',
status: '1',
createTime: '2026-07-10 17:01:17',
},
{
id: '12',
realName: '林四',
phone: '13600136012',
roleId: '4',
roleName: '财务',
status: '1',
createTime: '2026-07-09 20:18:30',
},
];
export function buildMockUserListPage(page: number, limit: number): MockPageData<MockUserVO> {
const start = (page - 1) * limit;
return { total: MOCK_USERS.length, list: MOCK_USERS.slice(start, start + limit) };
}
-143
View File
@@ -1,143 +0,0 @@
/**
* 钱包列表假数据
* 对应接口:GET /admin/finance/wallet/summary
*/
import type { WalletSummaryVO, WalletQueryParams, WalletSummaryPageData } from '@/api/wallet';
const MOCK_LIST: WalletSummaryVO[] = [
{
userId: 1001,
userNickname: '张三',
phone: '12345678997',
amount: '0.00',
frozenAmount: '0.00',
withdrawalAmount: '0.00',
},
{
userId: 1002,
userNickname: '李四',
phone: '12345678998',
amount: '350.00',
frozenAmount: '200.00',
withdrawalAmount: '1450.00',
},
{
userId: 1003,
userNickname: '王五',
phone: '12345678999',
amount: '1280.00',
frozenAmount: '0.00',
withdrawalAmount: '800.00',
},
{
userId: 1004,
userNickname: '赵六',
phone: '13800138000',
amount: '560.00',
frozenAmount: '100.00',
withdrawalAmount: '200.00',
},
{
userId: 1005,
userNickname: '钱七',
phone: '13900139001',
amount: '4200.00',
frozenAmount: '1500.00',
withdrawalAmount: '5600.00',
},
{
userId: 1006,
userNickname: '孙八',
phone: '12345678997',
amount: '880.00',
frozenAmount: '0.00',
withdrawalAmount: '300.00',
},
{
userId: 1007,
userNickname: '周九',
phone: '17000170001',
amount: '2100.00',
frozenAmount: '800.00',
withdrawalAmount: '3200.00',
},
{
userId: 1008,
userNickname: '吴十',
phone: '18900189000',
amount: '650.00',
frozenAmount: '0.00',
withdrawalAmount: '450.00',
},
{
userId: 1009,
userNickname: '郑十一',
phone: '15200152000',
amount: '980.00',
frozenAmount: '200.00',
withdrawalAmount: '1200.00',
},
{
userId: 1010,
userNickname: '冯十二',
phone: '18600186001',
amount: '320.00',
frozenAmount: '50.00',
withdrawalAmount: '100.00',
},
{
userId: 1011,
userNickname: '褚十三',
phone: '13500135000',
amount: '5800.00',
frozenAmount: '0.00',
withdrawalAmount: '2000.00',
},
{
userId: 1012,
userNickname: '卫十四',
phone: '13700137000',
amount: '75.00',
frozenAmount: '0.00',
withdrawalAmount: '75.00',
},
];
function computeExData(list: WalletSummaryVO[]) {
const totalAmount = list.reduce((s, i) => s + Number(i.amount || 0), 0).toFixed(2);
const totalFrozenAmount = list.reduce((s, i) => s + Number(i.frozenAmount || 0), 0).toFixed(2);
const totalWithdrawalAmount = list
.reduce((s, i) => s + Number(i.withdrawalAmount || 0), 0)
.toFixed(2);
return { totalAmount, totalFrozenAmount, totalWithdrawalAmount };
}
export function buildMockWalletPage(
page: number,
limit: number,
params: WalletQueryParams,
): WalletSummaryPageData {
let filtered = [...MOCK_LIST];
if (params.nickname) {
filtered = filtered.filter((item) => item.userNickname.includes(params.nickname!));
}
if (params.phone) {
filtered = filtered.filter((item) => item.phone.includes(params.phone!));
}
if (params.amountBegin) {
filtered = filtered.filter((item) => Number(item.amount) >= Number(params.amountBegin));
}
if (params.amountEnd) {
filtered = filtered.filter((item) => Number(item.amount) <= Number(params.amountEnd));
}
const total = filtered.length;
const start = (page - 1) * limit;
return {
total,
list: filtered.slice(start, start + limit),
exData: computeExData(filtered),
};
}
-187
View File
@@ -1,187 +0,0 @@
/**
* 钱包交易明细假数据
* 对应接口:GET /admin/finance/wallet/page
*
* type: 1=报名入账, 2=提现出账, 3=退款出账
* status(由 freezeAmount/freezeTime 推导): 1=已解冻, 2=冻结中, 3=正常
*/
import type { WalletTransactionVO, WalletTransactionQueryParams, PageData } from '@/api/wallet';
const MOCK_TRANSACTIONS: (WalletTransactionVO & { status: number })[] = [
{
type: 1,
amount: '+100.00',
afterAmount: '600.00',
withdrawAmount: '500.00',
freezeAmount: '100.00',
status: 1,
freezeTime: '2026-06-10 08:00',
unfreezeTime: '2026-06-12 08:00',
orderNo: 'SN20260610001',
createDate: '2026-06-10 08:00',
},
{
type: 2,
amount: '-500.00',
afterAmount: '1100.00',
withdrawAmount: '500.00',
freezeAmount: '600.00',
status: 2,
freezeTime: '2026-06-10 06:00',
unfreezeTime: '',
orderNo: 'SN20260610002',
createDate: '2026-06-10 06:00',
},
{
type: 3,
amount: '-100.00',
afterAmount: '500.00',
withdrawAmount: '500.00',
freezeAmount: '0.00',
status: 3,
freezeTime: '',
unfreezeTime: '',
orderNo: 'SN20260610003',
createDate: '2026-06-10 05:00',
},
{
type: 1,
amount: '+350.00',
afterAmount: '850.00',
withdrawAmount: '750.00',
freezeAmount: '100.00',
status: 3,
freezeTime: '',
unfreezeTime: '',
orderNo: 'SN20260609001',
createDate: '2026-06-09 17:30',
},
{
type: 2,
amount: '-250.00',
afterAmount: '600.00',
withdrawAmount: '500.00',
freezeAmount: '100.00',
status: 1,
freezeTime: '2026-06-08 10:00',
unfreezeTime: '2026-06-09 08:00',
orderNo: 'SN20260608001',
createDate: '2026-06-08 10:00',
},
{
type: 1,
amount: '+800.00',
afterAmount: '2000.00',
withdrawAmount: '1800.00',
freezeAmount: '200.00',
status: 3,
freezeTime: '',
unfreezeTime: '',
orderNo: 'SN20260607001',
createDate: '2026-06-07 14:00',
},
{
type: 2,
amount: '-300.00',
afterAmount: '1200.00',
withdrawAmount: '1000.00',
freezeAmount: '200.00',
status: 2,
freezeTime: '2026-06-06 09:00',
unfreezeTime: '',
orderNo: 'SN20260606001',
createDate: '2026-06-06 09:00',
},
{
type: 3,
amount: '-50.00',
afterAmount: '1500.00',
withdrawAmount: '1300.00',
freezeAmount: '200.00',
status: 1,
freezeTime: '2026-06-05 10:00',
unfreezeTime: '2026-06-07 10:00',
orderNo: 'SN20260605001',
createDate: '2026-06-05 10:00',
},
{
type: 1,
amount: '+200.00',
afterAmount: '1550.00',
withdrawAmount: '1350.00',
freezeAmount: '200.00',
status: 3,
freezeTime: '',
unfreezeTime: '',
orderNo: 'SN20260604001',
createDate: '2026-06-04 08:30',
},
{
type: 2,
amount: '-600.00',
afterAmount: '950.00',
withdrawAmount: '750.00',
freezeAmount: '200.00',
status: 1,
freezeTime: '2026-06-03 16:00',
unfreezeTime: '2026-06-05 16:00',
orderNo: 'SN20260603001',
createDate: '2026-06-03 16:00',
},
{
type: 1,
amount: '+450.00',
afterAmount: '1050.00',
withdrawAmount: '850.00',
freezeAmount: '200.00',
status: 1,
freezeTime: '2026-06-02 11:00',
unfreezeTime: '2026-06-04 11:00',
orderNo: 'SN20260602001',
createDate: '2026-06-02 11:00',
},
{
type: 3,
amount: '-120.00',
afterAmount: '600.00',
withdrawAmount: '400.00',
freezeAmount: '200.00',
status: 3,
freezeTime: '',
unfreezeTime: '',
orderNo: 'SN20260601001',
createDate: '2026-06-01 15:00',
},
];
function inDateRange(date: string, begin?: string, end?: string): boolean {
if (!begin && !end) return true;
const d = date.slice(0, 10);
if (begin && d < begin) return false;
if (end && d > end) return false;
return true;
}
export function buildMockWalletTransactionPage(
page: number,
limit: number,
params: WalletTransactionQueryParams,
): PageData<WalletTransactionVO> {
let filtered = [...MOCK_TRANSACTIONS];
if (params.type) {
filtered = filtered.filter((item) => item.type === Number(params.type));
}
if (params.status) {
filtered = filtered.filter((item) => item.status === Number(params.status));
}
filtered = filtered.filter((item) =>
inDateRange(item.createDate, params.createDateBegin, params.createDateEnd),
);
const total = filtered.length;
const start = (page - 1) * limit;
const list = filtered.slice(start, start + limit).map(({ status, ...rest }) => rest);
return { total, list };
}
-150
View File
@@ -1,150 +0,0 @@
/**
* 提现详情假数据
* 对应接口:GET /admin/finance/withdraw/info?id=xxx
*
* 字段对齐 TournamentFinanceWithdrawInfoVO。
*/
import type { WithdrawInfoVO } from '@/api/withdraw';
/** 银行卡类型映射 */
const BANK_CARD_TYPE_MAP: Record<number, string> = { 1: '个人' };
const MOCK_DETAILS: Record<number, WithdrawInfoVO> = {
1: {
nickname: '可乐',
phone: '17762466262',
realName: '可乐',
createDate: '2026-07-30 09:53:00',
withdrawType: '银行卡',
withdrawAmount: '1000.00',
feeRate: '0.006',
feeAmount: '6.00',
receivedAmount: '994.00',
auditStatus: 0,
payStatus: 0,
bankCardType: 1,
cardHolderName: '龚',
bankCardNumber: '6215581807006475126',
bankName: '交通银行',
branchName: '交通银行宜昌西坝支行',
payTime: '',
orderNo: 'WD04111775872421935100',
auditDate: '',
auditNickname: '',
auditImg: '',
auditImgList: [],
},
2: {
nickname: '李四',
phone: '12345678998',
realName: '李四',
createDate: '2026-07-29 14:30:00',
withdrawType: '银行卡',
withdrawAmount: '200.00',
feeRate: '0.006',
feeAmount: '1.20',
receivedAmount: '198.80',
auditStatus: 1,
payStatus: 1,
bankCardType: 1,
cardHolderName: '李四',
bankCardNumber: '6222021234567890123',
bankName: '工商银行',
branchName: '工商银行北京中关村支行',
payTime: '2026-07-29 14:35:00',
orderNo: 'SN20260729001',
auditDate: '2026-07-29 14:32:00',
auditNickname: '管理员A',
auditImg: '',
auditImgList: [
'https://via.placeholder.com/200x200?text=Audit1',
'https://via.placeholder.com/200x200?text=Audit2',
],
},
3: {
nickname: '王五',
phone: '13800138000',
realName: '王五',
createDate: '2026-07-28 11:15:00',
withdrawType: '银行卡',
withdrawAmount: '500.00',
feeRate: '0.006',
feeAmount: '3.00',
receivedAmount: '497.00',
auditStatus: 1,
payStatus: 2,
bankCardType: 1,
cardHolderName: '王五',
bankCardNumber: '微信钱包',
bankName: '微信支付',
branchName: '-',
payTime: '',
orderNo: 'SN20260728002',
auditDate: '2026-07-28 11:20:00',
auditNickname: '管理员B',
auditImg: '',
auditImgList: [],
},
4: {
nickname: '赵六',
phone: '13900139001',
realName: '赵六',
createDate: '2026-07-27 09:45:00',
withdrawType: '银行卡',
withdrawAmount: '80.00',
feeRate: '0.006',
feeAmount: '0.48',
receivedAmount: '79.52',
auditStatus: 2,
payStatus: 0,
bankCardType: 1,
cardHolderName: '赵六',
bankCardNumber: '6217858000123456789',
bankName: '建设银行',
branchName: '建设银行上海陆家嘴支行',
payTime: '',
orderNo: '',
auditDate: '2026-07-27 10:00:00',
auditNickname: '管理A',
auditImg: '',
auditImgList: ['https://via.placeholder.com/200x200?text=Reject'],
},
5: {
nickname: '孙七',
phone: '18600186001',
realName: '孙七',
createDate: '2026-07-26 16:00:00',
withdrawType: '银行卡',
withdrawAmount: '3000.00',
feeRate: '0.006',
feeAmount: '18.00',
receivedAmount: '2982.00',
auditStatus: 0,
payStatus: 0,
bankCardType: 1,
cardHolderName: '孙七',
bankCardNumber: '6228480012345678910',
bankName: '农业银行',
branchName: '农业银行深圳福田支行',
payTime: '',
orderNo: 'WD05261775872421935200',
auditDate: '',
auditNickname: '',
auditImg: '',
auditImgList: [],
},
};
export function buildMockWithdrawInfo(id: number): WithdrawInfoVO {
const detail = MOCK_DETAILS[id];
if (detail) return { ...detail };
// 找不到对应 ID 时返回一个默认详情(基于 ID 4 的结构)
return {
...MOCK_DETAILS[4],
nickname: `用户${id}`,
phone: `1380000${String(id).padStart(4, '0')}`,
realName: `用户${id}`,
createDate: '2026-07-27 09:45:00',
};
}
-194
View File
@@ -1,194 +0,0 @@
/**
* 提现列表假数据
* 对应接口:GET /admin/finance/withdraw/page
*
* 前端筛选 + 分页,模拟后端行为。
*/
import type { WithdrawVO, WithdrawQueryParams, PageData } from '@/api/withdraw';
const MOCK_LIST: WithdrawVO[] = [
{
id: 1,
nickname: '可乐',
phone: '17762466262',
realName: '可乐',
withdrawType: '银行卡',
withdrawAmount: '1000.00',
receivedAmount: '994.00',
auditStatus: 0,
payStatus: 0,
serialNumber: '',
createDate: '2026-07-30 09:53:00',
},
{
id: 2,
nickname: '李四',
phone: '12345678998',
realName: '李四',
withdrawType: '银行卡',
withdrawAmount: '200.00',
receivedAmount: '198.80',
auditStatus: 1,
payStatus: 1,
serialNumber: 'SN20260729001',
createDate: '2026-07-29 14:30:00',
},
{
id: 3,
nickname: '王五',
phone: '13800138000',
realName: '王五',
withdrawType: '银行卡',
withdrawAmount: '500.00',
receivedAmount: '497.00',
auditStatus: 1,
payStatus: 2,
serialNumber: 'SN20260728002',
createDate: '2026-07-28 11:15:00',
},
{
id: 4,
nickname: '赵六',
phone: '13900139001',
realName: '赵六',
withdrawType: '银行卡',
withdrawAmount: '80.00',
receivedAmount: '79.52',
auditStatus: 2,
payStatus: 0,
serialNumber: '',
createDate: '2026-07-27 09:45:00',
},
{
id: 5,
nickname: '孙七',
phone: '18600186001',
realName: '孙七',
withdrawType: '银行卡',
withdrawAmount: '3000.00',
receivedAmount: '2982.00',
auditStatus: 0,
payStatus: 0,
serialNumber: '',
createDate: '2026-07-26 16:00:00',
},
{
id: 6,
nickname: '周八',
phone: '15200152000',
realName: '周八',
withdrawType: '银行卡',
withdrawAmount: '150.00',
receivedAmount: '149.10',
auditStatus: 1,
payStatus: 1,
serialNumber: 'SN20260725003',
createDate: '2026-07-25 10:20:00',
},
{
id: 7,
nickname: '吴九',
phone: '17000170001',
realName: '吴九',
withdrawType: '银行卡',
withdrawAmount: '600.00',
receivedAmount: '596.40',
auditStatus: 2,
payStatus: 0,
serialNumber: '',
createDate: '2026-07-24 08:30:00',
},
{
id: 8,
nickname: '郑十',
phone: '18900189000',
realName: '郑十',
withdrawType: '银行卡',
withdrawAmount: '2200.00',
receivedAmount: '2186.80',
auditStatus: 0,
payStatus: 0,
serialNumber: '',
createDate: '2026-07-23 13:10:00',
},
{
id: 9,
nickname: '可乐',
phone: '17762466262',
realName: '可乐',
withdrawType: '银行卡',
withdrawAmount: '450.00',
receivedAmount: '447.30',
auditStatus: 1,
payStatus: 2,
serialNumber: 'SN20260722004',
createDate: '2026-07-22 15:45:00',
},
{
id: 10,
nickname: '李四',
phone: '12345678998',
realName: '李四',
withdrawType: '银行卡',
withdrawAmount: '1800.00',
receivedAmount: '1789.20',
auditStatus: 0,
payStatus: 0,
serialNumber: '',
createDate: '2026-07-21 09:00:00',
},
{
id: 11,
nickname: '王五',
phone: '13800138000',
realName: '王五',
withdrawType: '银行卡',
withdrawAmount: '320.00',
receivedAmount: '318.08',
auditStatus: 1,
payStatus: 1,
serialNumber: 'SN20260720005',
createDate: '2026-07-20 17:30:00',
},
{
id: 12,
nickname: '孙七',
phone: '18600186001',
realName: '孙七',
withdrawType: '银行卡',
withdrawAmount: '750.00',
receivedAmount: '745.50',
auditStatus: 2,
payStatus: 0,
serialNumber: '',
createDate: '2026-07-19 11:00:00',
},
];
export function buildMockWithdrawPage(
page: number,
limit: number,
params: WithdrawQueryParams,
): PageData<WithdrawVO> {
let filtered = [...MOCK_LIST];
if (params.nickname) {
filtered = filtered.filter((item) => item.nickname.includes(params.nickname!));
}
if (params.phone) {
filtered = filtered.filter((item) => item.phone.includes(params.phone!));
}
if (params.realName) {
filtered = filtered.filter((item) => item.realName.includes(params.realName!));
}
if (params.auditStatus) {
filtered = filtered.filter((item) => item.auditStatus === Number(params.auditStatus));
}
if (params.payStatus) {
filtered = filtered.filter((item) => item.payStatus === Number(params.payStatus));
}
const total = filtered.length;
const start = (page - 1) * limit;
return { total, list: filtered.slice(start, start + limit) };
}
@@ -13,10 +13,25 @@ import {
Spin, Spin,
} from 'ant-design-vue'; } from 'ant-design-vue';
import { JUMP_TYPE_OPTIONS } from '../model/useBannerModel'; import { JUMP_TYPE_OPTIONS } from '../model/useBannerModel';
import {
TITLE_MAX,
PATH_MAX,
URL_MAX,
titleRules,
imageUrlRules,
typeRules,
linkUrlRules,
miniProgramPathRules,
areaRules,
startTimeRules,
endTimeRules,
sortRules,
} from '../model/form';
import { getProvinceCityCascaderOptions, cityNameToCascaderPath } from '@/utils/areaData'; import { getProvinceCityCascaderOptions, cityNameToCascaderPath } from '@/utils/areaData';
import { ImageCropper } from '@yp-component/root'; import { ImageCropper } from '@yp-component/root';
import { FileImageOutlined } from '@ant-design/icons-vue'; import { FileImageOutlined } from '@ant-design/icons-vue';
import { uploadFile, OssUploadType } from '@/utils/oss'; import { uploadFile, OssUploadType } from '@/utils/oss';
import { isImageFile } from '@/utils/form';
import { getEventList, getEventDetail, type TournamentAdminVO } from '../../list/model/services'; import { getEventList, getEventDetail, type TournamentAdminVO } from '../../list/model/services';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import styles from './BannerFormModal.module.less'; import styles from './BannerFormModal.module.less';
@@ -40,11 +55,6 @@ const getDefaultForm = () => ({
sort: 1, sort: 1,
}); });
/** 表单字段最大长度限制 */
const TITLE_MAX = 50;
const PATH_MAX = 200;
const URL_MAX = 500;
/** 图片上传限制:最大 10MB */ /** 图片上传限制:最大 10MB */
const IMAGE_MAX_SIZE = 10 * 1024 * 1024; const IMAGE_MAX_SIZE = 10 * 1024 * 1024;
@@ -351,7 +361,7 @@ export default defineComponent({
target.value = ''; target.value = '';
if (!file) return; if (!file) return;
if (!/^image\//.test(file.type)) { if (!isImageFile(file)) {
message.error('请选择图片文件'); message.error('请选择图片文件');
return; return;
} }
@@ -468,14 +478,7 @@ export default defineComponent({
requiredMark requiredMark
> >
{/* ===== Banner 标题 ===== */} {/* ===== Banner 标题 ===== */}
<Form.Item <Form.Item label="Banner标题" name="title" rules={titleRules}>
label="Banner标题"
name="title"
rules={[
{ required: true, message: '请输入' },
{ max: TITLE_MAX, message: `标题最多 ${TITLE_MAX} 个字符` },
]}
>
<Input <Input
placeholder="请输入标题" placeholder="请输入标题"
maxlength={TITLE_MAX} maxlength={TITLE_MAX}
@@ -488,23 +491,10 @@ export default defineComponent({
<Form.Item <Form.Item
label="上传图片" label="上传图片"
name="imageUrl" name="imageUrl"
rules={[ rules={imageUrlRules(
{ required: true, message: '请上传 Banner 图片' }, () => formData.imageStatus,
{ () => formData.imageUrl,
validator: async () => { )}
if (formData.imageStatus === 'uploading') {
return Promise.reject(new Error('图片仍在上传中,请稍候'));
}
if (
formData.imageUrl?.startsWith('blob:') ||
formData.imageUrl?.startsWith('data:')
) {
return Promise.reject(new Error('请等待图片上传完成'));
}
return Promise.resolve();
},
},
]}
> >
<div class={styles.uploader}> <div class={styles.uploader}>
<input <input
@@ -549,11 +539,7 @@ export default defineComponent({
</Form.Item> </Form.Item>
{/* ===== 跳转类型 ===== */} {/* ===== 跳转类型 ===== */}
<Form.Item <Form.Item label="跳转类型" name="type" rules={typeRules}>
label="跳转类型"
name="type"
rules={[{ required: true, message: '请选择跳转类型' }]}
>
<Select <Select
options={JUMP_TYPE_OPTIONS as any} options={JUMP_TYPE_OPTIONS as any}
placeholder="请选择" placeholder="请选择"
@@ -584,14 +570,7 @@ export default defineComponent({
{/* ===== 联动字段:自定义链接 ===== */} {/* ===== 联动字段:自定义链接 ===== */}
{formData.type === '2' && ( {formData.type === '2' && (
<Form.Item <Form.Item label="跳转地址" name="linkUrl" rules={linkUrlRules}>
label="跳转地址"
name="linkUrl"
rules={[
{ required: true, message: '请输入跳转地址' },
{ max: URL_MAX, message: `链接最多 ${URL_MAX} 个字符` },
]}
>
<Input <Input
placeholder="https://xxx" placeholder="https://xxx"
maxlength={URL_MAX} maxlength={URL_MAX}
@@ -603,14 +582,7 @@ export default defineComponent({
{/* ===== 联动字段:小程序页面 ===== */} {/* ===== 联动字段:小程序页面 ===== */}
{formData.type === '3' && ( {formData.type === '3' && (
<Form.Item <Form.Item label="小程序路径" name="linkUrl" rules={miniProgramPathRules}>
label="小程序路径"
name="linkUrl"
rules={[
{ required: true, message: '请输入小程序页面路径' },
{ max: PATH_MAX, message: `路径最多 ${PATH_MAX} 个字符` },
]}
>
<Input <Input
placeholder="/pages/index/index" placeholder="/pages/index/index"
maxlength={PATH_MAX} maxlength={PATH_MAX}
@@ -621,11 +593,7 @@ export default defineComponent({
)} )}
{/* ===== 所属区域(省市树种选择 + 全国) ===== */} {/* ===== 所属区域(省市树种选择 + 全国) ===== */}
<Form.Item <Form.Item label="所属区域" name="area" rules={areaRules}>
label="所属区域"
name="area"
rules={[{ required: true, message: '请选择所属区域' }]}
>
<Cascader <Cascader
v-model:value={areaPath.value} v-model:value={areaPath.value}
options={areaCascaderOptions} options={areaCascaderOptions}
@@ -641,16 +609,7 @@ export default defineComponent({
label="开始时间" label="开始时间"
name="displayStartTime" name="displayStartTime"
class={styles.timeItem} class={styles.timeItem}
rules={[ rules={startTimeRules(() => formData.displayTimeRange)}
{
validator: async () => {
if (!formData.displayTimeRange?.[0]) {
return Promise.reject(new Error('请选择开始时间'));
}
return Promise.resolve();
},
},
]}
> >
<DatePicker <DatePicker
showTime showTime
@@ -679,16 +638,7 @@ export default defineComponent({
label="结束时间" label="结束时间"
name="displayEndTime" name="displayEndTime"
class={styles.timeItem} class={styles.timeItem}
rules={[ rules={endTimeRules(() => formData.displayTimeRange)}
{
validator: async () => {
if (!formData.displayTimeRange?.[1]) {
return Promise.reject(new Error('请选择结束时间'));
}
return Promise.resolve();
},
},
]}
> >
<DatePicker <DatePicker
showTime showTime
@@ -715,14 +665,7 @@ export default defineComponent({
</div> </div>
{/* ===== 排序 ===== */} {/* ===== 排序 ===== */}
<Form.Item <Form.Item label="排序" name="sort" rules={sortRules}>
label="排序"
name="sort"
rules={[
{ required: true, message: '请输入排序' },
{ type: 'number', min: 1, max: 999, message: '排序范围 1-999' },
]}
>
<InputNumber <InputNumber
placeholder="数字越小越靠前" placeholder="数字越小越靠前"
min={1} min={1}
+114
View File
@@ -0,0 +1,114 @@
/**
* Banner 表单域控制器
*
* 集中管理 Banner 新增/编辑弹窗的所有表单规则校验与常量。
* 各规则数组 / 工厂函数可直接传给 antd Form.Item 的 rules prop。
*/
import type { Rule } from 'ant-design-vue/es/form';
// ============================================================
// 常量
// ============================================================
/** 标题最大长度 */
export const TITLE_MAX = 50;
/** 小程序路径最大长度 */
export const PATH_MAX = 200;
/** 跳转链接最大长度 */
export const URL_MAX = 500;
// ============================================================
// 表单规则
// ============================================================
/** Banner 标题:必填 + 50 字符上限 */
export const titleRules: Rule[] = [
{ required: true, message: '请输入' },
{ max: TITLE_MAX, message: `标题最多 ${TITLE_MAX} 个字符` },
];
/**
* Banner 图片:必填 + 上传状态校验
*
* @param getImageStatus 获取当前上传状态的 getter
* @param getImageUrl 获取当前图片 URL 的 getter
*/
export function imageUrlRules(getImageStatus: () => string, getImageUrl: () => string): Rule[] {
return [
{ required: true, message: '请上传 Banner 图片' },
{
validator: async () => {
if (getImageStatus() === 'uploading') {
return Promise.reject(new Error('图片仍在上传中,请稍候'));
}
const url = getImageUrl();
if (url.startsWith('blob:') || url.startsWith('data:')) {
return Promise.reject(new Error('请等待图片上传完成'));
}
return Promise.resolve();
},
},
];
}
/** 跳转类型:必选 */
export const typeRules: Rule[] = [{ required: true, message: '请选择跳转类型' }];
/** 跳转地址(自定义链接 scene=2):必填 + 最大长度 */
export const linkUrlRules: Rule[] = [
{ required: true, message: '请输入跳转地址' },
{ max: URL_MAX, message: `链接最多 ${URL_MAX} 个字符` },
];
/** 小程序页面路径(scene=3):必填 + 最大长度 */
export const miniProgramPathRules: Rule[] = [
{ required: true, message: '请输入小程序页面路径' },
{ max: PATH_MAX, message: `路径最多 ${PATH_MAX} 个字符` },
];
/** 所属区域:必选 */
export const areaRules: Rule[] = [{ required: true, message: '请选择所属区域' }];
/** 排序:必填 + 1-999 整数 */
export const sortRules: Rule[] = [
{ required: true, message: '请输入排序' },
{ type: 'number', min: 1, max: 999, message: '排序范围 1-999' },
];
/**
* 展示开始时间规则:校验 displayTimeRange[0] 非空
*
* @param getDisplayTimeRange 获取展示时间范围的 getter
*/
export function startTimeRules(getDisplayTimeRange: () => [string, string] | null): Rule[] {
return [
{
validator: async () => {
const range = getDisplayTimeRange();
if (!range?.[0]) {
return Promise.reject(new Error('请选择开始时间'));
}
return Promise.resolve();
},
},
];
}
/**
* 展示结束时间规则:校验 displayTimeRange[1] 非空
*
* @param getDisplayTimeRange 获取展示时间范围的 getter
*/
export function endTimeRules(getDisplayTimeRange: () => [string, string] | null): Rule[] {
return [
{
validator: async () => {
const range = getDisplayTimeRange();
if (!range?.[1]) {
return Promise.reject(new Error('请选择结束时间'));
}
return Promise.resolve();
},
},
];
}
@@ -3,8 +3,6 @@
* 该页面使用的所有接口集中管理 * 该页面使用的所有接口集中管理
*/ */
import { get, post } from '@/utils/request'; import { get, post } from '@/utils/request';
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
import { MOCK_BANNER_LIST } from '@/config/mock/bannerList';
import type { import type {
BannerListQueryParams, BannerListQueryParams,
BannerSaveParams, BannerSaveParams,
@@ -32,8 +30,6 @@ const bannerSave = '/admin/manager/banner/save';
const bannerUpdate = '/admin/manager/banner/update'; const bannerUpdate = '/admin/manager/banner/update';
const bannerActive = '/admin/manager/banner/active'; const bannerActive = '/admin/manager/banner/active';
const delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
// ============================================================ // ============================================================
// API 函数 // API 函数
// ============================================================ // ============================================================
@@ -42,10 +38,6 @@ const delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve,
export async function getBannerList( export async function getBannerList(
params: BannerListQueryParams, params: BannerListQueryParams,
): Promise<ApiResult<TournamentAdminBannerListVO[]>> { ): Promise<ApiResult<TournamentAdminBannerListVO[]>> {
if (USE_MOCK) {
await delay(MOCK_DELAY);
return { code: 200, msg: 'success', data: MOCK_BANNER_LIST };
}
return get(bannerList, params as Record<string, any>); return get(bannerList, params as Record<string, any>);
} }
@@ -1,5 +1,8 @@
import { defineComponent, reactive } from 'vue'; import { defineComponent, reactive, ref } from 'vue';
import { Modal, Descriptions, Image, Radio, Input, Form } from 'ant-design-vue'; import { Modal, Descriptions, Image, Radio, Input, Form, Spin, message } from 'ant-design-vue';
import { useEffect, useState } from '@/hooks';
import { getEventDetail, type TournamentAdminInfoVO } from '../model/services';
import { validateEventAuditSubmit } from '../model/form';
import styles from './EventAuditModal.module.less'; import styles from './EventAuditModal.module.less';
const { TextArea } = Input; const { TextArea } = Input;
@@ -11,31 +14,6 @@ interface EventAuditModalProps {
onSubmit?: (payload: { approved: boolean; comment: string }) => void; onSubmit?: (payload: { approved: boolean; comment: string }) => 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',
};
/** /**
* 赛事审核弹窗 * 赛事审核弹窗
* 包含:基础信息 + 封面 + 审核操作(通过/未通过 + 审核内容) * 包含:基础信息 + 封面 + 审核操作(通过/未通过 + 审核内容)
@@ -49,7 +27,8 @@ export default defineComponent({
onSubmit: { type: Function, default: undefined }, onSubmit: { type: Function, default: undefined },
}, },
setup(props: EventAuditModalProps) { setup(props: EventAuditModalProps) {
const detail = MOCK_DETAIL; const detail = ref<TournamentAdminInfoVO | null>(null);
const [loading, setLoading] = useState(false);
// 审核表单 // 审核表单
const auditForm = reactive({ const auditForm = reactive({
@@ -57,18 +36,145 @@ export default defineComponent({
comment: '', comment: '',
}); });
const handleOk = () => { /** 加载赛事详情 */
if (!auditForm.comment.trim()) { const fetchDetail = async (id: string) => {
// 不通过时强制要求填写审核内容 setLoading(true);
if (!auditForm.approved) { detail.value = null;
// 这里交给校验,简化版本不做强制校验 try {
const res = await getEventDetail(id);
if (res.code == 200) {
detail.value = res.data;
} else {
message.error(res.msg || '获取赛事详情失败');
} }
} catch {
// 网络层已统一提示
} finally {
setLoading(false);
}
};
/** 弹窗打开时重新拉取详情 */
useEffect(() => {
if (props.visible && props.record?.id) {
auditForm.approved = true;
auditForm.comment = '';
fetchDetail(props.record.id);
}
}, [() => props.visible]);
const handleOk = () => {
const check = validateEventAuditSubmit({
approved: auditForm.approved,
comment: auditForm.comment,
});
if (!check.valid) {
message.warning(check.error!);
return;
} }
if (props.onSubmit) { if (props.onSubmit) {
props.onSubmit({ approved: auditForm.approved, comment: auditForm.comment }); props.onSubmit({ approved: auditForm.approved, comment: auditForm.comment });
} }
}; };
/** 格式化时间范围 */
const formatRange = (start: string, end: string) => {
if (!start && !end) return '-';
return `${start || '?'} ~ ${end || '?'}`;
};
/** 渲染 0/1 开关文本 */
const boolText = (val: string, trueText: string, falseText: string) => {
return val === '1' ? trueText : falseText;
};
/** 渲染详情内容 */
const renderContent = () => {
const d = detail.value!;
return (
<>
{/* ===== 基础信息 ===== */}
<div class={styles.section}>
<div class={styles.sectionTitle}></div>
<Descriptions size="small" bordered column={3} class={styles.desc}>
<Descriptions.Item label="赛事名称">{d.name || '-'}</Descriptions.Item>
<Descriptions.Item label="赛事时间" span={2}>
{formatRange(d.startTimeBegin, d.startTimeEnd)}
</Descriptions.Item>
<Descriptions.Item label="赛事说明" span={3}>
{d.descr || '-'}
</Descriptions.Item>
<Descriptions.Item label="报名起止时间">
{formatRange(d.signupTimeBegin, d.signupTimeEnd)}
</Descriptions.Item>
<Descriptions.Item label="取消报名截止时间" span={2}>
{d.signupTimeEnd || '-'}
</Descriptions.Item>
<Descriptions.Item label="省市区">{d.venueArea || '-'}</Descriptions.Item>
<Descriptions.Item label="地点">{d.venue || '-'}</Descriptions.Item>
<Descriptions.Item label="场地编号">{d.venueNo || '-'}</Descriptions.Item>
<Descriptions.Item label="公开活动">
{boolText(d.isPublic, '在首页展示赛事', '非公开')}
</Descriptions.Item>
<Descriptions.Item label="证件号">
{boolText(d.isRequireIdCardNo, '需要提供', '无需提供')}
</Descriptions.Item>
<Descriptions.Item label="证件图片">
{boolText(d.isRequireIdCardImg, '需要提供', '无需提供')}
</Descriptions.Item>
<Descriptions.Item label="创建时间">{d.createTime || '-'}</Descriptions.Item>
<Descriptions.Item label="创建人">{d.createNickname || '-'}</Descriptions.Item>
<Descriptions.Item label="状态"></Descriptions.Item>
<Descriptions.Item label="报名人数">{d.signupCount || '0'}</Descriptions.Item>
<Descriptions.Item label="取消报名人数" span={1}>
{d.dropoutCount || '0'}
</Descriptions.Item>
<Descriptions.Item label="最大人数">{d.dtotalLimit || '-'}</Descriptions.Item>
<Descriptions.Item label="对岸数量">{d.matchTotalCount || '0'}</Descriptions.Item>
<Descriptions.Item label="已完成对岸数量">{d.matchCount || '0'}</Descriptions.Item>
</Descriptions>
</div>
{/* ===== 封面 ===== */}
<div class={styles.section}>
<div class={styles.sectionTitle}></div>
{d.img ? <Image src={d.img} /> : <span style={{ color: '#999' }}></span>}
</div>
{/* ===== 审核操作 ===== */}
<div class={styles.auditSection}>
<div class={styles.auditHeader}>
<span class={styles.auditTitle}></span>
</div>
<Form layout="horizontal" class={styles.auditForm}>
<Form.Item label="审核状态" class={styles.auditField}>
<Radio.Group v-model={auditForm.approved}>
<Radio value={true}></Radio>
<Radio value={false}></Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="审核内容" class={styles.auditField}>
<TextArea
v-model={auditForm.comment}
placeholder="请输入审核内容"
rows={4}
maxlength={500}
showCount
/>
</Form.Item>
</Form>
</div>
</>
);
};
return () => ( return () => (
<Modal <Modal
visible={props.visible} visible={props.visible}
@@ -82,75 +188,13 @@ export default defineComponent({
destroyOnClose destroyOnClose
wrapClassName={styles.eventAuditModalMain} wrapClassName={styles.eventAuditModalMain}
> >
{/* ===== 基础信息 ===== */} {loading.value ? (
<div class={styles.section}> <div style={{ textAlign: 'center', padding: '80px 0' }}>
<div class={styles.sectionTitle}></div> <Spin size="large" />
<Descriptions size="small" bordered column={3} class={styles.desc}>
<Descriptions.Item label="赛事名称">{detail.name}</Descriptions.Item>
<Descriptions.Item label="赛事时间" span={2}>
{detail.eventTime}
</Descriptions.Item>
<Descriptions.Item label="赛事说明" span={3}>
{detail.descr}
</Descriptions.Item>
<Descriptions.Item label="报名起止时间">{detail.registerTime}</Descriptions.Item>
<Descriptions.Item label="取消报名截止时间" span={2}>
{detail.cancelDeadline}
</Descriptions.Item>
<Descriptions.Item label="省市区">{detail.region}</Descriptions.Item>
<Descriptions.Item label="地点">{detail.address}</Descriptions.Item>
<Descriptions.Item label="场地编号">{detail.venueNo}</Descriptions.Item>
<Descriptions.Item label="公开活动">{detail.publicEvent}</Descriptions.Item>
<Descriptions.Item label="证件号">{detail.needIdCard}</Descriptions.Item>
<Descriptions.Item label="证件图片">{detail.needIdCardImage}</Descriptions.Item>
<Descriptions.Item label="创建时间">{detail.createTime}</Descriptions.Item>
<Descriptions.Item label="创建人">{detail.creator}</Descriptions.Item>
<Descriptions.Item label="状态">{detail.status}</Descriptions.Item>
<Descriptions.Item label="报名人数">{detail.enrolledCount}</Descriptions.Item>
<Descriptions.Item label="取消报名人数" span={1}>
{detail.cancelledCount}
</Descriptions.Item>
<Descriptions.Item label="最大人数">{detail.maxCount}</Descriptions.Item>
<Descriptions.Item label="对岸数量">{detail.crossedCount}</Descriptions.Item>
<Descriptions.Item label="已完成对岸数量">{detail.completedCount}</Descriptions.Item>
</Descriptions>
</div>
{/* ===== 封面 ===== */}
<div class={styles.section}>
<div class={styles.sectionTitle}></div>
<Image src={detail.cover} />
</div>
{/* ===== 审核操作 ===== */}
<div class={styles.auditSection}>
<div class={styles.auditHeader}>
<span class={styles.auditTitle}></span>
</div> </div>
<Form layout="horizontal" class={styles.auditForm}> ) : detail.value ? (
<Form.Item label="审核状态" class={styles.auditField}> renderContent()
<Radio.Group v-model={auditForm.approved}> ) : null}
<Radio value={true}></Radio>
<Radio value={false}></Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="审核内容" class={styles.auditField}>
<TextArea
v-model={auditForm.comment}
placeholder="请输入审核内容"
rows={4}
maxlength={500}
showCount
/>
</Form.Item>
</Form>
</div>
</Modal> </Modal>
); );
}, },
@@ -34,6 +34,16 @@
} }
} }
// Item 的 class 会同时挂在 label/content 两个同级 td/th 上,不能用后代选择器
.statCell:global(.ant-descriptions-item-content) {
min-width: 60px;
}
.statCellNoWrap:global(.ant-descriptions-item-label),
.statCellNoWrap:global(.ant-descriptions-item-content) {
white-space: nowrap;
}
.group { .group {
margin-bottom: 16px; margin-bottom: 16px;
@@ -192,7 +192,9 @@ export default defineComponent({
<Descriptions.Item label="省市区">{detail.value.venueArea}</Descriptions.Item> <Descriptions.Item label="省市区">{detail.value.venueArea}</Descriptions.Item>
<Descriptions.Item label="地点">{detail.value.venue}</Descriptions.Item> <Descriptions.Item label="地点">{detail.value.venue}</Descriptions.Item>
<Descriptions.Item label="场地编号">{detail.value.venueNo}</Descriptions.Item> <Descriptions.Item label="场地编号" class={styles.statCell}>
{detail.value.venueNo || '-'}
</Descriptions.Item>
<Descriptions.Item label="公开活动"> <Descriptions.Item label="公开活动">
{formatPublic(detail.value.isPublic)} {formatPublic(detail.value.isPublic)}
@@ -200,7 +202,7 @@ export default defineComponent({
<Descriptions.Item label="证件号"> <Descriptions.Item label="证件号">
{formatYN(detail.value.isRequireIdCardNo)} {formatYN(detail.value.isRequireIdCardNo)}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="证件图片"> <Descriptions.Item label="证件图片" class={styles.statCell}>
{formatYN(detail.value.isRequireIdCardImg)} {formatYN(detail.value.isRequireIdCardImg)}
</Descriptions.Item> </Descriptions.Item>
@@ -211,7 +213,12 @@ export default defineComponent({
<Descriptions.Item label="报名人数">{detail.value.signupCount}</Descriptions.Item> <Descriptions.Item label="报名人数">{detail.value.signupCount}</Descriptions.Item>
<Descriptions.Item label="退赛人数">{detail.value.dropoutCount}</Descriptions.Item> <Descriptions.Item label="退赛人数">{detail.value.dropoutCount}</Descriptions.Item>
<Descriptions.Item label="最大人数">{detail.value.dtotalLimit}</Descriptions.Item> <Descriptions.Item
label="最大人数"
class={[styles.statCell, styles.statCellNoWrap]}
>
{detail.value.totalLimit || '-'}
</Descriptions.Item>
<Descriptions.Item label="总对阵数"> <Descriptions.Item label="总对阵数">
{detail.value.matchTotalCount} {detail.value.matchTotalCount}
+32
View File
@@ -0,0 +1,32 @@
/**
* 赛事审核 - 表单域控制器
*
* 集中管理赛事审核弹窗的提交校验逻辑。
*/
// ============================================================
// 提交校验
// ============================================================
export interface EventAuditForm {
approved: boolean;
comment: string;
}
export interface EventAuditSubmitCheckResult {
valid: boolean;
error?: string;
}
/**
* 校验赛事审核提交数据
*
* @param form 审核表单 { approved, comment }
*/
export function validateEventAuditSubmit(form: EventAuditForm): EventAuditSubmitCheckResult {
// 不通过时强制要求填写审核内容
if (!form.approved && !form.comment.trim()) {
return { valid: false, error: '审核不通过时请填写审核内容' };
}
return { valid: true };
}
+11 -11
View File
@@ -50,24 +50,24 @@ export function useLogColumns() {
width: 200, width: 200,
align: 'center' as const, align: 'center' as const,
}, },
{ // {
title: '操作对象', // title: '操作对象',
dataIndex: 'obj', // dataIndex: 'obj',
key: 'obj', // key: 'obj',
width: 240, // width: 240,
align: 'center' as const, // align: 'center' as const,
}, // },
{ {
title: '操作内容', title: '操作内容',
dataIndex: 'content', dataIndex: 'obj',
key: 'content', key: 'obj',
width: 360, width: 360,
align: 'center' as const, align: 'center' as const,
}, },
{ {
title: '操作时间', title: '操作时间',
dataIndex: 'createDate', dataIndex: 'createTime',
key: 'createDate', key: 'createTime',
width: 170, width: 170,
align: 'center' as const, align: 'center' as const,
}, },
-34
View File
@@ -3,10 +3,6 @@
* 该页面使用的所有接口集中管理 * 该页面使用的所有接口集中管理
*/ */
import { get, post } from '@/utils/request'; import { get, post } from '@/utils/request';
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
import { buildMockOrderListPage } from '@/config/mock/orderList';
import { MOCK_ORDER_DETAIL } from '@/config/mock/orderDetail';
import { buildMockOrderPlayerListPage } from '@/config/mock/orderPlayerList';
import type { import type {
OrderListQueryParams, OrderListQueryParams,
OrderDetailQueryParams, OrderDetailQueryParams,
@@ -40,8 +36,6 @@ const orderDetail = '/admin/manager/order/info';
const orderPlayerList = '/admin/manager/order/info/userPage'; const orderPlayerList = '/admin/manager/order/info/userPage';
const retryRefund = '/admin/manager/order/retryRefund'; const retryRefund = '/admin/manager/order/retryRefund';
const delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
// ============================================================ // ============================================================
// API 函数 // API 函数
// ============================================================ // ============================================================
@@ -50,16 +44,6 @@ const delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve,
export async function getOrderList( export async function getOrderList(
params: OrderListQueryParams, params: OrderListQueryParams,
): Promise<ApiResult<PageData<TournamentAdminOrderPageVO>>> { ): Promise<ApiResult<PageData<TournamentAdminOrderPageVO>>> {
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: buildMockOrderListPage(page, limit),
};
}
return get(orderList, params as Record<string, any>); return get(orderList, params as Record<string, any>);
} }
@@ -67,14 +51,6 @@ export async function getOrderList(
export async function getOrderDetail( export async function getOrderDetail(
params: OrderDetailQueryParams, params: OrderDetailQueryParams,
): Promise<ApiResult<TournamentAdminOrderInfoVO>> { ): Promise<ApiResult<TournamentAdminOrderInfoVO>> {
if (USE_MOCK) {
await delay(MOCK_DELAY);
return {
code: 200,
msg: 'success',
data: { ...MOCK_ORDER_DETAIL },
};
}
return get(orderDetail, params as Record<string, any>); return get(orderDetail, params as Record<string, any>);
} }
@@ -82,16 +58,6 @@ export async function getOrderDetail(
export async function getOrderPlayerList( export async function getOrderPlayerList(
params: OrderPlayerListQueryParams, params: OrderPlayerListQueryParams,
): Promise<ApiResult<PageData<TournamentAdminOrderInfoPageVO>>> { ): Promise<ApiResult<PageData<TournamentAdminOrderInfoPageVO>>> {
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: buildMockOrderPlayerListPage(page, limit),
};
}
return get(orderPlayerList, params as Record<string, any>); return get(orderPlayerList, params as Record<string, any>);
} }
-5
View File
@@ -3,7 +3,6 @@
* 接口文档: GET /admin/manager/user/page * 接口文档: GET /admin/manager/user/page
*/ */
import { get, post } from '@/utils/request'; import { get, post } from '@/utils/request';
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
import type { import type {
UserSaveParams, UserSaveParams,
UserUpdateParams, UserUpdateParams,
@@ -61,8 +60,6 @@ const userUpdate = '/admin/sys/user/update';
const userActive = '/admin/manager/user/active'; const userActive = '/admin/manager/user/active';
const userUpdatePwd = '/admin/sys/user/updatepwd'; const userUpdatePwd = '/admin/sys/user/updatepwd';
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
// ============================================================ // ============================================================
// API 函数 // API 函数
// ============================================================ // ============================================================
@@ -76,13 +73,11 @@ export function getUserList(
/** 新增用户 */ /** 新增用户 */
export function saveUser(params: UserSaveParams): Promise<ApiResult<Record<string, never>>> { export function saveUser(params: UserSaveParams): Promise<ApiResult<Record<string, never>>> {
if (USE_MOCK) return delay(MOCK_DELAY).then(() => ({ code: 200, msg: 'success', data: {} }));
return post(userSave, params); return post(userSave, params);
} }
/** 更新用户 */ /** 更新用户 */
export function updateUser(params: UserUpdateParams): Promise<ApiResult<Record<string, never>>> { export function updateUser(params: UserUpdateParams): Promise<ApiResult<Record<string, never>>> {
if (USE_MOCK) return delay(MOCK_DELAY).then(() => ({ code: 200, msg: 'success', data: {} }));
return post(userUpdate, params); return post(userUpdate, params);
} }
@@ -3,8 +3,6 @@
* 该页面使用的所有接口集中管理 * 该页面使用的所有接口集中管理
*/ */
import { get } from '@/utils/request'; import { get } from '@/utils/request';
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
import { buildMockPaymentFlowPage } from '@/config/mock/paymentFlow';
import type { import type {
PaymentFlowQueryParams, PaymentFlowQueryParams,
PaymentFlowVO, PaymentFlowVO,
@@ -25,8 +23,6 @@ export type {
// ============================================================ // ============================================================
const paymentFlowList = '/admin/finance/paymentFlow/page'; const paymentFlowList = '/admin/finance/paymentFlow/page';
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
// ============================================================ // ============================================================
// API 函数 // API 函数
// ============================================================ // ============================================================
@@ -35,11 +31,5 @@ const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
export async function getPaymentFlowList( export async function getPaymentFlowList(
params: PaymentFlowQueryParams, params: PaymentFlowQueryParams,
): Promise<ApiResult<PaymentFlowPageData>> { ): Promise<ApiResult<PaymentFlowPageData>> {
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: buildMockPaymentFlowPage(page, limit, params) };
}
return get(paymentFlowList, params as Record<string, any>); return get(paymentFlowList, params as Record<string, any>);
} }
@@ -3,9 +3,6 @@
* 该页面使用的所有接口集中管理 * 该页面使用的所有接口集中管理
*/ */
import { get } from '@/utils/request'; import { get } from '@/utils/request';
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
import { buildMockWalletPage } from '@/config/mock/walletList';
import { buildMockWalletTransactionPage } from '@/config/mock/walletTransactions';
import type { import type {
WalletQueryParams, WalletQueryParams,
WalletSummaryVO, WalletSummaryVO,
@@ -32,8 +29,6 @@ export type {
const walletList = '/admin/finance/wallet/summary'; const walletList = '/admin/finance/wallet/summary';
const walletTransaction = '/admin/finance/wallet/page'; const walletTransaction = '/admin/finance/wallet/page';
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
// ============================================================ // ============================================================
// API 函数 // API 函数
// ============================================================ // ============================================================
@@ -42,12 +37,6 @@ const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
export async function getWalletList( export async function getWalletList(
params: WalletQueryParams, params: WalletQueryParams,
): Promise<ApiResult<WalletSummaryPageData>> { ): Promise<ApiResult<WalletSummaryPageData>> {
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: buildMockWalletPage(page, limit, params) };
}
return get(walletList, params as Record<string, any>); return get(walletList, params as Record<string, any>);
} }
@@ -55,11 +44,5 @@ export async function getWalletList(
export async function getWalletTransactionList( export async function getWalletTransactionList(
params: WalletTransactionQueryParams, params: WalletTransactionQueryParams,
): Promise<ApiResult<PageData<WalletTransactionVO>>> { ): Promise<ApiResult<PageData<WalletTransactionVO>>> {
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: buildMockWalletTransactionPage(page, limit, params) };
}
return get(walletTransaction, params as Record<string, any>); return get(walletTransaction, params as Record<string, any>);
} }
@@ -6,11 +6,15 @@ import { PlusOutlined } from '@ant-design/icons-vue';
import { uploadFile, OssUploadType } from '@/utils/oss'; import { uploadFile, OssUploadType } from '@/utils/oss';
import { getWithdrawInfo, postWithdrawAudit, type WithdrawInfoVO } from '../model/services'; import { getWithdrawInfo, postWithdrawAudit, type WithdrawInfoVO } from '../model/services';
import { getAuditStatusText, getPayStatusText } from '../model/useWithdrawModel'; import { getAuditStatusText, getPayStatusText } from '../model/useWithdrawModel';
import {
MAX_IMAGE_COUNT,
MAX_IMAGE_SIZE,
validateImageFile,
validateWithdrawAuditSubmit,
} from '../model/form';
import styles from './WithdrawDetailModal.module.less'; import styles from './WithdrawDetailModal.module.less';
const { TextArea } = Input; const { TextArea } = Input;
const MAX_IMAGE_COUNT = 3;
const MAX_IMAGE_SIZE = 5 * 1024 * 1024;
interface WithdrawDetailModalProps { interface WithdrawDetailModalProps {
visible: boolean; visible: boolean;
@@ -67,16 +71,9 @@ export default defineComponent({
const beforeUpload = async (file: UploadFile) => { const beforeUpload = async (file: UploadFile) => {
const rawFile = file as unknown as File; const rawFile = file as unknown as File;
if (imageList.value.length >= MAX_IMAGE_COUNT) { const check = validateImageFile(rawFile, imageList.value.length);
message.warning(`最多上传 ${MAX_IMAGE_COUNT} 张图片`); if (!check.valid) {
return false; message.error(check.error!);
}
if (!/^image\//.test(rawFile.type)) {
message.error('请选择图片文件');
return false;
}
if (rawFile.size >= MAX_IMAGE_SIZE) {
message.error('图片大小不能超过 5MB');
return false; return false;
} }
@@ -114,21 +111,16 @@ export default defineComponent({
// ===== 提交审核 ===== // ===== 提交审核 =====
const handleSubmit = async () => { const handleSubmit = async () => {
// 驳回:审核内容必填 // 提交前校验
if (!auditForm.pass && !auditForm.remark.trim()) { const hasDoneImage = imageList.value.some((item) => item.status === 'done' && item.url);
message.warning('驳回时请填写审核内容'); const isUploading = imageList.value.some((item) => item.status === 'uploading');
return; const check = validateWithdrawAuditSubmit(
} { pass: auditForm.pass, remark: auditForm.remark },
// 通过:必须上传图片 hasDoneImage,
if (auditForm.pass) { isUploading,
const hasDoneImage = imageList.value.some((item) => item.status === 'done' && item.url); );
if (!hasDoneImage) { if (!check.valid) {
message.warning('审核通过时必须上传审核图片'); message.warning(check.error!);
return;
}
}
if (imageList.value.some((item) => item.status === 'uploading')) {
message.warning('图片仍在上传中,请稍候');
return; return;
} }
+84
View File
@@ -0,0 +1,84 @@
/**
* 提现审核 - 表单域控制器
*
* 集中管理提现审核弹窗的图片上传校验、提交校验逻辑与常量。
*/
import { isImageFile } from '@/utils/form';
// ============================================================
// 常量
// ============================================================
/** 审核图片最大上传张数 */
export const MAX_IMAGE_COUNT = 3;
/** 审核图片单张最大体积:5MB */
export const MAX_IMAGE_SIZE = 5 * 1024 * 1024;
// ============================================================
// 图片上传校验
// ============================================================
export interface ImageUploadCheckResult {
valid: boolean;
error?: string;
}
/**
* 校验待上传的审核图片
* @param file 待上传的文件对象
* @param currentCount 当前已上传的图片数量
* @returns 校验结果
*/
export function validateImageFile(file: File, currentCount: number): ImageUploadCheckResult {
if (currentCount >= MAX_IMAGE_COUNT) {
return { valid: false, error: `最多上传 ${MAX_IMAGE_COUNT} 张图片` };
}
if (!isImageFile(file)) {
return { valid: false, error: '请选择图片文件' };
}
if (file.size >= MAX_IMAGE_SIZE) {
return { valid: false, error: '图片大小不能超过 5MB' };
}
return { valid: true };
}
// ============================================================
// 提交审核校验
// ============================================================
export interface WithdrawAuditForm {
pass: boolean;
remark: string;
}
export interface WithdrawAuditSubmitCheckResult {
valid: boolean;
error?: string;
}
/**
* 校验提现审核提交数据
*
* @param form 审核表单 { pass, remark }
* @param hasDoneImage 是否有上传成功的图片
* @param isUploading 是否有图片仍在上传中
*/
export function validateWithdrawAuditSubmit(
form: WithdrawAuditForm,
hasDoneImage: boolean,
isUploading: boolean,
): WithdrawAuditSubmitCheckResult {
// 驳回:审核内容必填
if (!form.pass && !form.remark.trim()) {
return { valid: false, error: '驳回时请填写审核内容' };
}
// 通过:必须上传图片
if (form.pass && !hasDoneImage) {
return { valid: false, error: '审核通过时必须上传审核图片' };
}
// 图片上传未完成
if (isUploading) {
return { valid: false, error: '图片仍在上传中,请稍候' };
}
return { valid: true };
}
@@ -3,9 +3,6 @@
* 该页面使用的所有接口集中管理 * 该页面使用的所有接口集中管理
*/ */
import { get, post } from '@/utils/request'; import { get, post } from '@/utils/request';
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
import { buildMockWithdrawPage } from '@/config/mock/withdrawList';
import { buildMockWithdrawInfo } from '@/config/mock/withdrawInfo';
import type { import type {
WithdrawQueryParams, WithdrawQueryParams,
WithdrawVO, WithdrawVO,
@@ -29,8 +26,6 @@ const withdrawList = '/admin/finance/withdraw/page';
const withdrawInfo = '/admin/finance/withdraw/info'; const withdrawInfo = '/admin/finance/withdraw/info';
const withdrawAudit = '/admin/finance/withdraw/audit'; const withdrawAudit = '/admin/finance/withdraw/audit';
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
// ============================================================ // ============================================================
// API 函数 // API 函数
// ============================================================ // ============================================================
@@ -39,25 +34,11 @@ const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
export async function getWithdrawList( export async function getWithdrawList(
params: WithdrawQueryParams, params: WithdrawQueryParams,
): Promise<ApiResult<PageData<WithdrawVO>>> { ): Promise<ApiResult<PageData<WithdrawVO>>> {
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: buildMockWithdrawPage(page, limit, params),
};
}
return get(withdrawList, params as Record<string, any>); return get(withdrawList, params as Record<string, any>);
} }
/** 提现详情 */ /** 提现详情 */
export async function getWithdrawInfo(id: number): Promise<ApiResult<WithdrawInfoVO>> { export async function getWithdrawInfo(id: number): Promise<ApiResult<WithdrawInfoVO>> {
if (USE_MOCK) {
await delay(MOCK_DELAY);
return { code: 200, msg: 'success', data: buildMockWithdrawInfo(id) };
}
return get(withdrawInfo, { id: String(id) }); return get(withdrawInfo, { id: String(id) });
} }
+2 -6
View File
@@ -6,6 +6,7 @@ import { auth } from '@/hooks/useAuth';
import { useEffect, useState } from '@/hooks'; import { useEffect, useState } from '@/hooks';
import { useUserStore } from '@/stores/userStore'; import { useUserStore } from '@/stores/userStore';
import { login as loginApi } from '@/api/login'; import { login as loginApi } from '@/api/login';
import { loginFormRules } from './model/form';
import styles from './index.module.less'; import styles from './index.module.less';
interface LoginForm { interface LoginForm {
@@ -40,11 +41,6 @@ export default defineComponent({
} }
}, []); }, []);
const rules = {
username: [{ required: true, message: '请输入用户名' }],
password: [{ required: true, message: '请输入密码' }],
};
const handleSubmit = async () => { const handleSubmit = async () => {
if (!form.phone || !form.password) { if (!form.phone || !form.password) {
message.warning('请输入用户名和密码'); message.warning('请输入用户名和密码');
@@ -214,7 +210,7 @@ export default defineComponent({
<Form <Form
class={styles.loginForm} class={styles.loginForm}
model={form} model={form}
rules={rules} rules={loginFormRules}
layout="vertical" layout="vertical"
onFinish={handleSubmit} onFinish={handleSubmit}
> >
+16
View File
@@ -0,0 +1,16 @@
/**
* 登录页 - 表单域控制器
*
* 集中管理登录表单的校验规则。
*/
import type { Rule } from 'ant-design-vue/es/form';
// ============================================================
// 表单规则
// ============================================================
/** 登录表单的整体规则(form-level ruleskey 对应 FormItem 的 name */
export const loginFormRules: Record<string, Rule[]> = {
phone: [{ required: true, message: '请输入用户名' }],
password: [{ required: true, message: '请输入密码' }],
};
-19
View File
@@ -3,8 +3,6 @@
* 该页面使用的所有接口集中管理 * 该页面使用的所有接口集中管理
*/ */
import { get } from '@/utils/request'; import { get } from '@/utils/request';
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
import { buildMockOperationLogPage } from '@/config/mock/operationLogs';
import type { import type {
OperationLogQueryParams, OperationLogQueryParams,
SysOperationLogVO, SysOperationLogVO,
@@ -24,30 +22,13 @@ export type {
// ============================================================ // ============================================================
const operationLogList = '/admin/sys/operation/page'; const operationLogList = '/admin/sys/operation/page';
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
// ============================================================ // ============================================================
// API 函数 // API 函数
// ============================================================ // ============================================================
function pickFilters(params: OperationLogQueryParams): OperationLogQueryParams {
const { page: _page, limit: _limit, ...filters } = params;
return filters;
}
/** 系统操作日志分页 */ /** 系统操作日志分页 */
export async function getOperationLogList( export async function getOperationLogList(
params: OperationLogQueryParams, params: OperationLogQueryParams,
): Promise<ApiResult<PageData<SysOperationLogVO>>> { ): Promise<ApiResult<PageData<SysOperationLogVO>>> {
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: buildMockOperationLogPage(page, limit, pickFilters(params)),
};
}
return get(operationLogList, params as Record<string, any>); return get(operationLogList, params as Record<string, any>);
} }
+11
View File
@@ -27,6 +27,12 @@ export const LOGIN_PASSWORD_REGEX = /^(?=.*[A-Za-z])(?=.*\d)[\w!@#$%^&*()_+\-=]{
*/ */
export const REAL_NAME_REGEX = /^[\u4e00-\u9fa5A-Za-z0-9·]{1,8}$/; export const REAL_NAME_REGEX = /^[\u4e00-\u9fa5A-Za-z0-9·]{1,8}$/;
/**
* 图片 MIME 类型前缀
* - 用于上传前校验文件是否为图片
*/
export const IMAGE_TYPE_REGEX = /^image\//;
// ============================================================ // ============================================================
// 校验函数 // 校验函数
// ============================================================ // ============================================================
@@ -45,3 +51,8 @@ export function isValidPassword(value: unknown): boolean {
export function isValidRealName(value: unknown): boolean { export function isValidRealName(value: unknown): boolean {
return typeof value === 'string' && value.length > 0 && REAL_NAME_REGEX.test(value); return typeof value === 'string' && value.length > 0 && REAL_NAME_REGEX.test(value);
} }
/** 校验文件是否为图片类型 */
export function isImageFile(file: { type: string }): boolean {
return IMAGE_TYPE_REGEX.test(file.type);
}