129 lines
5.1 KiB
JavaScript
129 lines
5.1 KiB
JavaScript
import AddOutlined from "@mui/icons-material/AddOutlined";
|
||
import { useMemo, useState } from "react";
|
||
import { AdminFilterSelect, AdminListToolbar } from "@/shared/ui/AdminListLayout.jsx";
|
||
import { Button } from "@/shared/ui/Button.jsx";
|
||
import { DataTable } from "@/shared/ui/DataTable.jsx";
|
||
import { TimeText } from "@/shared/ui/TimeText.jsx";
|
||
import { formatNumber, formatPercent } from "../format.js";
|
||
import { OpsStatusBadge } from "./OpsStatusBadge.jsx";
|
||
|
||
export function ConfigsView({ apps, luckyGifts, onCreate, onEdit, saving }) {
|
||
const [appFilter, setAppFilter] = useState("");
|
||
|
||
const appOptions = useMemo(
|
||
() =>
|
||
apps.map((item) => [
|
||
item.app_code ?? item.appCode,
|
||
`${item.app_name ?? item.appName ?? ""} (${item.app_code ?? item.appCode})`,
|
||
]),
|
||
[apps],
|
||
);
|
||
// 配置列表已在 dashboard 阶段跨应用拉全,应用筛选只做前端过滤,避免重复扇出请求。
|
||
const items = useMemo(
|
||
() => (appFilter ? luckyGifts.filter((config) => config.app_code === appFilter) : luckyGifts),
|
||
[appFilter, luckyGifts],
|
||
);
|
||
|
||
const columns = useMemo(() => buildColumns({ onEdit, saving }), [onEdit, saving]);
|
||
|
||
return (
|
||
<div className="ops-view">
|
||
<AdminListToolbar
|
||
actions={
|
||
<Button
|
||
disabled={saving}
|
||
startIcon={<AddOutlined fontSize="small" />}
|
||
variant="primary"
|
||
onClick={onCreate}
|
||
>
|
||
添加配置
|
||
</Button>
|
||
}
|
||
filters={
|
||
<AdminFilterSelect
|
||
label="应用"
|
||
options={[["", "全部应用"], ...appOptions]}
|
||
value={appFilter}
|
||
onChange={setAppFilter}
|
||
/>
|
||
}
|
||
/>
|
||
<DataTable
|
||
columns={columns}
|
||
emptyLabel="暂无幸运礼物配置"
|
||
items={items}
|
||
minWidth="1260px"
|
||
rowKey={(item) => `${item.app_code}:${item.pool_id}:${item.rule_version}`}
|
||
title="幸运礼物配置"
|
||
total={items.length}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function buildColumns({ onEdit, saving }) {
|
||
return [
|
||
{ key: "app_code", label: "应用", width: "minmax(88px, 0.7fr)" },
|
||
{ key: "pool_id", label: "奖池", width: "minmax(110px, 0.9fr)" },
|
||
{
|
||
key: "rule_version",
|
||
label: "版本",
|
||
render: (item) => (item.is_default ? "-" : `v${item.rule_version}`),
|
||
width: "minmax(72px, 0.5fr)",
|
||
},
|
||
{
|
||
key: "strategy_version",
|
||
label: "策略",
|
||
// 升级前的不可变版本可能没有该字段,owner 会按 fixed_v2 解释;列表保持同一兼容口径,
|
||
// 不能把空值展示成 dynamic_v3,避免运营误判已完成策略切换。
|
||
render: (item) => item.strategy_version || "fixed_v2",
|
||
width: "minmax(112px, 0.8fr)",
|
||
},
|
||
{
|
||
key: "enabled",
|
||
label: "状态",
|
||
render: (item) => {
|
||
// 默认草稿是服务端为未配置奖池生成的占位,不参与线上抽奖,必须与"停用"区分开。
|
||
if (item.is_default) {
|
||
return <OpsStatusBadge label="默认草稿" tone="warning" />;
|
||
}
|
||
return item.enabled ? (
|
||
<OpsStatusBadge label="已启用" tone="succeeded" />
|
||
) : (
|
||
<OpsStatusBadge label="已停用" />
|
||
);
|
||
},
|
||
width: "minmax(128px, 0.8fr)",
|
||
},
|
||
{ key: "target_rtp_percent", label: "目标 RTP", render: (item) => formatPercent(item.target_rtp_percent) },
|
||
{ key: "pool_rate_percent", label: "公共奖池", render: (item) => formatPercent(item.pool_rate_percent) },
|
||
{
|
||
key: "profit_anchor_rate",
|
||
label: "平台 / 主播",
|
||
render: (item) => `${formatPercent(item.profit_rate_percent)} / ${formatPercent(item.anchor_rate_percent)}`,
|
||
width: "minmax(144px, 1fr)",
|
||
},
|
||
{ key: "control_band_percent", label: "控制带宽", render: (item) => formatPercent(item.control_band_percent) },
|
||
{
|
||
key: "settlement_window_wager",
|
||
label: "结算窗口流水",
|
||
render: (item) => formatNumber(item.settlement_window_wager),
|
||
},
|
||
{
|
||
key: "created_at_ms",
|
||
label: "发布时间",
|
||
render: (item) => (item.is_default ? "-" : <TimeText value={item.created_at_ms} />),
|
||
width: "minmax(176px, 1.1fr)",
|
||
},
|
||
{
|
||
key: "actions",
|
||
label: "操作",
|
||
render: (item) => (
|
||
<Button disabled={saving} size="small" onClick={() => onEdit(item)}>
|
||
{item.is_default ? "发布配置" : "新增版本"}
|
||
</Button>
|
||
),
|
||
},
|
||
];
|
||
}
|