feat: 接口联调
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { defineComponent, reactive, onMounted } from 'vue';
|
||||
import { defineComponent, reactive, ref } from 'vue';
|
||||
import { Modal, Descriptions, Image, Table, Pagination, Space, Spin } from 'ant-design-vue';
|
||||
import { EyeOutlined } from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
type InnerCategoryVO,
|
||||
type TournamentAdminInfoSignupVO,
|
||||
} from '../model/services';
|
||||
import { useState } from '@/hooks';
|
||||
import { useState, useEffect } from '@/hooks';
|
||||
import styles from './EventDetailModal.module.less';
|
||||
|
||||
interface EventDetailModalProps {
|
||||
@@ -67,8 +67,8 @@ const PLAYER_COLUMNS = [
|
||||
title: '证件图片',
|
||||
key: 'idCardImgs',
|
||||
width: 180,
|
||||
customRender: ({ record }: { record: TournamentAdminInfoSignupVO }) =>
|
||||
renderIdImages(record.idCardImgList, record.idCardImg),
|
||||
customRender: ({ record }: { record?: TournamentAdminInfoSignupVO }) =>
|
||||
record ? renderIdImages(record.idCardImgList || [], record.idCardImg || '') : <span>-</span>,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -82,17 +82,19 @@ export default defineComponent({
|
||||
setup(props: EventDetailModalProps) {
|
||||
// ===== 详情数据 =====
|
||||
const [detail, setDetail] = useState<TournamentAdminInfoVO | null>(null);
|
||||
const [detailLoading, setDetailLoading] = useState(true);
|
||||
const [detailLoading, setDetailLoading] = useState(false);
|
||||
|
||||
// ===== 每个组别的选手数据(key = categoryId) =====
|
||||
const categoryPlayers = reactive<Record<string, TournamentAdminInfoSignupVO[]>>({});
|
||||
const categoryPlayerTotal = reactive<Record<string, number>>({});
|
||||
const categoryPlayerLoading = reactive<Record<string, boolean>>({});
|
||||
const categoryPlayerPage = reactive<Record<string, number>>({});
|
||||
const categoryPlayers = ref<Record<string, TournamentAdminInfoSignupVO[]>>({});
|
||||
const categoryPlayerTotal = ref<Record<string, number>>({});
|
||||
const categoryPlayerLoading = ref<Record<string, boolean>>({});
|
||||
const categoryPlayerPage = ref<Record<string, number>>({});
|
||||
|
||||
/** 获取某个组别的选手列表(分页) */
|
||||
const fetchPlayers = async (categoryId: string, page: number) => {
|
||||
categoryPlayerLoading[categoryId] = true;
|
||||
categoryPlayerLoading.value[categoryId] = true;
|
||||
// 触发响应式更新
|
||||
categoryPlayerLoading.value = { ...categoryPlayerLoading.value };
|
||||
try {
|
||||
const res = await getSignupList({
|
||||
categoryId,
|
||||
@@ -100,14 +102,18 @@ export default defineComponent({
|
||||
limit: String(PAGE_SIZE),
|
||||
});
|
||||
if (res.code == 200) {
|
||||
categoryPlayers[categoryId] = res.data.list;
|
||||
categoryPlayerTotal[categoryId] = res.data.total;
|
||||
categoryPlayerPage[categoryId] = page;
|
||||
categoryPlayers.value[categoryId] = res.data.list;
|
||||
categoryPlayers.value = { ...categoryPlayers.value };
|
||||
categoryPlayerTotal.value[categoryId] = res.data.total;
|
||||
categoryPlayerTotal.value = { ...categoryPlayerTotal.value };
|
||||
categoryPlayerPage.value[categoryId] = page;
|
||||
categoryPlayerPage.value = { ...categoryPlayerPage.value };
|
||||
}
|
||||
} catch {
|
||||
// mock 模式不会到这里,真实模式下由 API 层处理
|
||||
} finally {
|
||||
categoryPlayerLoading[categoryId] = false;
|
||||
categoryPlayerLoading.value[categoryId] = false;
|
||||
categoryPlayerLoading.value = { ...categoryPlayerLoading.value };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -116,24 +122,19 @@ export default defineComponent({
|
||||
fetchPlayers(categoryId, page);
|
||||
};
|
||||
|
||||
// ===== 初始化 =====
|
||||
onMounted(async () => {
|
||||
/** 获取赛事详情 */
|
||||
const fetchDetail = async (id: string) => {
|
||||
setDetailLoading(true);
|
||||
setDetail(null);
|
||||
try {
|
||||
const id = props.record?.id;
|
||||
if (!id) {
|
||||
message.error('缺少赛事ID');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await getEventDetail(String(id));
|
||||
if (res.code == 200) {
|
||||
const res = await getEventDetail(id);
|
||||
if (res.code == 200 && res.data) {
|
||||
setDetail(res.data);
|
||||
// 为每个组别加载第一页选手
|
||||
for (const cat of res.data.categoryList) {
|
||||
categoryPlayers[cat.id] = [];
|
||||
categoryPlayerTotal[cat.id] = 0;
|
||||
categoryPlayerPage[cat.id] = 1;
|
||||
categoryPlayers.value[cat.id] = [];
|
||||
categoryPlayerTotal.value[cat.id] = 0;
|
||||
categoryPlayerPage.value[cat.id] = 1;
|
||||
fetchPlayers(cat.id, 1);
|
||||
}
|
||||
} else {
|
||||
@@ -144,7 +145,14 @@ export default defineComponent({
|
||||
} finally {
|
||||
setDetailLoading(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 弹窗打开且 有 record.id 时自动请求(useEffect 带 immediate: true,兼容 destroyOnClose 重建场景)
|
||||
useEffect(() => {
|
||||
if (props.visible && props.record?.id) {
|
||||
fetchDetail(String(props.record.id));
|
||||
}
|
||||
}, [() => props.visible, () => props.record?.id]);
|
||||
|
||||
return () => (
|
||||
<Modal
|
||||
@@ -234,8 +242,8 @@ export default defineComponent({
|
||||
</div>
|
||||
<Table
|
||||
columns={PLAYER_COLUMNS}
|
||||
dataSource={categoryPlayers[cat.id] || []}
|
||||
loading={categoryPlayerLoading[cat.id]}
|
||||
dataSource={categoryPlayers.value[cat.id] || []}
|
||||
loading={categoryPlayerLoading.value[cat.id]}
|
||||
scroll={{ x: 'max-content', y: 200 }}
|
||||
size="small"
|
||||
pagination={false}
|
||||
@@ -243,9 +251,9 @@ export default defineComponent({
|
||||
/>
|
||||
<div class={styles.paginationRow}>
|
||||
<Pagination
|
||||
current={categoryPlayerPage[cat.id] || 1}
|
||||
current={categoryPlayerPage.value[cat.id] || 1}
|
||||
pageSize={PAGE_SIZE}
|
||||
total={categoryPlayerTotal[cat.id] || 0}
|
||||
total={categoryPlayerTotal.value[cat.id] || 0}
|
||||
showSizeChanger={false}
|
||||
size="small"
|
||||
onChange={(page: number) => handlePlayerPageChange(cat.id, page)}
|
||||
|
||||
@@ -1,36 +1,54 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import { Modal, Image } from 'ant-design-vue';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { Modal, Image, Spin } from 'ant-design-vue';
|
||||
import { getRuleInfo } from '../model/services';
|
||||
import type { TournamentAdminRuleInfoVO } from '@/api/events/types';
|
||||
import { useEffect } from '@/hooks';
|
||||
import styles from './EventRegulationModal.module.less';
|
||||
|
||||
interface EventRegulationModalProps {
|
||||
visible: boolean;
|
||||
/** 当前赛事记录(需包含 id 字段) */
|
||||
record?: Record<string, any>;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
/** 规程内容 */
|
||||
const REGULATION_TEXT = [
|
||||
'一、赛事目的:推动羽毛球运动发展,提高运动员技术水平,增进体育文化交流。',
|
||||
'二、参赛资格:凡身体健康、年龄符合要求的羽毛球爱好者均可报名参加。',
|
||||
'三、比赛规则:采用中国羽毛球协会审定的最新《羽毛球竞赛规则》。',
|
||||
'四、赛制安排:比赛分小组赛和淘汰赛两个阶段,小组赛采用单循环,淘汰赛采用单败淘汰。',
|
||||
'五、器材要求:参赛运动员自备球拍,比赛用球由组委会统一提供。',
|
||||
'六、裁判设置:每场比赛设主裁判1名、副裁判2名,确保比赛公平公正。',
|
||||
].join('\n\n');
|
||||
|
||||
/** 规程图片(mock:8 张示例图,4 列 grid) */
|
||||
const REGULATION_IMAGES = Array.from({ length: 8 }, (_, i) => ({
|
||||
id: i + 1,
|
||||
src: `https://picsum.photos/seed/reg${i + 1}/300/200`,
|
||||
alt: `规程图片 ${i + 1}`,
|
||||
}));
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EventRegulationModal',
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
record: { type: Object, default: () => ({}) },
|
||||
onClose: { type: Function, required: true },
|
||||
},
|
||||
setup(props: EventRegulationModalProps) {
|
||||
const loading = ref(false);
|
||||
const ruleData = ref<TournamentAdminRuleInfoVO | null>(null);
|
||||
|
||||
/** 调用 getRuleInfo 接口获取规程数据 */
|
||||
const fetchRuleInfo = async (id: string) => {
|
||||
loading.value = true;
|
||||
ruleData.value = null;
|
||||
try {
|
||||
const res = await getRuleInfo(id);
|
||||
if (res.code == 200 && res.data) {
|
||||
ruleData.value = res.data;
|
||||
} else {
|
||||
ruleData.value = null;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取赛事规程失我败:', e);
|
||||
ruleData.value = null;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 弹窗打开且 有 record.id 时自动请求(useEffect 带 immediate: true,兼容 destroyOnClose 重建场景)
|
||||
useEffect(() => {
|
||||
if (props.visible && props.record?.id) {
|
||||
fetchRuleInfo(props.record.id);
|
||||
}
|
||||
}, [() => props.visible, () => props.record?.id]);
|
||||
|
||||
return () => (
|
||||
<Modal
|
||||
visible={props.visible}
|
||||
@@ -42,25 +60,48 @@ export default defineComponent({
|
||||
destroyOnClose
|
||||
wrapClassName={styles.eventRegulationModalMain}
|
||||
>
|
||||
{/* 规程内容 */}
|
||||
<div class={styles.section}>
|
||||
<div class={styles.sectionTitle}>规程内容</div>
|
||||
<div class={styles.textBlock}>{REGULATION_TEXT}</div>
|
||||
</div>
|
||||
|
||||
{/* 规程图片:4 列 grid,点击可浏览 */}
|
||||
<div class={styles.section}>
|
||||
<div class={styles.sectionTitle}>规程图片</div>
|
||||
<Image.PreviewGroup>
|
||||
<div class={styles.imageGrid}>
|
||||
{REGULATION_IMAGES.map((img) => (
|
||||
<div key={img.id} class={styles.imageCell}>
|
||||
<Image src={img.src} />
|
||||
<Spin spinning={loading.value}>
|
||||
{ruleData.value ? (
|
||||
<>
|
||||
{/* 规程内容 */}
|
||||
{ruleData.value.rulesDescr && (
|
||||
<div class={styles.section}>
|
||||
<div class={styles.sectionTitle}>规程内容</div>
|
||||
<div class={styles.textBlock}>{ruleData.value.rulesDescr}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Image.PreviewGroup>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 规程图片 */}
|
||||
{(ruleData.value.rulesDescrImgList?.length ?? 0) > 0 && (
|
||||
<div class={styles.section}>
|
||||
<div class={styles.sectionTitle}>规程图片</div>
|
||||
<Image.PreviewGroup>
|
||||
<div class={styles.imageGrid}>
|
||||
{ruleData.value.rulesDescrImgList!.map((src: string, idx: number) => (
|
||||
<div key={idx} class={styles.imageCell}>
|
||||
<Image src={src} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Image.PreviewGroup>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 无数据提示 */}
|
||||
{!ruleData.value.rulesDescr && !ruleData.value.rulesDescrImgList?.length && (
|
||||
<div style={{ textAlign: 'center', color: 'rgba(0,0,0,0.45)', padding: '40px 0' }}>
|
||||
暂无规程信息
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
!loading.value && (
|
||||
<div style={{ textAlign: 'center', color: 'rgba(0,0,0,0.45)', padding: '40px 0' }}>
|
||||
暂无规程信息
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</Spin>
|
||||
</Modal>
|
||||
);
|
||||
},
|
||||
|
||||
+110
-170
@@ -1,4 +1,4 @@
|
||||
import { defineComponent, onMounted } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
Cascader,
|
||||
Tooltip,
|
||||
Pagination,
|
||||
Modal,
|
||||
message,
|
||||
} from 'ant-design-vue';
|
||||
import {
|
||||
useEventListModel,
|
||||
@@ -20,9 +18,9 @@ import {
|
||||
ID_CARD_OPTIONS,
|
||||
ID_CARD_IMAGE_OPTIONS,
|
||||
} from './model/useEventListModel';
|
||||
import { toggleEventOnline } from './model/services';
|
||||
import { useEventListColumns } from './model/useEventListColumns';
|
||||
import { regionOptions } from '@/utils/areaData';
|
||||
import { useContainerSize, useState } from '@/hooks';
|
||||
import { useContainerSize } from '@/hooks';
|
||||
import EventRegulationModal from './components/EventRegulationModal';
|
||||
import EventDetailModal from './components/EventDetailModal';
|
||||
import EventAuditModal from './components/EventAuditModal';
|
||||
@@ -31,85 +29,12 @@ import pageStyles from '@/assets/styles/pageLayout.module.less';
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
/**
|
||||
* bodyCell 渲染函数
|
||||
*/
|
||||
function renderBodyCell({
|
||||
column,
|
||||
text,
|
||||
record,
|
||||
onView,
|
||||
onViewRegulation,
|
||||
onAudit,
|
||||
onToggleShelf,
|
||||
}: {
|
||||
column: any;
|
||||
text: any;
|
||||
record: any;
|
||||
onView: (record: any) => void;
|
||||
onViewRegulation: (record: any) => void;
|
||||
onAudit: (record: any) => void;
|
||||
onToggleShelf: (record: any) => void;
|
||||
}) {
|
||||
if (column.dataIndex === 'name') {
|
||||
const maxLen = 15;
|
||||
const raw = text || '';
|
||||
const display = raw.length > maxLen ? raw.slice(0, maxLen) + '...' : raw;
|
||||
return raw.length > maxLen ? (
|
||||
<Tooltip title={raw}>
|
||||
<span>{display}</span>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<span>{display}</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (column.key === 'regulation') {
|
||||
return (
|
||||
<a style={{ cursor: 'pointer' }} onClick={() => onViewRegulation(record)}>
|
||||
查看
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
if (column.key === 'action') {
|
||||
const status = record.status;
|
||||
|
||||
return (
|
||||
// 报名中(0) / 进行中(1):查看 + 下架
|
||||
((status === '0' || status === '1') && (
|
||||
<Space>
|
||||
<Button type="link" size="small" onClick={() => onView(record)}>
|
||||
查看
|
||||
</Button>
|
||||
<Button type="link" size="small" onClick={() => onToggleShelf(record)}>
|
||||
下架
|
||||
</Button>
|
||||
</Space>
|
||||
)) ||
|
||||
// 已下架(4):查看 + 上架
|
||||
(status === '4' && (
|
||||
<Space>
|
||||
<Button type="link" size="small" onClick={() => onView(record)}>
|
||||
查看
|
||||
</Button>
|
||||
<Button type="link" size="small" onClick={() => onToggleShelf(record)}>
|
||||
上架
|
||||
</Button>
|
||||
</Space>
|
||||
)) || (
|
||||
// 已结束(2) / 已删除(3):仅查看
|
||||
<Button type="link" size="small" onClick={() => onView(record)}>
|
||||
查看
|
||||
</Button>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* 赛事列表
|
||||
* 赛事列表页面(纯视图层)
|
||||
*
|
||||
* 架构分层:
|
||||
* - 数据层:services.ts(API 调用)
|
||||
* - 逻辑层:useEventListModel.ts(状态 + 业务逻辑 + 交互处理)
|
||||
* - 视图层:useEventListColumns.ts(表格列配置)+ 本组件(纯 UI 渲染)
|
||||
*/
|
||||
export default defineComponent({
|
||||
name: 'EventList',
|
||||
@@ -118,94 +43,122 @@ export default defineComponent({
|
||||
filterForm,
|
||||
loading,
|
||||
dataSource,
|
||||
columns,
|
||||
pagination,
|
||||
regulationModalVisible,
|
||||
detailModalVisible,
|
||||
auditModalVisible,
|
||||
currentRecord,
|
||||
togglingId,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handlePageChange,
|
||||
openDetailModal,
|
||||
openRegulationModal,
|
||||
closeRegulationModal,
|
||||
closeDetailModal,
|
||||
closeAuditModal,
|
||||
submitAudit,
|
||||
handleToggleShelf,
|
||||
} = useEventListModel();
|
||||
|
||||
const { columns } = useEventListColumns();
|
||||
const { containerRef, height } = useContainerSize();
|
||||
|
||||
// 弹窗状态
|
||||
const [regulationModalVisible, setRegulationModalVisible] = useState<boolean>(false);
|
||||
const [detailModalVisible, setDetailModalVisible] = useState<boolean>(false);
|
||||
const [auditModalVisible, setAuditModalVisible] = useState<boolean>(false);
|
||||
const [currentRecord, setCurrentRecord] = useState<any>({});
|
||||
|
||||
const handleView = (record: any) => {
|
||||
console.log('查看赛事:', record.id);
|
||||
setCurrentRecord(record);
|
||||
setDetailModalVisible(true);
|
||||
/** 渲染赛事名称(超长省略 + Tooltip) */
|
||||
const renderName = (text: string) => {
|
||||
const maxLen = 15;
|
||||
const raw = text || '';
|
||||
const display = raw.length > maxLen ? raw.slice(0, maxLen) + '...' : raw;
|
||||
return raw.length > maxLen ? (
|
||||
<Tooltip title={raw}>
|
||||
<span>{display}</span>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<span>{display}</span>
|
||||
);
|
||||
};
|
||||
|
||||
/** 上架 / 下架 通用 */
|
||||
const handleToggleShelf = (record: any) => {
|
||||
const isRemoved = record.status === '4';
|
||||
const actionText = isRemoved ? '上架' : '下架';
|
||||
Modal.confirm({
|
||||
title: `${actionText}确认`,
|
||||
content: `确认${actionText}"${record.name}"该赛事吗,下架后用户将无法搜索和报名该赛事,已有报名和订单不受影响。`,
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
const online = isRemoved ? '1' : '0';
|
||||
try {
|
||||
const res = await toggleEventOnline({ id: record.id, online });
|
||||
if (res.code == 200) {
|
||||
message.success(`${actionText}成功`);
|
||||
handleSearch();
|
||||
} else {
|
||||
message.error(res.msg || `${actionText}失败`);
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error(`${actionText}失败:`, e);
|
||||
}
|
||||
},
|
||||
});
|
||||
/** 渲染操作列(纯视图,逻辑在 model 的 handleToggleShelf) */
|
||||
const renderAction = (record: any) => {
|
||||
const status = record.status;
|
||||
|
||||
if (status === '0' || status === '1') {
|
||||
return (
|
||||
<Space>
|
||||
<Button type="link" size="small" onClick={() => openDetailModal(record)}>
|
||||
查看
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
loading={togglingId.value === record.id}
|
||||
onClick={() => handleToggleShelf(record)}
|
||||
>
|
||||
下架
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === '4') {
|
||||
return (
|
||||
<Space>
|
||||
<Button type="link" size="small" onClick={() => openDetailModal(record)}>
|
||||
查看
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
loading={togglingId.value === record.id}
|
||||
onClick={() => handleToggleShelf(record)}
|
||||
>
|
||||
上架
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button type="link" size="small" onClick={() => openDetailModal(record)}>
|
||||
查看
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
/** 打开审核弹窗 */
|
||||
const handleAudit = (record: any) => {
|
||||
console.log('审核赛事:', record.id);
|
||||
setCurrentRecord(record);
|
||||
setAuditModalVisible(true);
|
||||
};
|
||||
|
||||
/** 提交审核 */
|
||||
const handleAuditSubmit = (payload: { approved: boolean; comment: string }) => {
|
||||
console.log('提交审核:', currentRecord.value.id, payload);
|
||||
// TODO: 替换为真实 API 调用
|
||||
message.success('审核提交成功');
|
||||
setAuditModalVisible(false);
|
||||
};
|
||||
|
||||
const handleViewRegulation = (record: any) => {
|
||||
console.log('查看规程:', record.id);
|
||||
setCurrentRecord(record);
|
||||
setRegulationModalVisible(true);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
handleSearch();
|
||||
});
|
||||
|
||||
/** 最终表格列:模型列 + 赛事规程 + 操作 */
|
||||
/** 完整表格列 */
|
||||
const tableColumns = [
|
||||
...columns,
|
||||
{ title: '赛事规程', key: 'regulation', width: 100, align: 'center' as const },
|
||||
...columns.map((col: any) => {
|
||||
if (col.dataIndex === 'name') {
|
||||
return {
|
||||
...col,
|
||||
customRender: ({ text }: { text: string }) => renderName(text),
|
||||
};
|
||||
}
|
||||
return col;
|
||||
}),
|
||||
{
|
||||
title: '赛事规程',
|
||||
key: 'regulation',
|
||||
width: 100,
|
||||
align: 'center' as const,
|
||||
customRender: ({ record }: { record: any }) => (
|
||||
<a style={{ cursor: 'pointer' }} onClick={() => openRegulationModal(record)}>
|
||||
查看
|
||||
</a>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 160,
|
||||
fixed: 'right' as const,
|
||||
align: 'center' as const,
|
||||
customRender: ({ record }: { record: any }) => renderAction(record),
|
||||
},
|
||||
];
|
||||
|
||||
return () => (
|
||||
<div class={pageStyles.containerMain}>
|
||||
{/* ===== 筛选区 ===== */}
|
||||
<div class={pageStyles.filter}>
|
||||
<Form style={{ rowGap: '10px' }} layout="inline" model={filterForm}>
|
||||
<Form.Item label="创建时间" name="createDateRange">
|
||||
@@ -308,7 +261,6 @@ export default defineComponent({
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
{/* ===== 表格区 ===== */}
|
||||
<div class={pageStyles.table}>
|
||||
<div ref={containerRef} class={pageStyles.tableBody}>
|
||||
<Table
|
||||
@@ -317,26 +269,14 @@ export default defineComponent({
|
||||
loading={loading.value}
|
||||
scroll={{ x: 'max-content', y: height.value }}
|
||||
pagination={false}
|
||||
>
|
||||
{{
|
||||
bodyCell: (args: any) =>
|
||||
renderBodyCell({
|
||||
...args,
|
||||
onView: handleView,
|
||||
onViewRegulation: handleViewRegulation,
|
||||
onAudit: handleAudit,
|
||||
onToggleShelf: handleToggleShelf,
|
||||
}),
|
||||
}}
|
||||
</Table>
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 独立分页,右下方 */}
|
||||
<div class={pageStyles.pagination}>
|
||||
<Pagination
|
||||
current={pagination.value.current}
|
||||
pageSize={pagination.value.pageSize}
|
||||
total={pagination.value.total}
|
||||
current={(pagination as any).current.value}
|
||||
pageSize={(pagination as any).pageSize.value}
|
||||
total={(pagination as any).total.value}
|
||||
showSizeChanger
|
||||
showTotal={(total: number) => `共 ${total} 条`}
|
||||
onChange={handlePageChange}
|
||||
@@ -344,26 +284,26 @@ export default defineComponent({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ===== 弹窗区 ===== */}
|
||||
{regulationModalVisible.value && (
|
||||
<EventRegulationModal
|
||||
visible={regulationModalVisible.value}
|
||||
onClose={() => setRegulationModalVisible(false)}
|
||||
record={currentRecord.value}
|
||||
onClose={closeRegulationModal}
|
||||
/>
|
||||
)}
|
||||
{detailModalVisible.value && (
|
||||
<EventDetailModal
|
||||
visible={detailModalVisible.value}
|
||||
record={currentRecord.value}
|
||||
onClose={() => setDetailModalVisible(false)}
|
||||
onClose={closeDetailModal}
|
||||
/>
|
||||
)}
|
||||
{auditModalVisible.value && (
|
||||
<EventAuditModal
|
||||
visible={auditModalVisible.value}
|
||||
record={currentRecord.value}
|
||||
onClose={() => setAuditModalVisible(false)}
|
||||
onSubmit={handleAuditSubmit}
|
||||
onClose={closeAuditModal}
|
||||
onSubmit={submitAudit}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 赛事列表页配置
|
||||
* 职责:集中管理字段映射、选项配置等常量
|
||||
*/
|
||||
|
||||
import type { EventListQueryParams } from './services';
|
||||
|
||||
export const FILTER_DEFAULTS = {
|
||||
createDateRange: null as [string, string] | null,
|
||||
startDateRange: null as [string, string] | null,
|
||||
name: '',
|
||||
createName: '',
|
||||
status: '',
|
||||
region: [] as string[],
|
||||
isPublic: '',
|
||||
idCardNo: '',
|
||||
idCardImg: '',
|
||||
};
|
||||
|
||||
export type FieldMapping = [
|
||||
keyof typeof FILTER_DEFAULTS,
|
||||
keyof EventListQueryParams,
|
||||
((v: any) => any)?,
|
||||
];
|
||||
|
||||
export const FIELD_MAPPINGS: FieldMapping[] = [
|
||||
['name', 'name', (v) => v?.trim?.() ?? v],
|
||||
['createName', 'createName', (v) => v?.trim?.() ?? v],
|
||||
['status', 'status'],
|
||||
['isPublic', 'isPublic'],
|
||||
['idCardNo', 'idCardNo'],
|
||||
['idCardImg', 'idCardImg'],
|
||||
['region', 'areaName', (v) => v?.[v.length - 1]],
|
||||
['createDateRange', 'createDate', (v) => v?.[0]],
|
||||
['createDateRange', 'createDateEnd', (v) => v?.[1]],
|
||||
['startDateRange', 'startDate', (v) => v?.[0]],
|
||||
['startDateRange', 'startDateEnd', (v) => v?.[1]],
|
||||
];
|
||||
|
||||
/** 赛事状态选项(value 对应 API status) */
|
||||
export const EVENT_STATUS_OPTIONS = [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: '0', label: '报名中' },
|
||||
{ value: '1', label: '进行中' },
|
||||
{ value: '2', label: '已结束' },
|
||||
{ value: '3', label: '已删除' },
|
||||
{ value: '4', label: '已下架' },
|
||||
] as const;
|
||||
|
||||
/** 公开活动选项(value 对应 API isPublic: 0=非公开,1=公开) */
|
||||
export const PUBLIC_EVENT_OPTIONS = [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: '1', label: '公开' },
|
||||
{ value: '0', label: '非公开' },
|
||||
] as const;
|
||||
|
||||
/** 证件号选项(value 对应 API idCardNo: 0=无需,1=需要) */
|
||||
export const ID_CARD_OPTIONS = [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: '0', label: '无需提供' },
|
||||
{ value: '1', label: '需提供' },
|
||||
] as const;
|
||||
|
||||
/** 证件图片选项(value 对应 API idCardImg: 0=无需,1=需要) */
|
||||
export const ID_CARD_IMAGE_OPTIONS = [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: '0', label: '无需提供' },
|
||||
{ value: '1', label: '需提供' },
|
||||
] as const;
|
||||
@@ -3,10 +3,6 @@
|
||||
* 该页面使用的所有接口集中管理
|
||||
*/
|
||||
import { get, post } from '@/utils/request';
|
||||
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
|
||||
import { MOCK_EVENT_DETAIL } from '@/config/mock/eventDetail';
|
||||
import { buildMockEventListPage } from '@/config/mock/eventList';
|
||||
import { buildMockSignupListPage } from '@/config/mock/eventSignup';
|
||||
import type {
|
||||
EventListQueryParams,
|
||||
SignupListQueryParams,
|
||||
@@ -16,12 +12,14 @@ import type {
|
||||
TournamentAdminVO,
|
||||
TournamentAdminInfoVO,
|
||||
TournamentAdminInfoSignupVO,
|
||||
TournamentAdminRuleInfoVO,
|
||||
} from '@/api/events/types';
|
||||
|
||||
export type {
|
||||
TournamentAdminVO,
|
||||
TournamentAdminInfoVO,
|
||||
TournamentAdminInfoSignupVO,
|
||||
TournamentAdminRuleInfoVO,
|
||||
InnerCategoryVO,
|
||||
EventListQueryParams,
|
||||
SignupListQueryParams,
|
||||
@@ -30,62 +28,33 @@ export type {
|
||||
ApiResult,
|
||||
} from '@/api/events/types';
|
||||
|
||||
// ============================================================
|
||||
// URL 常量
|
||||
// ============================================================
|
||||
const eventList = '/manager/tm/page';
|
||||
const eventDetail = '/manager/tm/info';
|
||||
const signupList = '/manager/tm/info/signupPage';
|
||||
const toggleOnline = '/manager/tm/online';
|
||||
|
||||
const delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
// ============================================================
|
||||
// API 函数
|
||||
// ============================================================
|
||||
const eventList = '/admin/manager/tm/page';
|
||||
const eventDetail = '/admin/manager/tm/info';
|
||||
const signupList = '/admin/manager/tm/info/signupPage';
|
||||
const toggleOnline = '/admin/manager/tm/online';
|
||||
const ruleInfo = `/admin/manager/tm/ruleInfo`;
|
||||
|
||||
/** 赛事列表 - 列表分页 */
|
||||
export async function getEventList(
|
||||
params: EventListQueryParams,
|
||||
): Promise<ApiResult<PageData<TournamentAdminVO>>> {
|
||||
if (USE_MOCK) {
|
||||
await delay(MOCK_DELAY);
|
||||
const page = Number(params.page) || 1;
|
||||
const limit = Number(params.limit) || 10;
|
||||
return {
|
||||
code: 200,
|
||||
msg: 'success',
|
||||
data: buildMockEventListPage(page, limit),
|
||||
};
|
||||
}
|
||||
return get(eventList, params as Record<string, any>);
|
||||
}
|
||||
|
||||
/** 赛事列表 - 赛事详情 */
|
||||
export async function getEventDetail(id: string): Promise<ApiResult<TournamentAdminInfoVO>> {
|
||||
if (USE_MOCK) {
|
||||
await delay(MOCK_DELAY);
|
||||
return {
|
||||
code: 200,
|
||||
msg: 'success',
|
||||
data: { ...MOCK_EVENT_DETAIL },
|
||||
};
|
||||
}
|
||||
return get(eventDetail, { id });
|
||||
}
|
||||
|
||||
/** 赛事列表 - 规则详情 */
|
||||
export async function getRuleInfo(id: string): Promise<ApiResult<TournamentAdminRuleInfoVO>> {
|
||||
return get(ruleInfo, { id });
|
||||
}
|
||||
|
||||
/** 赛事列表 - 赛事详情 - 人员列表 */
|
||||
export async function getSignupList(
|
||||
params: SignupListQueryParams,
|
||||
): Promise<ApiResult<PageData<TournamentAdminInfoSignupVO>>> {
|
||||
if (USE_MOCK) {
|
||||
await delay(MOCK_DELAY);
|
||||
return {
|
||||
code: 200,
|
||||
msg: 'success',
|
||||
data: buildMockSignupListPage(params),
|
||||
};
|
||||
}
|
||||
return get(signupList, params as Record<string, any>);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import { h } from 'vue';
|
||||
import { StatusTag, type StatusTagTone } from '@/components';
|
||||
import type { TournamentAdminVO } from './services';
|
||||
|
||||
/** 状态标签映射 */
|
||||
const STATUS_MAP: Record<string, { label: string; tone: StatusTagTone }> = {
|
||||
'0': { label: '报名中', tone: 'primary' },
|
||||
'1': { label: '进行中', tone: 'cyan' },
|
||||
'2': { label: '已结束', tone: 'neutral' },
|
||||
'3': { label: '已删除', tone: 'neutral' },
|
||||
'4': { label: '已下架', tone: 'orange' },
|
||||
};
|
||||
|
||||
/** 0/1 → 显示文字 */
|
||||
const formatDisplayValue = (value: string, map: Record<string, string>) => map[value] ?? value;
|
||||
|
||||
const ID_CARD_DISPLAY_MAP: Record<string, string> = { '0': '否', '1': '是' };
|
||||
const IS_PUBLIC_DISPLAY_MAP: Record<string, string> = { '0': '非公开', '1': '公开' };
|
||||
|
||||
/**
|
||||
* 赛事列表 - 表格列配置(视图层)
|
||||
* 纯展示逻辑,不包含状态管理
|
||||
*/
|
||||
export function useEventListColumns() {
|
||||
const columns = [
|
||||
{ title: '赛事ID', dataIndex: 'id', key: 'id', width: 180 },
|
||||
{
|
||||
title: '赛事名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
title: '赛事时间',
|
||||
key: 'eventTime',
|
||||
width: 260,
|
||||
customRender: ({ record }: { record: TournamentAdminVO }) =>
|
||||
`${record.startTimeBegin} 至 ${record.startTimeEnd}`,
|
||||
},
|
||||
{
|
||||
title: '省市区',
|
||||
dataIndex: 'venueArea',
|
||||
key: 'venueArea',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '地点',
|
||||
dataIndex: 'venue',
|
||||
key: 'venue',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '是否公开',
|
||||
dataIndex: 'isPublic',
|
||||
key: 'isPublic',
|
||||
width: 100,
|
||||
customRender: ({ text }: { text: string }) => formatDisplayValue(text, IS_PUBLIC_DISPLAY_MAP),
|
||||
},
|
||||
{
|
||||
title: '是否提供证件号',
|
||||
dataIndex: 'isRequireIdCardNo',
|
||||
key: 'isRequireIdCardNo',
|
||||
width: 160,
|
||||
customRender: ({ text }: { text: string }) => formatDisplayValue(text, ID_CARD_DISPLAY_MAP),
|
||||
},
|
||||
{
|
||||
title: '是否提供证件图片',
|
||||
dataIndex: 'isRequireIdCardImg',
|
||||
key: 'isRequireIdCardImg',
|
||||
width: 160,
|
||||
customRender: ({ text }: { text: string }) => formatDisplayValue(text, ID_CARD_DISPLAY_MAP),
|
||||
},
|
||||
{
|
||||
title: '创建人昵称',
|
||||
dataIndex: 'createNickname',
|
||||
key: 'createNickname',
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
title: '创建人手机号',
|
||||
dataIndex: 'createPhone',
|
||||
key: 'createPhone',
|
||||
width: 130,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
width: 170,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 90,
|
||||
customRender: ({ text }: { text: string }) => {
|
||||
const info = STATUS_MAP[text] || { label: text, tone: 'default' as const };
|
||||
return h(StatusTag, { label: info.label, tone: info.tone });
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '报名人数',
|
||||
dataIndex: 'signupCount',
|
||||
key: 'signupCount',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '取消报名人数',
|
||||
dataIndex: 'dropoutCount',
|
||||
key: 'dropoutCount',
|
||||
width: 140,
|
||||
},
|
||||
];
|
||||
|
||||
return { columns };
|
||||
}
|
||||
@@ -1,84 +1,36 @@
|
||||
import { computed, reactive, toRef, Ref, h } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { StatusTag, type StatusTagTone } from '@/components';
|
||||
import { useState, useDebounce, useThrottleFn } from '@/hooks';
|
||||
import type { TournamentAdminVO, EventListQueryParams } from './services';
|
||||
import { getEventList } from './services';
|
||||
import { computed, reactive, toRef, Ref } from 'vue';
|
||||
import { Modal, message } from 'ant-design-vue';
|
||||
import { useState, useDebounce } from '@/hooks';
|
||||
import { useRequest } from '@/hooks/useRequest';
|
||||
import { useEffect } from '@/hooks/useEffect';
|
||||
import { usePagination } from '@/hooks/usePagination';
|
||||
import type { TournamentAdminVO, EventListQueryParams, PageData, ApiResult } from './services';
|
||||
import { getEventList, toggleEventOnline } from './services';
|
||||
import {
|
||||
FILTER_DEFAULTS,
|
||||
FIELD_MAPPINGS,
|
||||
EVENT_STATUS_OPTIONS,
|
||||
PUBLIC_EVENT_OPTIONS,
|
||||
ID_CARD_OPTIONS,
|
||||
ID_CARD_IMAGE_OPTIONS,
|
||||
} from './config';
|
||||
|
||||
// ============================================================
|
||||
// 常量
|
||||
// ============================================================
|
||||
|
||||
/** 赛事状态选项(value 对应 API status: 不传=全部、0=报名中,1=进行中,2=已结束,3=已删除,4=已下架) */
|
||||
export const EVENT_STATUS_OPTIONS = [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: '0', label: '报名中' },
|
||||
{ value: '1', label: '进行中' },
|
||||
{ value: '2', label: '已结束' },
|
||||
{ value: '3', label: '已删除' },
|
||||
{ value: '4', label: '已下架' },
|
||||
] as const;
|
||||
|
||||
/** 公开活动选项(value 对应 API isPublic: 0=非公开,1=公开) */
|
||||
export const PUBLIC_EVENT_OPTIONS = [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: '1', label: '公开' },
|
||||
{ value: '0', label: '非公开' },
|
||||
] as const;
|
||||
|
||||
/** 证件号选项(value 对应 API idCardNo: 0=无需,1=需要) */
|
||||
export const ID_CARD_OPTIONS = [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: '0', label: '无需提供' },
|
||||
{ value: '1', label: '需提供' },
|
||||
] as const;
|
||||
|
||||
/** 证件图片选项(value 对应 API idCardImg: 0=无需,1=需要) */
|
||||
export const ID_CARD_IMAGE_OPTIONS = [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: '0', label: '无需提供' },
|
||||
{ value: '1', label: '需提供' },
|
||||
] as const;
|
||||
|
||||
/** 状态标签映射 */
|
||||
const STATUS_MAP: Record<string, { label: string; tone: StatusTagTone }> = {
|
||||
'0': { label: '报名中', tone: 'primary' },
|
||||
'1': { label: '进行中', tone: 'cyan' },
|
||||
'2': { label: '已结束', tone: 'neutral' },
|
||||
'3': { label: '已删除', tone: 'neutral' },
|
||||
'4': { label: '已下架', tone: 'orange' },
|
||||
};
|
||||
|
||||
/** 0/1 → 显示文字 */
|
||||
const formatDisplayValue = (value: string, map: Record<string, string>) => map[value] ?? value;
|
||||
|
||||
const ID_CARD_DISPLAY_MAP: Record<string, string> = { '0': '否', '1': '是' };
|
||||
const IS_PUBLIC_DISPLAY_MAP: Record<string, string> = { '0': '非公开', '1': '公开' };
|
||||
|
||||
// ============================================================
|
||||
// Model
|
||||
// ============================================================
|
||||
export { EVENT_STATUS_OPTIONS, PUBLIC_EVENT_OPTIONS, ID_CARD_OPTIONS, ID_CARD_IMAGE_OPTIONS };
|
||||
|
||||
/**
|
||||
* 赛事列表页数据模型
|
||||
* 筛选字段名与 API query 参数(EventListQueryParams)保持一致,
|
||||
* 仅日期区间 RangePicker 拆分为 start/end 两个字段、省市区 Cascader 取末级为 areaName。
|
||||
* 职责:状态管理 + 数据获取 + 业务逻辑 + 交互处理
|
||||
*/
|
||||
export function useEventListModel() {
|
||||
// ===== 筛选条件(key 名对齐 API 查询参数) =====
|
||||
const filterForm = reactive({
|
||||
createDateRange: null as [string, string] | null,
|
||||
startDateRange: null as [string, string] | null,
|
||||
name: '',
|
||||
createName: '',
|
||||
status: '',
|
||||
region: [] as string[],
|
||||
isPublic: '',
|
||||
idCardNo: '',
|
||||
idCardImg: '',
|
||||
});
|
||||
const filterForm = reactive({ ...FILTER_DEFAULTS });
|
||||
const [regulationModalVisible, setRegulationModalVisible] = useState(false);
|
||||
const [detailModalVisible, setDetailModalVisible] = useState(false);
|
||||
const [auditModalVisible, setAuditModalVisible] = useState(false);
|
||||
const [currentRecord, setCurrentRecord] = useState<any>({});
|
||||
const [togglingId, setTogglingId] = useState('');
|
||||
|
||||
const pagination = usePagination({ defaultCurrent: 1, defaultPageSize: 10 });
|
||||
|
||||
// 可搜索字段防抖
|
||||
const { debouncedValue: debouncedName } = useDebounce(toRef(filterForm, 'name') as Ref<string>, {
|
||||
delay: 300,
|
||||
});
|
||||
@@ -87,215 +39,177 @@ export function useEventListModel() {
|
||||
{ delay: 300 },
|
||||
);
|
||||
|
||||
// ===== 表格状态 =====
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [dataSource, setDataSource] = useState<TournamentAdminVO[]>([]);
|
||||
const [pagination, setPagination] = useState({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
// ===== 表格列配置(dataIndex 对齐 TournamentAdminVO 字段) =====
|
||||
const columns = [
|
||||
{ title: '赛事ID', dataIndex: 'id', key: 'id', width: 180 },
|
||||
{
|
||||
title: '赛事名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
title: '赛事时间',
|
||||
key: 'eventTime',
|
||||
width: 260,
|
||||
customRender: ({ record }: { record: TournamentAdminVO }) =>
|
||||
`${record.startTimeBegin} 至 ${record.startTimeEnd}`,
|
||||
},
|
||||
{
|
||||
title: '省市区',
|
||||
dataIndex: 'venueArea',
|
||||
key: 'venueArea',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '地点',
|
||||
dataIndex: 'venue',
|
||||
key: 'venue',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '是否公开',
|
||||
dataIndex: 'isPublic',
|
||||
key: 'isPublic',
|
||||
width: 100,
|
||||
customRender: ({ text }: { text: string }) => formatDisplayValue(text, IS_PUBLIC_DISPLAY_MAP),
|
||||
},
|
||||
{
|
||||
title: '是否提供证件号',
|
||||
dataIndex: 'isRequireIdCardNo',
|
||||
key: 'isRequireIdCardNo',
|
||||
width: 160,
|
||||
customRender: ({ text }: { text: string }) => formatDisplayValue(text, ID_CARD_DISPLAY_MAP),
|
||||
},
|
||||
{
|
||||
title: '是否提供证件图片',
|
||||
dataIndex: 'isRequireIdCardImg',
|
||||
key: 'isRequireIdCardImg',
|
||||
width: 160,
|
||||
customRender: ({ text }: { text: string }) => formatDisplayValue(text, ID_CARD_DISPLAY_MAP),
|
||||
},
|
||||
{
|
||||
title: '创建人昵称',
|
||||
dataIndex: 'createNickname',
|
||||
key: 'createNickname',
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
title: '创建人手机号',
|
||||
dataIndex: 'createPhone',
|
||||
key: 'createPhone',
|
||||
width: 130,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
width: 170,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: 90,
|
||||
customRender: ({ text }: { text: string }) => {
|
||||
const info = STATUS_MAP[text] || { label: text, tone: 'default' as const };
|
||||
return h(StatusTag, { label: info.label, tone: info.tone });
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '报名人数',
|
||||
dataIndex: 'signupCount',
|
||||
key: 'signupCount',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '取消报名人数',
|
||||
dataIndex: 'dropoutCount',
|
||||
key: 'dropoutCount',
|
||||
width: 140,
|
||||
},
|
||||
];
|
||||
|
||||
// ===== 构建 API 查询参数 =====
|
||||
const buildQueryParams = (): EventListQueryParams => {
|
||||
const params: EventListQueryParams = {
|
||||
page: String(pagination.value.current),
|
||||
limit: String(pagination.value.pageSize),
|
||||
};
|
||||
const { page, pageSize } = pagination.params.value;
|
||||
|
||||
if (filterForm.createDateRange) {
|
||||
params.createDate = filterForm.createDateRange[0];
|
||||
params.createDateEnd = filterForm.createDateRange[1];
|
||||
}
|
||||
if (filterForm.startDateRange) {
|
||||
params.startDate = filterForm.startDateRange[0];
|
||||
params.startDateEnd = filterForm.startDateRange[1];
|
||||
}
|
||||
if (filterForm.region.length > 0) {
|
||||
params.areaName = filterForm.region[filterForm.region.length - 1];
|
||||
}
|
||||
if (filterForm.name.trim()) {
|
||||
params.name = filterForm.name.trim();
|
||||
}
|
||||
if (filterForm.createName.trim()) {
|
||||
params.createName = filterForm.createName.trim();
|
||||
}
|
||||
if (filterForm.status) {
|
||||
params.status = filterForm.status;
|
||||
}
|
||||
if (filterForm.isPublic) {
|
||||
params.isPublic = filterForm.isPublic;
|
||||
}
|
||||
if (filterForm.idCardNo) {
|
||||
params.idCardNo = filterForm.idCardNo;
|
||||
}
|
||||
if (filterForm.idCardImg) {
|
||||
params.idCardImg = filterForm.idCardImg;
|
||||
}
|
||||
const params = FIELD_MAPPINGS.reduce(
|
||||
(acc, [formKey, paramKey, transform]) => {
|
||||
const rawValue = filterForm[formKey];
|
||||
if (!hasValue(rawValue)) return acc;
|
||||
|
||||
return params;
|
||||
const value = safeTransform(rawValue, transform);
|
||||
if (isEmptyValue(value)) return acc;
|
||||
|
||||
return { ...acc, [paramKey]: value };
|
||||
},
|
||||
{} as Record<string, any>,
|
||||
);
|
||||
|
||||
return { page: String(page), limit: String(pageSize), ...params } as EventListQueryParams;
|
||||
};
|
||||
|
||||
// ===== 计算属性 =====
|
||||
const {
|
||||
data,
|
||||
loading,
|
||||
run: fetchList,
|
||||
} = useRequest<ApiResult<PageData<TournamentAdminVO>>>(() => getEventList(buildQueryParams()), {
|
||||
refreshDeps: [],
|
||||
formatResult: (res) => res,
|
||||
});
|
||||
|
||||
// 提取列表数据
|
||||
const listData = computed<PageData<TournamentAdminVO> | undefined>(() => {
|
||||
const res = data.value;
|
||||
return res ? (res as any).data : undefined;
|
||||
});
|
||||
|
||||
const dataSource = computed(() => listData.value?.list || []);
|
||||
|
||||
// 同步分页总条数
|
||||
useEffect(() => {
|
||||
const total = (data.value as any)?.data?.total;
|
||||
if (total !== undefined) pagination.setTotal(total);
|
||||
}, [data]);
|
||||
|
||||
const hasFilter = computed(() => {
|
||||
const f = filterForm;
|
||||
return (
|
||||
filterForm.createDateRange !== null ||
|
||||
filterForm.startDateRange !== null ||
|
||||
debouncedName.value.trim() !== '' ||
|
||||
debouncedCreator.value.trim() !== '' ||
|
||||
filterForm.status !== '' ||
|
||||
filterForm.region.length > 0 ||
|
||||
filterForm.isPublic !== '' ||
|
||||
filterForm.idCardNo !== '' ||
|
||||
filterForm.idCardImg !== ''
|
||||
!!f.createDateRange ||
|
||||
!!f.startDateRange ||
|
||||
!!debouncedName.value?.trim() ||
|
||||
!!debouncedCreator.value?.trim() ||
|
||||
!!f.status ||
|
||||
f.region.length > 0 ||
|
||||
!!f.isPublic ||
|
||||
!!f.idCardNo ||
|
||||
!!f.idCardImg
|
||||
);
|
||||
});
|
||||
|
||||
// ===== 方法 =====
|
||||
const handleSearch = () => fetchList();
|
||||
|
||||
/** 查询 */
|
||||
const handleSearch = useThrottleFn(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const queryParams = buildQueryParams();
|
||||
console.log('赛事列表查询参数:', queryParams);
|
||||
const res = await getEventList(queryParams);
|
||||
|
||||
if (res.code == 200) {
|
||||
setDataSource(res.data.list);
|
||||
setPagination({ ...pagination.value, total: res.data.total });
|
||||
} else {
|
||||
message.error(res.msg || '查询失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('赛事列表查询失败:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
/** 重置 */
|
||||
const handleReset = useThrottleFn(() => {
|
||||
filterForm.createDateRange = null;
|
||||
filterForm.startDateRange = null;
|
||||
filterForm.name = '';
|
||||
filterForm.createName = '';
|
||||
filterForm.status = '';
|
||||
filterForm.region = [];
|
||||
filterForm.isPublic = '';
|
||||
filterForm.idCardNo = '';
|
||||
filterForm.idCardImg = '';
|
||||
setPagination({ current: 1, pageSize: 10, total: 0 });
|
||||
setDataSource([]);
|
||||
// 等待 debounce 生效后自动查询
|
||||
setTimeout(() => handleSearch(), 350);
|
||||
}, 500);
|
||||
const handleReset = () => {
|
||||
Object.assign(filterForm, FILTER_DEFAULTS);
|
||||
pagination.reset();
|
||||
setTimeout(fetchList, 350);
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number, pageSize: number) => {
|
||||
setPagination({ ...pagination.value, current: page, pageSize });
|
||||
handleSearch();
|
||||
pagination.setCurrent(page);
|
||||
if (pageSize !== (pagination as any).pageSize.value) {
|
||||
pagination.setPageSize(pageSize);
|
||||
}
|
||||
setTimeout(fetchList, 0);
|
||||
};
|
||||
|
||||
const openDetailModal = (record: any) => {
|
||||
setCurrentRecord(record);
|
||||
setDetailModalVisible(true);
|
||||
};
|
||||
|
||||
const openRegulationModal = (record: any) => {
|
||||
setCurrentRecord(record);
|
||||
setRegulationModalVisible(true);
|
||||
};
|
||||
|
||||
const openAuditModal = (record: any) => {
|
||||
setCurrentRecord(record);
|
||||
setAuditModalVisible(true);
|
||||
};
|
||||
|
||||
const closeRegulationModal = () => setRegulationModalVisible(false);
|
||||
const closeDetailModal = () => setDetailModalVisible(false);
|
||||
const closeAuditModal = () => setAuditModalVisible(false);
|
||||
|
||||
// TODO
|
||||
const submitAudit = (payload: { approved: boolean; comment: string }) => {
|
||||
console.log('提交审核:', currentRecord.value.id, payload);
|
||||
message.success('审核提交成功');
|
||||
setAuditModalVisible(false);
|
||||
};
|
||||
|
||||
const handleToggleShelf = (record: any) => {
|
||||
const isRemoved = record.status === '4';
|
||||
const actionText = isRemoved ? '上架' : '下架';
|
||||
const descText = isRemoved
|
||||
? `确认上架"${record.name}"吗?上架后用户可搜索和报名该赛事。`
|
||||
: `确认下架"${record.name}"吗?下架后用户将无法搜索和报名该赛事。`;
|
||||
|
||||
Modal.confirm({
|
||||
title: `${actionText}确认`,
|
||||
content: descText,
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
setTogglingId(record.id);
|
||||
try {
|
||||
const res = await toggleEventOnline({ id: record.id, online: isRemoved ? '1' : '0' });
|
||||
if (res.code === 200) {
|
||||
message.success(`${actionText}成功`);
|
||||
fetchList();
|
||||
} else {
|
||||
message.error(res.msg || `${actionText}失败`);
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(e?.message || `${actionText}失败`);
|
||||
} finally {
|
||||
setTogglingId('');
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
filterForm,
|
||||
loading,
|
||||
dataSource,
|
||||
columns,
|
||||
pagination,
|
||||
hasFilter,
|
||||
regulationModalVisible,
|
||||
detailModalVisible,
|
||||
auditModalVisible,
|
||||
currentRecord,
|
||||
togglingId,
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handlePageChange,
|
||||
openDetailModal,
|
||||
openRegulationModal,
|
||||
openAuditModal,
|
||||
closeRegulationModal,
|
||||
closeDetailModal,
|
||||
closeAuditModal,
|
||||
submitAudit,
|
||||
handleToggleShelf,
|
||||
};
|
||||
}
|
||||
|
||||
function hasValue(v: any): boolean {
|
||||
if (v == null) return false;
|
||||
if (Array.isArray(v)) return v.length > 0 && v.some((item) => item != null);
|
||||
if (typeof v === 'string') return v.trim() !== '';
|
||||
return true;
|
||||
}
|
||||
|
||||
function isEmptyValue(v: any): boolean {
|
||||
return v == null || (typeof v === 'string' && v.trim() === '');
|
||||
}
|
||||
|
||||
function safeTransform(value: any, transform?: (v: any) => any): any {
|
||||
if (!transform) return value;
|
||||
try {
|
||||
return transform(value) ?? value;
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* 该页面使用的所有接口集中管理
|
||||
*/
|
||||
import { get } from '@/utils/request';
|
||||
import { USE_MOCK, MOCK_DELAY } from '@/config/mock';
|
||||
import { buildMockEventOperationLogPage } from '@/config/mock/eventOperationLogs';
|
||||
import type {
|
||||
OperationLogQueryParams,
|
||||
TournamentAdminOperationPageVO,
|
||||
@@ -24,30 +22,13 @@ export type {
|
||||
// ============================================================
|
||||
const operationLogs = '/tournament/operation/page';
|
||||
|
||||
const delay = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
|
||||
|
||||
// ============================================================
|
||||
// API 函数
|
||||
// ============================================================
|
||||
|
||||
function pickFilters(params: OperationLogQueryParams): OperationLogQueryParams {
|
||||
const { page: _page, limit: _limit, ...filters } = params;
|
||||
return filters;
|
||||
}
|
||||
|
||||
/** 赛事操作日志分页 */
|
||||
export async function getOperationLogs(
|
||||
params: OperationLogQueryParams,
|
||||
): Promise<ApiResult<PageData<TournamentAdminOperationPageVO>>> {
|
||||
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: buildMockEventOperationLogPage(page, limit, pickFilters(params)),
|
||||
};
|
||||
}
|
||||
return get(operationLogs, params as Record<string, any>);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineComponent, onMounted } from 'vue';
|
||||
import { defineComponent } from 'vue';
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
@@ -143,10 +143,6 @@ export default defineComponent({
|
||||
},
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
handleSearch();
|
||||
});
|
||||
|
||||
return () => (
|
||||
<div class={pageStyles.containerMain}>
|
||||
{/* ===== 筛选区 ===== */}
|
||||
@@ -264,12 +260,12 @@ export default defineComponent({
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
{/* 独立分页,右下方 */}
|
||||
{/* 独立分页 */}
|
||||
<div class={pageStyles.pagination}>
|
||||
<Pagination
|
||||
current={pagination.value.current}
|
||||
pageSize={pagination.value.pageSize}
|
||||
total={pagination.value.total}
|
||||
current={(pagination as any).current.value}
|
||||
pageSize={(pagination as any).pageSize.value}
|
||||
total={(pagination as any).total.value}
|
||||
showSizeChanger
|
||||
showTotal={(total: number) => `共 ${total} 条`}
|
||||
onChange={handlePageChange}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 订单管理页配置
|
||||
* 职责:集中管理字段映射、选项配置等常量
|
||||
*/
|
||||
|
||||
import type { OrderListQueryParams } from './services';
|
||||
|
||||
// ============================================================
|
||||
// 筛选字段默认值
|
||||
// ============================================================
|
||||
export const FILTER_DEFAULTS = {
|
||||
orderDateRange: null as [string, string] | null,
|
||||
orderNo: '',
|
||||
tName: '',
|
||||
sName: '',
|
||||
status: '',
|
||||
refundStatus: '',
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 字段映射配置
|
||||
// ============================================================
|
||||
export type FieldMapping = [
|
||||
keyof typeof FILTER_DEFAULTS,
|
||||
keyof OrderListQueryParams,
|
||||
((v: any) => any)?,
|
||||
];
|
||||
|
||||
export const FIELD_MAPPINGS: FieldMapping[] = [
|
||||
['orderNo', 'orderNo', (v) => v?.trim?.() ?? v],
|
||||
['tName', 'tName', (v) => v?.trim?.() ?? v],
|
||||
['sName', 'sName', (v) => v?.trim?.() ?? v],
|
||||
['status', 'status'],
|
||||
['refundStatus', 'refundStatus'],
|
||||
['orderDateRange', 'orderDate', (v) => v?.[0]],
|
||||
['orderDateRange', 'orderDateEnd', (v) => v?.[1]],
|
||||
];
|
||||
|
||||
// ============================================================
|
||||
// 下拉选项配置
|
||||
// ============================================================
|
||||
|
||||
/** 订单状态选项(value 对应 API status: 不传=全部、1=支付成功,2=部分退款,3=全部退款,4=超时关闭) */
|
||||
export const ORDER_STATUS_OPTIONS = [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: '1', label: '支付成功' },
|
||||
{ value: '2', label: '部分退款' },
|
||||
{ value: '3', label: '全部退款' },
|
||||
{ value: '4', label: '超时关闭' },
|
||||
] as const;
|
||||
|
||||
/** 退款状态选项(value 对应 API refundStatus: 不传=全部、0=退款失败,1=退款成功) */
|
||||
export const REFUND_STATUS_OPTIONS = [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: '1', label: '退款成功' },
|
||||
{ value: '0', label: '退款失败' },
|
||||
] as const;
|
||||
@@ -35,10 +35,10 @@ export type {
|
||||
// ============================================================
|
||||
// URL 常量
|
||||
// ============================================================
|
||||
const orderList = '/manager/order/page';
|
||||
const orderDetail = '/manager/order/info';
|
||||
const orderPlayerList = '/manager/order/info/page';
|
||||
const retryRefund = '/manager/order/retryRefund';
|
||||
const orderList = '/admin/manager/order/page';
|
||||
const orderDetail = '/admin/manager/order/info';
|
||||
const orderPlayerList = '/admin/manager/order/info/userPage';
|
||||
const retryRefund = '/admin/manager/order/retryRefund';
|
||||
|
||||
const delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
|
||||
@@ -1,33 +1,24 @@
|
||||
import { computed, reactive, toRef, Ref, h } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { StatusTag, type StatusTagTone } from '@/components';
|
||||
import { useState, useDebounce, useThrottleFn } from '@/hooks';
|
||||
import {
|
||||
getOrderList,
|
||||
type TournamentAdminOrderPageVO,
|
||||
type OrderListQueryParams,
|
||||
import { useState, useDebounce } from '@/hooks';
|
||||
import { useRequest } from '@/hooks/useRequest';
|
||||
import { useEffect } from '@/hooks/useEffect';
|
||||
import { usePagination } from '@/hooks/usePagination';
|
||||
import type {
|
||||
TournamentAdminOrderPageVO,
|
||||
OrderListQueryParams,
|
||||
PageData,
|
||||
ApiResult,
|
||||
} from './services';
|
||||
import { MOCK_ORDER_SUMMARY } from '@/config/mock/orderList';
|
||||
import { getOrderList } from './services';
|
||||
import {
|
||||
FILTER_DEFAULTS,
|
||||
FIELD_MAPPINGS,
|
||||
ORDER_STATUS_OPTIONS,
|
||||
REFUND_STATUS_OPTIONS,
|
||||
} from './config';
|
||||
|
||||
// ============================================================
|
||||
// 常量
|
||||
// ============================================================
|
||||
|
||||
/** 订单状态选项(value 对应 API status: 不传=全部、1=支付成功,2=部分退款,3=全部退款,4=超时关闭) */
|
||||
export const ORDER_STATUS_OPTIONS = [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: '1', label: '支付成功' },
|
||||
{ value: '2', label: '部分退款' },
|
||||
{ value: '3', label: '全部退款' },
|
||||
{ value: '4', label: '超时关闭' },
|
||||
] as const;
|
||||
|
||||
/** 退款状态选项(value 对应 API refundStatus: 不传=全部、0=退款失败,1=退款成功) */
|
||||
export const REFUND_STATUS_OPTIONS = [
|
||||
{ value: '', label: '全部' },
|
||||
{ value: '1', label: '退款成功' },
|
||||
{ value: '0', label: '退款失败' },
|
||||
] as const;
|
||||
export { ORDER_STATUS_OPTIONS, REFUND_STATUS_OPTIONS };
|
||||
|
||||
/** 订单状态 StatusTag 映射 */
|
||||
const ORDER_STATUS_MAP: Record<number, { label: string; tone: StatusTagTone }> = {
|
||||
@@ -44,26 +35,15 @@ const REFUND_STATUS_MAP: Record<number, { label: string; tone: StatusTagTone }>
|
||||
3: { label: '退款失败', tone: 'danger' },
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Model
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 订单列表页数据模型
|
||||
* 筛选字段名与 API query 参数(OrderListQueryParams)保持一致。
|
||||
* 职责:状态管理 + 数据获取 + 业务逻辑 + 交互处理
|
||||
*/
|
||||
export function useOrderModel() {
|
||||
// ===== 筛选条件(key 名对齐 API 查询参数) =====
|
||||
const filterForm = reactive({
|
||||
orderDateRange: null as [string, string] | null,
|
||||
orderNo: '',
|
||||
tName: '',
|
||||
sName: '',
|
||||
status: '',
|
||||
refundStatus: '',
|
||||
});
|
||||
const filterForm = reactive({ ...FILTER_DEFAULTS });
|
||||
|
||||
const pagination = usePagination({ defaultCurrent: 1, defaultPageSize: 10 });
|
||||
|
||||
// 可搜索字段防抖
|
||||
const { debouncedValue: debouncedOrderNo } = useDebounce(
|
||||
toRef(filterForm, 'orderNo') as Ref<string>,
|
||||
{ delay: 300 },
|
||||
@@ -77,19 +57,70 @@ export function useOrderModel() {
|
||||
{ delay: 300 },
|
||||
);
|
||||
|
||||
// ===== 表格状态 =====
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [dataSource, setDataSource] = useState<TournamentAdminOrderPageVO[]>([]);
|
||||
const [pagination, setPagination] = useState({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
const buildQueryParams = (): OrderListQueryParams => {
|
||||
const { page, pageSize } = pagination.params.value;
|
||||
|
||||
const params = FIELD_MAPPINGS.reduce(
|
||||
(acc, [formKey, paramKey, transform]) => {
|
||||
const rawValue = filterForm[formKey];
|
||||
if (!hasValue(rawValue)) return acc;
|
||||
|
||||
const value = safeTransform(rawValue, transform);
|
||||
if (isEmptyValue(value)) return acc;
|
||||
|
||||
return { ...acc, [paramKey]: value };
|
||||
},
|
||||
{} as Record<string, any>,
|
||||
);
|
||||
|
||||
return { page: String(page), limit: String(pageSize), ...params } as OrderListQueryParams;
|
||||
};
|
||||
|
||||
// ===== 列表数据请求 =====
|
||||
const {
|
||||
data,
|
||||
loading,
|
||||
run: fetchList,
|
||||
} = useRequest<ApiResult<PageData<TournamentAdminOrderPageVO>>>(
|
||||
() => getOrderList(buildQueryParams()),
|
||||
{ refreshDeps: [], formatResult: (res) => res },
|
||||
);
|
||||
|
||||
// 提取列表数据
|
||||
const listData = computed<PageData<TournamentAdminOrderPageVO> | undefined>(() => {
|
||||
const res = data.value;
|
||||
return res ? (res as any).data : undefined;
|
||||
});
|
||||
|
||||
// ===== 金额汇总(TODO: 汇总应来自独立接口) =====
|
||||
const [summary, setSummary] = useState(MOCK_ORDER_SUMMARY);
|
||||
const dataSource = computed(() => listData.value?.list || []);
|
||||
|
||||
// ===== 表格列配置(dataIndex 对齐 TournamentAdminOrderPageVO) =====
|
||||
// 同步分页总条数
|
||||
useEffect(() => {
|
||||
const total = (data.value as any)?.data?.total;
|
||||
if (total !== undefined) pagination.setTotal(total);
|
||||
}, [data]);
|
||||
|
||||
// ===== 金额汇总(TODO: 汇总应来自独立接口) =====
|
||||
const [summary] = useState({
|
||||
totalOrderAmount: 0,
|
||||
totalPaidAmount: 0,
|
||||
totalRefundAmount: 0,
|
||||
});
|
||||
|
||||
// ===== 计算属性 =====
|
||||
const hasFilter = computed(() => {
|
||||
const f = filterForm;
|
||||
return (
|
||||
!!f.orderDateRange ||
|
||||
!!debouncedOrderNo.value?.trim() ||
|
||||
!!debouncedTName.value?.trim() ||
|
||||
!!debouncedSName.value?.trim() ||
|
||||
!!f.status ||
|
||||
!!f.refundStatus
|
||||
);
|
||||
});
|
||||
|
||||
// ===== 表格列配置 =====
|
||||
const formatMoney = (text: string) => {
|
||||
const n = parseFloat(text);
|
||||
return isNaN(n) ? '0.00元' : `${n.toFixed(2)}元`;
|
||||
@@ -97,12 +128,7 @@ export function useOrderModel() {
|
||||
|
||||
const columns = [
|
||||
{ title: '订单编号', dataIndex: 'orderNo', key: 'orderNo', width: 200 },
|
||||
{
|
||||
title: '赛事名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: 180,
|
||||
},
|
||||
{ title: '赛事名称', dataIndex: 'name', key: 'name', width: 180 },
|
||||
{ title: '选手名称', dataIndex: 'realName', key: 'realName', width: 100 },
|
||||
{ title: '手机号', dataIndex: 'phone', key: 'phone', width: 130 },
|
||||
{ title: '比赛组别', dataIndex: 'categoryName', key: 'categoryName', width: 120 },
|
||||
@@ -176,91 +202,25 @@ export function useOrderModel() {
|
||||
},
|
||||
];
|
||||
|
||||
// ===== 构建 API 查询参数 =====
|
||||
const buildQueryParams = (): OrderListQueryParams => {
|
||||
const params: OrderListQueryParams = {
|
||||
page: String(pagination.value.current),
|
||||
limit: String(pagination.value.pageSize),
|
||||
};
|
||||
// ===== 列表操作 =====
|
||||
const handleSearch = () => fetchList();
|
||||
|
||||
if (filterForm.orderDateRange) {
|
||||
params.orderDate = filterForm.orderDateRange[0];
|
||||
params.orderDateEnd = filterForm.orderDateRange[1];
|
||||
}
|
||||
if (filterForm.orderNo.trim()) {
|
||||
params.orderNo = filterForm.orderNo.trim();
|
||||
}
|
||||
if (filterForm.tName.trim()) {
|
||||
params.tName = filterForm.tName.trim();
|
||||
}
|
||||
if (filterForm.sName.trim()) {
|
||||
params.sName = filterForm.sName.trim();
|
||||
}
|
||||
if (filterForm.status) {
|
||||
params.status = filterForm.status;
|
||||
}
|
||||
if (filterForm.refundStatus) {
|
||||
params.refundStatus = filterForm.refundStatus;
|
||||
}
|
||||
|
||||
return params;
|
||||
const handleReset = () => {
|
||||
Object.assign(filterForm, FILTER_DEFAULTS);
|
||||
pagination.reset();
|
||||
setTimeout(fetchList, 350);
|
||||
};
|
||||
|
||||
// ===== 计算属性 =====
|
||||
const hasFilter = computed(() => {
|
||||
return (
|
||||
filterForm.orderDateRange !== null ||
|
||||
debouncedOrderNo.value.trim() !== '' ||
|
||||
debouncedTName.value.trim() !== '' ||
|
||||
debouncedSName.value.trim() !== '' ||
|
||||
filterForm.status !== '' ||
|
||||
filterForm.refundStatus !== ''
|
||||
);
|
||||
});
|
||||
|
||||
// ===== 方法 =====
|
||||
|
||||
/** 查询 */
|
||||
const handleSearch = useThrottleFn(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const queryParams = buildQueryParams();
|
||||
console.log('订单列表查询参数:', queryParams);
|
||||
const res = await getOrderList(queryParams);
|
||||
|
||||
if (res.code == 200) {
|
||||
setDataSource(res.data.list);
|
||||
setPagination({ ...pagination.value, total: res.data.total });
|
||||
} else {
|
||||
message.error(res.msg || '查询失败');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('订单列表查询失败:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
/** 重置 */
|
||||
const handleReset = useThrottleFn(() => {
|
||||
filterForm.orderDateRange = null;
|
||||
filterForm.orderNo = '';
|
||||
filterForm.tName = '';
|
||||
filterForm.sName = '';
|
||||
filterForm.status = '';
|
||||
filterForm.refundStatus = '';
|
||||
setPagination({ current: 1, pageSize: 10, total: 0 });
|
||||
setDataSource([]);
|
||||
// 等待 debounce 生效后自动查询
|
||||
setTimeout(() => handleSearch(), 350);
|
||||
}, 500);
|
||||
|
||||
const handlePageChange = (page: number, pageSize: number) => {
|
||||
setPagination({ ...pagination.value, current: page, pageSize });
|
||||
handleSearch();
|
||||
pagination.setCurrent(page);
|
||||
if (pageSize !== (pagination as any).pageSize.value) {
|
||||
pagination.setPageSize(pageSize);
|
||||
}
|
||||
setTimeout(fetchList, 0);
|
||||
};
|
||||
|
||||
return {
|
||||
// 数据
|
||||
filterForm,
|
||||
loading,
|
||||
dataSource,
|
||||
@@ -268,8 +228,31 @@ export function useOrderModel() {
|
||||
summary,
|
||||
pagination,
|
||||
hasFilter,
|
||||
// 方法
|
||||
handleSearch,
|
||||
handleReset,
|
||||
handlePageChange,
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 工具函数 =====
|
||||
|
||||
function hasValue(v: any): boolean {
|
||||
if (v == null) return false;
|
||||
if (Array.isArray(v)) return v.length > 0 && v.some((item) => item != null);
|
||||
if (typeof v === 'string') return v.trim() !== '';
|
||||
return true;
|
||||
}
|
||||
|
||||
function isEmptyValue(v: any): boolean {
|
||||
return v == null || (typeof v === 'string' && v.trim() === '');
|
||||
}
|
||||
|
||||
function safeTransform(value: any, transform?: (v: any) => any): any {
|
||||
if (!transform) return value;
|
||||
try {
|
||||
return transform(value) ?? value;
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user