264 lines
9.3 KiB
JavaScript
264 lines
9.3 KiB
JavaScript
import Add from "@mui/icons-material/Add";
|
|
import MenuItem from "@mui/material/MenuItem";
|
|
import TextField from "@mui/material/TextField";
|
|
import { AdminFormDialog } from "@/shared/ui/AdminFormDialog.jsx";
|
|
import { DataState } from "@/shared/ui/DataState.jsx";
|
|
import { DataTable } from "@/shared/ui/DataTable.jsx";
|
|
import {
|
|
AdminActionIconButton,
|
|
AdminFilterSelect,
|
|
AdminListBody,
|
|
AdminListPage,
|
|
AdminListToolbar,
|
|
AdminSearchBox,
|
|
} from "@/shared/ui/AdminListLayout.jsx";
|
|
import { PaginationBar } from "@/shared/ui/PaginationBar.jsx";
|
|
import { formatMillis } from "@/shared/utils/time.js";
|
|
import {
|
|
resourceGrantStatusFilters,
|
|
resourceGrantStatusLabels,
|
|
resourceGrantSubjectLabels,
|
|
} from "@/features/resources/constants.js";
|
|
import { useResourceGrantListPage } from "@/features/resources/hooks/useResourcePages.js";
|
|
import styles from "@/features/resources/resources.module.css";
|
|
|
|
const grantColumns = [
|
|
{
|
|
key: "grant",
|
|
label: "赠送记录",
|
|
width: "minmax(240px, 1.2fr)",
|
|
render: (grant) => (
|
|
<div className={styles.stack}>
|
|
<span className={styles.name}>{grant.grantId || "-"}</span>
|
|
<span className={styles.meta}>{grant.commandId || "-"}</span>
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
key: "target",
|
|
label: "用户 ID",
|
|
width: "minmax(120px, 0.7fr)",
|
|
render: (grant) => grant.targetUserId || "-",
|
|
},
|
|
{
|
|
key: "subject",
|
|
label: "对象",
|
|
width: "minmax(160px, 0.8fr)",
|
|
render: (grant) => (
|
|
<div className={styles.stack}>
|
|
<span>{resourceGrantSubjectLabels[grant.grantSubjectType] || grant.grantSubjectType || "-"}</span>
|
|
<span className={styles.meta}>{grant.grantSubjectId || "-"}</span>
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
key: "items",
|
|
label: "发放内容",
|
|
width: "minmax(180px, 1fr)",
|
|
render: (grant) => <GrantItems grant={grant} />,
|
|
},
|
|
{
|
|
key: "status",
|
|
label: "状态",
|
|
width: "minmax(100px, 0.55fr)",
|
|
render: (grant) => (
|
|
<span className={`status-badge status-badge--${grant.status || "stopped"}`}>
|
|
<span className="status-point" />
|
|
{resourceGrantStatusLabels[grant.status] || grant.status || "-"}
|
|
</span>
|
|
),
|
|
},
|
|
{
|
|
key: "reason",
|
|
label: "原因",
|
|
width: "minmax(160px, 1fr)",
|
|
render: (grant) => grant.reason || "-",
|
|
},
|
|
{
|
|
key: "time",
|
|
label: "创建时间",
|
|
width: "minmax(170px, 0.9fr)",
|
|
render: (grant) => formatMillis(grant.createdAtMs),
|
|
},
|
|
];
|
|
|
|
export function ResourceGrantListPage() {
|
|
const page = useResourceGrantListPage();
|
|
const items = page.data.items || [];
|
|
const total = page.data.total || 0;
|
|
|
|
return (
|
|
<AdminListPage>
|
|
<AdminListToolbar
|
|
filters={
|
|
<>
|
|
<AdminSearchBox value={page.query} onChange={page.changeQuery} placeholder="搜索用户 ID..." />
|
|
<AdminFilterSelect
|
|
options={resourceGrantStatusFilters}
|
|
value={page.status}
|
|
onChange={page.changeStatus}
|
|
/>
|
|
</>
|
|
}
|
|
actions={
|
|
page.abilities.canCreateGrant ? (
|
|
<AdminActionIconButton label="资源赠送" primary onClick={page.openCreateGrant}>
|
|
<Add fontSize="small" />
|
|
</AdminActionIconButton>
|
|
) : null
|
|
}
|
|
/>
|
|
<DataState error={page.error} loading={page.loading} onRetry={page.reload}>
|
|
<AdminListBody>
|
|
<DataTable columns={grantColumns} items={items} minWidth="1190px" rowKey={(grant) => grant.grantId} />
|
|
{total > 0 ? (
|
|
<PaginationBar
|
|
page={page.page}
|
|
pageSize={page.data.pageSize || 10}
|
|
total={total}
|
|
onPageChange={page.setPage}
|
|
/>
|
|
) : null}
|
|
</AdminListBody>
|
|
</DataState>
|
|
<ResourceGrantDialog
|
|
disabled={!page.abilities.canCreateGrant}
|
|
form={page.form}
|
|
groupOptions={page.groupOptions}
|
|
loading={page.loadingAction === "grant-create"}
|
|
open={page.activeAction === "create"}
|
|
optionsLoading={page.optionsLoading}
|
|
resourceOptions={page.resourceOptions}
|
|
setForm={page.setForm}
|
|
onClose={page.closeAction}
|
|
onSubmit={page.submitGrant}
|
|
/>
|
|
</AdminListPage>
|
|
);
|
|
}
|
|
|
|
function ResourceGrantDialog({
|
|
disabled,
|
|
form,
|
|
groupOptions,
|
|
loading,
|
|
onClose,
|
|
onSubmit,
|
|
open,
|
|
optionsLoading,
|
|
resourceOptions,
|
|
setForm,
|
|
}) {
|
|
const submitDisabled = disabled || loading || optionsLoading;
|
|
return (
|
|
<AdminFormDialog
|
|
loading={loading}
|
|
open={open}
|
|
submitDisabled={submitDisabled}
|
|
title="资源赠送"
|
|
onClose={onClose}
|
|
onSubmit={onSubmit}
|
|
>
|
|
<TextField
|
|
disabled={disabled}
|
|
label="用户 ID"
|
|
required
|
|
type="number"
|
|
value={form.targetUserId}
|
|
onChange={(event) => setForm({ ...form, targetUserId: event.target.value })}
|
|
/>
|
|
<TextField
|
|
disabled={disabled}
|
|
label="对象类型"
|
|
required
|
|
select
|
|
value={form.subjectType}
|
|
onChange={(event) => setForm({ ...form, groupId: "", resourceId: "", subjectType: event.target.value })}
|
|
>
|
|
<MenuItem value="resource">资源</MenuItem>
|
|
<MenuItem value="resource_group">资源组</MenuItem>
|
|
</TextField>
|
|
{form.subjectType === "resource" ? (
|
|
<>
|
|
<TextField
|
|
disabled={disabled || optionsLoading}
|
|
label="资源"
|
|
required
|
|
select
|
|
value={form.resourceId}
|
|
onChange={(event) => setForm({ ...form, resourceId: event.target.value })}
|
|
>
|
|
{resourceOptions.map((resource) => (
|
|
<MenuItem key={resource.resourceId} value={String(resource.resourceId)}>
|
|
{resource.name || resource.resourceCode || resource.resourceId}
|
|
</MenuItem>
|
|
))}
|
|
</TextField>
|
|
<TextField
|
|
disabled={disabled}
|
|
label="数量"
|
|
required
|
|
type="number"
|
|
value={form.quantity}
|
|
onChange={(event) => setForm({ ...form, quantity: event.target.value })}
|
|
/>
|
|
<TextField
|
|
disabled={disabled}
|
|
label="有效天数"
|
|
type="number"
|
|
value={form.durationDays}
|
|
onChange={(event) => setForm({ ...form, durationDays: event.target.value })}
|
|
/>
|
|
</>
|
|
) : (
|
|
<TextField
|
|
disabled={disabled || optionsLoading}
|
|
label="资源组"
|
|
required
|
|
select
|
|
value={form.groupId}
|
|
onChange={(event) => setForm({ ...form, groupId: event.target.value })}
|
|
>
|
|
{groupOptions.map((group) => (
|
|
<MenuItem key={group.groupId} value={String(group.groupId)}>
|
|
{group.name || group.groupCode || group.groupId}
|
|
</MenuItem>
|
|
))}
|
|
</TextField>
|
|
)}
|
|
<TextField
|
|
disabled={disabled}
|
|
label="原因"
|
|
multiline
|
|
minRows={2}
|
|
required
|
|
value={form.reason}
|
|
onChange={(event) => setForm({ ...form, reason: event.target.value })}
|
|
/>
|
|
</AdminFormDialog>
|
|
);
|
|
}
|
|
|
|
function GrantItems({ grant }) {
|
|
const items = grant.items || [];
|
|
if (!items.length) {
|
|
return "-";
|
|
}
|
|
return (
|
|
<div className={styles.stack}>
|
|
{items.slice(0, 2).map((item) => (
|
|
<span key={item.grantItemId || `${grant.grantId}-${item.resourceId}`}>
|
|
{item.resourceId || "-"} / {item.quantity || 0}
|
|
{item.durationMs ? ` / ${formatDuration(item.durationMs)}` : ""}
|
|
</span>
|
|
))}
|
|
{items.length > 2 ? <span className={styles.meta}>+{items.length - 2}</span> : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function formatDuration(durationMs) {
|
|
const days = Number(durationMs || 0) / (24 * 60 * 60 * 1000);
|
|
return Number.isInteger(days) ? `${days}天` : `${days.toFixed(1)}天`;
|
|
}
|