221 lines
8.0 KiB
JavaScript
221 lines
8.0 KiB
JavaScript
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");
|
||
}
|