hyapp-admin-platform/src/features/resources/pages/ResourceGroupListPage.jsx
2026-06-02 17:24:22 +08:00

389 lines
15 KiB
JavaScript

import Add from "@mui/icons-material/Add";
import CategoryOutlined from "@mui/icons-material/CategoryOutlined";
import DeleteOutlineOutlined from "@mui/icons-material/DeleteOutlineOutlined";
import EditOutlined from "@mui/icons-material/EditOutlined";
import Inventory2Outlined from "@mui/icons-material/Inventory2Outlined";
import MonetizationOnOutlined from "@mui/icons-material/MonetizationOnOutlined";
import { AdminSwitch } from "@/shared/ui/AdminSwitch.jsx";
import TextField from "@mui/material/TextField";
import {
AdminFormDialog,
AdminFormFieldGrid,
AdminFormInlineActions,
AdminFormList,
AdminFormListRow,
AdminFormSection,
AdminFormSwitchField,
} from "@/shared/ui/AdminFormDialog.jsx";
import { Button } from "@/shared/ui/Button.jsx";
import { DataState } from "@/shared/ui/DataState.jsx";
import { DataTable } from "@/shared/ui/DataTable.jsx";
import {
AdminActionIconButton,
AdminListBody,
AdminListPage,
AdminListToolbar,
AdminRowActions,
} from "@/shared/ui/AdminListLayout.jsx";
import { createOptionsColumnFilter, createTextColumnFilter } from "@/shared/ui/tableFilters.js";
import { formatMillis } from "@/shared/utils/time.js";
import { ResourceSelectField } from "@/features/resources/components/ResourceSelectDrawer.jsx";
import { resourceGroupAssetLabels, resourceStatusFilters, resourceTypeLabels } from "@/features/resources/constants.js";
import { useResourceGroupListPage } from "@/features/resources/hooks/useResourcePages.js";
import styles from "@/features/resources/resources.module.css";
const dayMillis = 24 * 60 * 60 * 1000;
const baseColumns = [
{
key: "group",
label: "资源组",
width: "minmax(260px, 1.5fr)",
render: (group) => <GroupIdentity group={group} />,
},
{
key: "items",
label: "组内资源",
width: "minmax(320px, 1.7fr)",
render: (group) => <GroupItems group={group} />,
},
{
key: "sort",
label: "排序",
width: "minmax(80px, 0.45fr)",
render: (group) => group.sortOrder ?? 0,
},
{
key: "status",
label: "状态",
width: "minmax(92px, 0.55fr)",
},
{
key: "time",
label: "更新时间",
width: "minmax(170px, 0.9fr)",
render: (group) => formatMillis(group.updatedAtMs || group.createdAtMs),
},
{
key: "actions",
label: "操作",
width: "minmax(76px, 0.4fr)",
},
];
export function ResourceGroupListPage() {
const page = useResourceGroupListPage();
const items = page.data.items || [];
const total = page.data.total || 0;
const columns = baseColumns.map((column) =>
column.key === "group"
? {
...column,
filter: createTextColumnFilter({
placeholder: "搜索资源组名称、资源组编码",
value: page.query,
onChange: page.changeQuery,
}),
}
: column.key === "status"
? {
...column,
filter: createOptionsColumnFilter({
options: resourceStatusFilters,
placeholder: "搜索状态",
value: page.status,
onChange: page.changeStatus,
}),
render: (group) => <ResourceGroupStatusSwitch group={group} page={page} />,
}
: column.key === "actions"
? {
...column,
render: (group) => <ResourceGroupRowActions group={group} page={page} />,
}
: column,
);
return (
<AdminListPage>
<AdminListToolbar
actions={
page.abilities.canCreateGroup ? (
<AdminActionIconButton label="添加资源组" primary onClick={page.openCreateGroup}>
<Add fontSize="small" />
</AdminActionIconButton>
) : null
}
/>
<DataState error={page.error} loading={page.loading} onRetry={page.reload}>
<AdminListBody>
<DataTable
columns={columns}
items={items}
minWidth="1060px"
pagination={
total > 0
? {
page: page.page,
pageSize: page.data.pageSize || 50,
total,
onPageChange: page.setPage,
}
: undefined
}
rowKey={(group) => group.groupId}
/>
</AdminListBody>
</DataState>
<ResourceGroupFormDialog
disabled={
page.activeAction === "edit" ? !page.abilities.canUpdateGroup : !page.abilities.canCreateGroup
}
form={page.form}
loading={
page.activeAction === "edit"
? page.loadingAction === "group-edit"
: page.loadingAction === "group-create"
}
mode={page.activeAction === "edit" ? "edit" : "create"}
open={page.activeAction === "create" || page.activeAction === "edit"}
page={page}
onClose={page.closeAction}
onSubmit={page.submitGroup}
/>
</AdminListPage>
);
}
function ResourceGroupRowActions({ group, page }) {
return (
<AdminRowActions>
<AdminActionIconButton
disabled={!page.abilities.canUpdateGroup}
label="编辑资源组"
onClick={() => page.openEditGroup(group)}
>
<EditOutlined fontSize="small" />
</AdminActionIconButton>
</AdminRowActions>
);
}
function ResourceGroupStatusSwitch({ group, page }) {
const checked = group.status === "active";
return (
<AdminSwitch
checked={checked}
disabled={!page.abilities.canUpdateGroup || page.loadingAction === `group-status-${group.groupId}`}
inputProps={{ "aria-label": checked ? "禁用资源组" : "启用资源组" }}
onChange={(event) => page.toggleGroup(group, event.target.checked)}
/>
);
}
function ResourceGroupFormDialog({ disabled, form, loading, mode, onClose, onSubmit, open, page }) {
const submitDisabled = disabled || loading || page.resourceOptionsLoading;
return (
<AdminFormDialog
loading={loading}
open={open}
size="wide"
submitDisabled={submitDisabled}
title={mode === "edit" ? "编辑资源组" : "添加资源组"}
onClose={onClose}
onSubmit={onSubmit}
>
<AdminFormSection title="基础信息">
<AdminFormFieldGrid columns="minmax(210px, 1fr) minmax(210px, 1fr) 112px">
<TextField
disabled={disabled}
label="资源组名称"
required
value={form.name}
onChange={(event) => page.setForm({ ...form, name: event.target.value })}
/>
<TextField
disabled={disabled}
label="资源组编码"
required
value={form.groupCode}
onChange={(event) => page.setForm({ ...form, groupCode: event.target.value })}
/>
<TextField
disabled={disabled}
label="排序"
type="number"
value={form.sortOrder}
onChange={(event) => page.setForm({ ...form, sortOrder: event.target.value })}
/>
</AdminFormFieldGrid>
<TextField
disabled={disabled}
label="说明"
multiline
minRows={2}
value={form.description}
onChange={(event) => page.setForm({ ...form, description: event.target.value })}
/>
<AdminFormSwitchField
checked={form.enabled}
checkedLabel="启用"
disabled={disabled}
label="资源组状态"
uncheckedLabel="禁用"
onChange={(checked) => page.setForm({ ...form, enabled: checked })}
/>
</AdminFormSection>
<AdminFormSection
title="组内资源"
actions={
<AdminFormInlineActions>
<Button
disabled={disabled || loading}
onClick={page.addResourceItem}
startIcon={<Inventory2Outlined fontSize="small" />}
>
资源
</Button>
<Button
disabled={disabled || loading}
onClick={() => page.addWalletAssetItem("COIN")}
startIcon={<MonetizationOnOutlined fontSize="small" />}
>
金币
</Button>
</AdminFormInlineActions>
}
>
<AdminFormList>
{form.items.map((item, index) => (
<ResourceGroupItemEditor
disabled={disabled || loading}
index={index}
item={item}
key={`${item.itemType}-${item.walletAssetType || item.resourceId || index}-${index}`}
page={page}
/>
))}
</AdminFormList>
</AdminFormSection>
</AdminFormDialog>
);
}
function ResourceGroupItemEditor({ disabled, index, item, page }) {
if (item.itemType === "wallet_asset") {
const label = resourceGroupAssetLabels[item.walletAssetType] || "钱包资产";
return (
<AdminFormListRow>
<TextField disabled label="类型" value={label} />
<TextField
disabled={disabled}
label={`${label}数量`}
required
type="number"
value={item.walletAssetAmount}
onChange={(event) => page.updateGroupItem(index, { walletAssetAmount: event.target.value })}
/>
<TextField
disabled={disabled}
label="排序"
type="number"
value={item.sortOrder}
onChange={(event) => page.updateGroupItem(index, { sortOrder: event.target.value })}
/>
<AdminActionIconButton disabled={disabled} label="移除条目" onClick={() => page.removeGroupItem(index)}>
<DeleteOutlineOutlined fontSize="small" />
</AdminActionIconButton>
</AdminFormListRow>
);
}
return (
<AdminFormListRow>
<ResourceSelectField
disabled={disabled || page.resourceOptionsLoading}
drawerTitle="选择资源"
label="资源"
loading={page.resourceOptionsLoading}
resources={page.resourceOptions}
value={item.resourceId}
onChange={(resourceId) => page.updateGroupItem(index, { resourceId })}
/>
<TextField
disabled={disabled}
label="有效天数"
required
type="number"
value={item.durationDays}
onChange={(event) => page.updateGroupItem(index, { durationDays: event.target.value })}
/>
<TextField
disabled={disabled}
label="排序"
type="number"
value={item.sortOrder}
onChange={(event) => page.updateGroupItem(index, { sortOrder: event.target.value })}
/>
<AdminActionIconButton disabled={disabled} label="移除条目" onClick={() => page.removeGroupItem(index)}>
<DeleteOutlineOutlined fontSize="small" />
</AdminActionIconButton>
</AdminFormListRow>
);
}
function GroupIdentity({ group }) {
return (
<div className={styles.identity}>
<span className={styles.thumb}>
<CategoryOutlined fontSize="small" />
</span>
<div className={styles.identityText}>
<span className={styles.name}>{group.name || "-"}</span>
<span className={styles.meta}>{group.groupCode || group.groupId}</span>
</div>
</div>
);
}
function GroupItems({ group }) {
const items = group.items || [];
if (!items.length) {
return "-";
}
return (
<div className="admin-tag-list">
{items.slice(0, 4).map((item) => {
if (item.itemType === "wallet_asset") {
const label = resourceGroupAssetLabels[item.walletAssetType] || item.walletAssetType || "钱包资产";
return (
<span
className="admin-tag"
key={item.groupItemId || `${item.walletAssetType}-${item.walletAssetAmount}`}
>
{label} {formatNumber(item.walletAssetAmount)}
</span>
);
}
const resource = item.resource || {};
const type = resourceTypeLabels[resource.resourceType] || resource.resourceType || "资源";
return (
<span className="admin-tag" key={item.groupItemId || item.resourceId}>
{resource.name || item.resourceId} · {formatDurationDays(item.durationMs)} · {type}
</span>
);
})}
{items.length > 4 ? <span className="admin-tag">+{items.length - 4}</span> : null}
</div>
);
}
function formatDurationDays(durationMs) {
const days = Number(durationMs || 0) / dayMillis;
return Number.isInteger(days) ? days : days.toFixed(1);
}
function formatNumber(value) {
return Number(value || 0).toLocaleString("zh-CN");
}