封装index
import { Table, TableProps } from "antd";
import { ColumnType } from "antd/lib/table";
import { DataIndex } from "rc-table/lib/interface";
import Column from "antd/lib/table/Column";
export interface CustomColumnsType<T = unknown> extends ColumnType<T> {
custom?: boolean;
}
export interface IRecord<T> {
dataIndex: DataIndex;
text: any;
record: T;
index: number;
}
type Props<RecordType> = Omit<TableProps<RecordType>, "children" | "columns"> & {
children?: (record: IRecord<RecordType>) => React.ReactNode;
columns: CustomColumnsType<RecordType>[];
};
const CustomTable = <T extends object = any>(props: Props<T>) => {
const { columns, ...other } = props;
const defaultRender = (text: any) => {
return (
<div>
<div>{text || "--"}</div>
</div>
);
};
return (
<Table bordered {...other}>
{(columns || []).map(item => {
const { dataIndex = "", title, key, ...other } = item;
if (item.custom) {
return (
<Column
title={title}
dataIndex={dataIndex}
key={key}
align="center"
render={(text, record, index) => {
return props.children ? props.children({ dataIndex, text, record, index }) : null;
}}
{...other}
/>
);
}
return (
<Column
title={title}
className={item.className}
dataIndex={item.dataIndex}
key={item.key}
align="center"
render={item.render || defaultRender}
{...other}
/>
);
})}
</Table>
);
};
export default CustomTable;
config.tsx
import { Input, Select, DatePicker, MenuProps } from "antd";
import { billStatus, payStatus, payType } from "../const";
import { IFormConfig } from "@/components/SearchForm";
import { CustomColumnsType } from "@/components/Table";
import { V1OmcGameServerOrdersListData } from "@/api/modules/omgGameServer/data-contracts";
const { RangePicker } = DatePicker;
type ITableList = V1OmcGameServerOrdersListData["data"]["list"][0];
export const formConfig: IFormConfig[] = [
{ name: "startRange", children: <RangePicker /> },
{ name: "nickname", children: <Input placeholder="请输入用户昵称" /> },
{ name: "uid", children: <Input placeholder="请输入用户ID" /> },
{ name: "order_no", children: <Input placeholder="请输入充值订单号" /> },
{ name: "out_order_no", children: <Input placeholder="请输入支付订单号" /> },
{ name: "pay_status", children: <Select style={{ width: "140px" }} placeholder="请选择支付状态" options={payStatus} /> },
{ name: "status", children: <Select style={{ width: "140px" }} placeholder="请选择上账状态" options={billStatus} /> },
{ name: "pay_type", children: <Select style={{ width: "140px" }} placeholder="请选择类型" options={payType} /> }
];
export const tableColumns: CustomColumnsType<ITableList>[] = [
{ title: "订单号", dataIndex: "order_no", key: "order_no", fixed: "left", width: "200px" },
{ title: "支付时间", dataIndex: "paid_at", key: "paid_at", custom: true, width: "160px" },
{ title: "用户昵称", dataIndex: "nickname", key: "nickname", width: "120px" },
{ title: "用户ID", dataIndex: "uid", key: "uid", width: "120px" },
{ title: "金额", dataIndex: "real_money", key: "real_money", custom: true, width: "120px" },
{ title: "配置", dataIndex: "config_string", key: "config_string", width: "140px" },
{ title: "游戏服", dataIndex: "server_string", key: "server_string", width: "120px" },
{ title: "时长", dataIndex: "days", key: "days", width: "120px" },
{ title: "支付订单号", dataIndex: "out_order_no", key: "out_order_no", width: "230px" },
{ title: "类型", dataIndex: "pay_type", key: "pay_type", custom: true, width: "120px" },
{ title: "支付状态", dataIndex: "pay_status", key: "pay_status", custom: true, width: "120px" },
{ title: "上账状态", dataIndex: "status", key: "status", custom: true, width: "120px" },
{ title: "备注", dataIndex: "remark", key: "remark", width: "250px", custom: true },
{ title: "操作", dataIndex: "action", key: "action", width: "200px", custom: true }
];
export const enum ActionType {
RESET = 1, //重启
CLOSE = 2, //关机
UPDATE = 3, //更新游戏
DELAY = 4, //延时
RELOAD = 5, //重启
NO = 0
}
export const items: MenuProps["items"] = [
{
key: ActionType.RESET,
label: "重启"
},
{
key: ActionType.CLOSE,
label: "关机"
},
{
key: ActionType.UPDATE,
label: "更新游戏"
},
{
key: ActionType.DELAY,
label: "延时"
},
{
key: ActionType.RELOAD,
label: "重启"
}
];
界面使用
import SearchForm, { IFormConfig } from "@/shared/components/SearchForm";
import { ActionType, formConfig, items, MenuItem, tableColumns } from "./config";
import Table from "@/shared/components/Table";
import { TablePaginationConfig } from "antd/lib/table";
import { Button, Dropdown, MenuProps, Popconfirm, Space, message } from "antd";
import { useState } from "react";
import { IPageInfo } from "@/interface/common";
import api from "@/api/modules/omgGameServer/Api";
import { useDeepEqualEffect } from "@/hooks/useDeepEqualEffect";
import { V1OmcGameServerOrdersListData } from "@/api/modules/omgGameServer/data-contracts";
import findEnumLabel from "@/utils/findEnumLabel";
import { IBillStatus, billStatus, payStatus, payType } from "../const";
import dayjs from "dayjs";
import { centsToYuan } from "@/utils/util";
import parseDurationTime from "@/utils/parseDurationTime";
import UpdateImage from "./Update";
import { updateImage } from "@/api/modules/order";
import { DownOutlined } from "@ant-design/icons";
import SetDelay from "./setDelay";
const searchFormInitValue = { pay_status: "0", pay_type: "0", status: "0" };
type ITableList = V1OmcGameServerOrdersListData["data"]["list"][0];
const App: React.FC = () => {
const [loading, setLoading] = useState(false);
const [tableData, setTableData] = useState<ITableList[]>([]);
const [searchFormValue, setSearchFormValue] = useState(searchFormInitValue);
const [pageInfo, setPageInfo] = useState<IPageInfo>({
current: 1,
pageSize: 20,
total: 0
});
const [isShowUpdateImage, setIsShowUpdateImage] = useState(false);
const [info, setInfo] = useState({
current_image: "",
instance_id: 0
});
const [currentRecord, setCurrentRecord] = useState<ITableList | null>(null);
const [confirmVisible, setConfirmVisible] = useState(false);
const [currentAction, setCurrentAction] = useState<ActionType | null>(null);
const [isShowSetDelay, setIsShowSetDelay] = useState(false);
useDeepEqualEffect(() => {
queryList();
}, [searchFormValue, pageInfo.pageSize, pageInfo.current]);
const onFinish = (values: any) => {
const { startRange, ...other } = values;
const [start_time, end_time] = startRange || [];
setPageInfo({
...pageInfo,
current: 1
});
setSearchFormValue({
...other,
start_time: start_time ? dayjs(start_time.format("YYYY-MM-DD")).unix() : null,
end_time: end_time ? dayjs(end_time.format("YYYY-MM-DD")).unix() + (24 * 60 * 60 - 1) : null
});
};
const confirm = async (id?: number) => {
if (!id) {
message.error("id不能为空");
return;
}
setLoading(true);
await api.v1OmcGameServerOrderClearCreate({ id }).finally(() => setLoading(false));
queryList();
message.success("清理成功");
};
const queryList = async () => {
setLoading(true);
const { pageSize, current } = pageInfo;
const { data } = await api
.v1OmcGameServerOrdersList({
...searchFormValue,
page: `${current}`,
page_size: `${pageSize}`
})
.finally(() => setLoading(false));
const list = data?.list;
setTableData(list || []);
setPageInfo({ ...pageInfo, total: data?.total || 0 });
};
const handleIsShowUpdateImage = (isTrue: boolean) => {
setIsShowUpdateImage(isTrue);
};
const handleImage = (record: ITableList) => {
setInfo({ current_image: record.current_image, instance_id: record.instance_id });
handleIsShowUpdateImage(true);
};
/* 强制更新实例 */
const handleSubmit = async (instance_id: number) => {
const res: any = await updateImage(instance_id).finally(() => {
setLoading(false);
handleIsShowUpdateImage(false);
});
if (res.code == 0) {
message.success("更新成功");
} else {
message.error(res.msg);
}
};
const pageChange = (page: TablePaginationConfig) => {
const { current = 1, pageSize = 20 } = page;
setPageInfo({
...pageInfo,
current,
pageSize
});
};
const handleCancle = () => {
setCurrentAction(ActionType.NO);
setCurrentRecord(null);
setConfirmVisible(false);
};
// 处理确认后的实际操作
const handleActionConfirm = () => {
if (!currentAction || !currentRecord) return;
console.log(currentAction, currentAction === ActionType.RESET);
// 根据不同操作类型执行对应逻辑
switch (currentAction) {
case ActionType.RESET:
console.log("执行重启操作", currentRecord);
// 实际重启逻辑:api调用等
message.success("重启成功");
break;
case ActionType.CLOSE:
console.log("执行关机操作", currentRecord);
// 实际关机逻辑
message.success("关机成功");
break;
case ActionType.UPDATE:
console.log("执行更新游戏操作", currentRecord);
// 实际更新逻辑
message.success("更新游戏成功");
break;
case ActionType.RELOAD:
console.log("执行重开操作", currentRecord);
// 实际重开逻辑
message.success("重开成功");
break;
default:
break;
}
// 关闭确认弹窗
setConfirmVisible(false);
// 可根据需要刷新列表
queryList();
};
// 获取操作对应的中文名称(用于弹窗提示)
const getActionName = (action: ActionType) => {
switch (action) {
case ActionType.RESET:
return "重启";
case ActionType.CLOSE:
return "关机";
case ActionType.UPDATE:
return "更新游戏";
case ActionType.RELOAD:
return "重开";
default:
return "";
}
};
// 点击下拉菜单项时显示确认弹窗
const handleMenuItemClick = (record: ITableList, action: ActionType) => {
// 保存当前操作和记录
setCurrentAction(action);
setCurrentRecord(record);
// 显示确认弹窗
if (action != ActionType.DELAY) {
setConfirmVisible(true);
} else {
handleIsShowSetDelay(true);
}
};
const handleIsShowSetDelay = (val: boolean) => {
setIsShowSetDelay(val);
};
const handleDelaySubmit = (val: number) => {
console.log(val, "handleDelaySubmit");
queryList();
};
return (
<div className="content-box card">
<Space direction="vertical" size="middle">
<SearchForm className="search-form" formConfig={formConfig} initialValues={searchFormInitValue} onFinish={onFinish} />
<Table<ITableList>
columns={tableColumns}
scroll={{ x: 1000, y: 600 }}
dataSource={tableData}
loading={loading}
rowKey="id"
onChange={pageChange}
pagination={{
...pageInfo
}}
>
{({ dataIndex, text, record }) => {
switch (dataIndex) {
case "real_money":
return text ? `${centsToYuan(text)}元` : "-";
case "paid_at":
return text ? dayjs.unix(text).format("YYYY-MM-DD HH:mm:ss") : "-";
case "pay_type":
return findEnumLabel(payType, text);
case "pay_status":
return findEnumLabel(payStatus, text);
case "status":
return findEnumLabel(billStatus, text);
case "remark":
return (
<div style={{ textAlign: "left", paddingLeft: "20px" }}>
<div>最高人数:{record.max_online_num ? record.max_online_num : "--"}</div>
<div>连接时长:{record.total_link_time ? parseDurationTime(record.total_link_time) : "--"}</div>
</div>
);
case "action":
return (
<div className="flex">
<Popconfirm
title="确定清理吗?"
disabled={`${record.status}` === IBillStatus.RETURNED_BILL}
onConfirm={() => confirm(record.id!)}
>
<Button type="link" disabled={`${record.status}` === IBillStatus.RETURNED_BILL}>
清理
</Button>
</Popconfirm>
<Button
type="link"
onClick={() => {
handleImage(record);
}}
>
镜像
</Button>
<div className="flex center">
<Dropdown
className="flex center"
// 关键修改:通过menu的onClick回调传递record
menu={{
items, // 确保类型匹配
onClick: (menuInfo: any) => {
// 同时获取菜单信息和行数据
handleMenuItemClick(record, JSON.parse(menuInfo.key));
}
}}
>
<a onClick={e => e.preventDefault()}>
<Space>
更多
<DownOutlined />
</Space>
</a>
</Dropdown>
<Popconfirm
open={confirmVisible && currentAction && currentRecord && record.id === currentRecord.id}
title={`确定要${currentAction ? getActionName(JSON.parse(currentAction + "")) : ""}吗?`}
onConfirm={handleActionConfirm}
onCancel={() => handleCancle()}
okText="确认"
cancelText="取消"
/>
</div>
</div>
);
}
}}
</Table>
</Space>
<UpdateImage
isShow={isShowUpdateImage}
handleClose={() => {
handleIsShowUpdateImage(false);
}}
handleSubmit={handleSubmit}
info={info}
/>
<SetDelay
isShow={isShowSetDelay}
handleClose={() => {
handleIsShowSetDelay(false);
}}
handleSubmit={handleDelaySubmit}
/>
</div>
);
};
export default App;