2026-07-14 20:23:03 +08:00

221 lines
8.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import CardGiftcardOutlined from "@mui/icons-material/CardGiftcardOutlined";
import { useMemo, useState } from "react";
import { listGiftRecords } from "@/features/operations/api";
import styles from "@/features/operations/operations.module.css";
import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js";
import { AdminFilterResetButton, AdminListBody, AdminListToolbar } from "@/shared/ui/AdminListLayout.jsx";
import { AdminUserIdentity } from "@/shared/ui/AdminUserIdentity.jsx";
import { DataState } from "@/shared/ui/DataState.jsx";
import { DataTable } from "@/shared/ui/DataTable.jsx";
import { TimeRangeFilter } from "@/shared/ui/TimeRangeFilter.jsx";
import { TimeText } from "@/shared/ui/TimeText.jsx";
import { createOptionsColumnFilter, createUserIdentityColumnFilter } from "@/shared/ui/tableFilters.js";
import {
emptyUserIdentityFilter,
isUserIdentityFilterEmpty,
userIdentityFilterParams,
} from "@/shared/ui/userIdentityFilter.js";
const pageSize = 30;
const sceneOptions = [
{ label: "房间送礼", value: "room" },
{ label: "私聊送礼", value: "direct" },
];
export function GiftRecordTable() {
const [page, setPage] = useState(1);
const [scene, setScene] = useState("");
const [senderFilter, setSenderFilter] = useState(emptyUserIdentityFilter);
const [timeRange, setTimeRange] = useState({ endMs: "", startMs: "" });
const filters = useMemo(
() => ({
end_at_ms: timeRange.endMs || "",
scene,
...userIdentityFilterParams(senderFilter, {
displayUserId: "sender_display_user_id",
userId: "sender_user_id",
username: "sender_username",
}),
start_at_ms: timeRange.startMs || "",
}),
[scene, senderFilter, timeRange.endMs, timeRange.startMs],
);
const query = usePaginatedQuery({
errorMessage: "获取送礼记录失败",
fetcher: listGiftRecords,
filters,
page,
pageSize,
queryKey: ["gift-records", filters, page],
});
const data = query.data || { items: [], page, pageSize, total: 0 };
const items = data.items || [];
const total = data.total || 0;
const hasActiveFilters = Boolean(
!isUserIdentityFilterEmpty(senderFilter) || scene || timeRange.startMs || timeRange.endMs,
);
const changeScene = (value) => {
setScene(value);
setPage(1);
};
const changeSenderFilter = (value) => {
setSenderFilter(value);
setPage(1);
};
const changeTimeRange = (value) => {
setTimeRange(value);
setPage(1);
};
const resetFilters = () => {
setScene("");
setSenderFilter(emptyUserIdentityFilter);
setTimeRange({ endMs: "", startMs: "" });
setPage(1);
};
const columns = useMemo(
() => [
{
key: "index",
label: "序号",
render: (_entry, index) => <span className={styles.giftRecordIndex}>{index + 1}</span>,
resizable: false,
width: "72px",
},
{
filter: createUserIdentityColumnFilter({
value: senderFilter,
onChange: changeSenderFilter,
}),
key: "sender",
label: "送礼人",
render: (entry) => <AdminUserIdentity openInAppUserDetail user={entry.sender} />,
width: "minmax(210px, 1fr)",
},
{
key: "receiver",
label: "收礼人",
render: (entry) => <AdminUserIdentity openInAppUserDetail user={entry.receiver} />,
width: "minmax(210px, 1fr)",
},
{
key: "gift",
label: "礼物",
render: (entry) => <GiftCell gift={entry.gift} />,
width: "minmax(220px, 1fr)",
},
{
key: "giftValue",
label: "数量与价值",
render: (entry) => <GiftValueCell entry={entry} />,
width: "minmax(190px, 0.9fr)",
},
{
filter: createOptionsColumnFilter({
emptyLabel: "全部场景",
options: sceneOptions,
placeholder: "送礼场景",
value: scene,
onChange: changeScene,
}),
key: "scene",
label: "场景",
render: (entry) => <SceneCell entry={entry} />,
width: "minmax(132px, 0.7fr)",
},
{
key: "createdAtMs",
label: "送礼时间",
render: (entry) => <TimeText value={entry.createdAtMs} />,
width: "minmax(170px, 0.8fr)",
},
],
[scene, senderFilter],
);
return (
<div className={styles.ledgerTableShell}>
<AdminListToolbar
filters={
<>
<TimeRangeFilter value={timeRange} onChange={changeTimeRange} />
<AdminFilterResetButton disabled={!hasActiveFilters} onClick={resetFilters} />
</>
}
/>
<DataState error={query.error} loading={query.loading} onRetry={query.reload}>
<AdminListBody>
<DataTable
columns={columns}
emptyLabel="当前无送礼记录"
items={items}
minWidth="1230px"
pagination={
total > 0
? {
page,
pageSize: data.pageSize || pageSize,
total,
onPageChange: setPage,
}
: undefined
}
rowKey={(entry) => entry.transactionId}
/>
</AdminListBody>
</DataState>
</div>
);
}
function GiftCell({ gift = {} }) {
return (
<div className={styles.giftRecordGift}>
<span className={styles.giftRecordCover}>
{gift.coverUrl ? <img src={gift.coverUrl} alt="" /> : <CardGiftcardOutlined fontSize="small" />}
</span>
<span className={styles.giftRecordGiftText}>
<span className={styles.giftRecordGiftName}>{gift.name || "-"}</span>
{gift.giftId ? <span className={styles.giftRecordGiftId}>{gift.giftId}</span> : null}
</span>
</div>
);
}
function GiftValueCell({ entry }) {
return (
<div className={styles.giftRecordMetrics}>
<span className={styles.giftRecordMetric}>
<span>数量</span>
<strong>× {formatNumber(entry.giftCount)}</strong>
</span>
<span className={styles.giftRecordMetric}>
<span>单价</span>
<strong>{formatNumber(entry.unitValue)}</strong>
</span>
<span className={`${styles.giftRecordMetric} ${styles.giftRecordTotal}`}>
<span>总价值</span>
<strong>{formatNumber(entry.giftValue)}</strong>
</span>
</div>
);
}
function SceneCell({ entry }) {
const direct = entry.scene === "direct";
return (
<span className={styles.giftRecordScene}>
<span className={`${styles.giftRecordSceneBadge} ${direct ? styles.giftRecordSceneDirect : styles.giftRecordSceneRoom}`}>
{direct ? "私聊" : "房间"}
</span>
{!direct && entry.roomId ? <span className={styles.giftRecordRoomId}>{entry.roomId}</span> : null}
</span>
);
}
function formatNumber(value) {
return Number(value || 0).toLocaleString("zh-CN");
}