feat room rocket reward quantity
This commit is contained in:
parent
ebb4545ad6
commit
53b74f9dc2
@ -5,6 +5,7 @@ export interface RoomRocketRewardItemDto {
|
|||||||
rewardItemId: string;
|
rewardItemId: string;
|
||||||
resourceGroupId: number;
|
resourceGroupId: number;
|
||||||
weight: number;
|
weight: number;
|
||||||
|
quantity: number;
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
iconUrl?: string;
|
iconUrl?: string;
|
||||||
}
|
}
|
||||||
@ -61,10 +62,7 @@ export type RoomRocketConfigPayload = Pick<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
type RawReward = RoomRocketRewardItemDto & Record<string, unknown>;
|
type RawReward = RoomRocketRewardItemDto & Record<string, unknown>;
|
||||||
type RawLevel = Omit<
|
type RawLevel = Omit<RoomRocketLevelDto, "igniterRewards" | "inRoomRewards" | "top1Rewards"> & {
|
||||||
RoomRocketLevelDto,
|
|
||||||
"igniterRewards" | "inRoomRewards" | "top1Rewards"
|
|
||||||
> & {
|
|
||||||
igniterRewards?: RawReward[];
|
igniterRewards?: RawReward[];
|
||||||
igniter_rewards?: RawReward[];
|
igniter_rewards?: RawReward[];
|
||||||
inRoomRewards?: RawReward[];
|
inRoomRewards?: RawReward[];
|
||||||
@ -166,10 +164,18 @@ function normalizeRewards(rewards?: RawReward[]): RoomRocketRewardItemDto[] {
|
|||||||
rewardItemId: stringValue(reward.rewardItemId ?? reward.reward_item_id) || `reward_${index + 1}`,
|
rewardItemId: stringValue(reward.rewardItemId ?? reward.reward_item_id) || `reward_${index + 1}`,
|
||||||
resourceGroupId: numberValue(reward.resourceGroupId ?? reward.resource_group_id),
|
resourceGroupId: numberValue(reward.resourceGroupId ?? reward.resource_group_id),
|
||||||
weight: numberValue(reward.weight),
|
weight: numberValue(reward.weight),
|
||||||
|
quantity: positiveNumberValue(reward.quantity) || 1,
|
||||||
displayName: stringValue(reward.displayName ?? reward.display_name),
|
displayName: stringValue(reward.displayName ?? reward.display_name),
|
||||||
iconUrl: stringValue(reward.iconUrl ?? reward.icon_url),
|
iconUrl: stringValue(reward.iconUrl ?? reward.icon_url),
|
||||||
}))
|
}))
|
||||||
.filter((reward) => reward.resourceGroupId > 0 || reward.weight > 0 || reward.displayName || reward.iconUrl);
|
.filter(
|
||||||
|
(reward) =>
|
||||||
|
reward.resourceGroupId > 0 ||
|
||||||
|
reward.weight > 0 ||
|
||||||
|
reward.quantity > 0 ||
|
||||||
|
reward.displayName ||
|
||||||
|
reward.iconUrl,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeGiftFuelRules(rules?: RawGiftFuelRule[]): GiftFuelRuleDto[] {
|
function normalizeGiftFuelRules(rules?: RawGiftFuelRule[]): GiftFuelRuleDto[] {
|
||||||
@ -182,7 +188,9 @@ function normalizeGiftFuelRules(rules?: RawGiftFuelRule[]): GiftFuelRuleDto[] {
|
|||||||
fixedFuel: numberValue(rule.fixedFuel),
|
fixedFuel: numberValue(rule.fixedFuel),
|
||||||
excluded: Boolean(rule.excluded),
|
excluded: Boolean(rule.excluded),
|
||||||
}))
|
}))
|
||||||
.filter((rule) => rule.giftId || rule.giftTypeCode || rule.multiplierPpm > 0 || rule.fixedFuel > 0 || rule.excluded);
|
.filter(
|
||||||
|
(rule) => rule.giftId || rule.giftTypeCode || rule.multiplierPpm > 0 || rule.fixedFuel > 0 || rule.excluded,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringValue(value: unknown) {
|
function stringValue(value: unknown) {
|
||||||
@ -193,3 +201,8 @@ function numberValue(value: unknown) {
|
|||||||
const number = Number(value || 0);
|
const number = Number(value || 0);
|
||||||
return Number.isFinite(number) ? number : 0;
|
return Number.isFinite(number) ? number : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function positiveNumberValue(value: unknown) {
|
||||||
|
const number = numberValue(value);
|
||||||
|
return number > 0 ? number : 0;
|
||||||
|
}
|
||||||
|
|||||||
@ -293,21 +293,27 @@ function LevelNavItem({ active, level, onClick }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function RewardTable({ disabled, levelIndex, poolKey, resourceGroups, rewards, setForm }) {
|
function RewardTable({ disabled, levelIndex, poolKey, resourceGroups, rewards, setForm }) {
|
||||||
|
const quantityEnabled = poolKey === "inRoomRewards";
|
||||||
|
|
||||||
if (!rewards.length) {
|
if (!rewards.length) {
|
||||||
return <div className={styles.emptyState}>未配置奖项</div>;
|
return <div className={styles.emptyState}>未配置奖项</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.rewardTable}>
|
<div className={styles.rewardTable}>
|
||||||
<div className={styles.rewardTableHead}>
|
<div className={[styles.rewardTableHead, quantityEnabled ? styles.rewardTableWithQuantity : ""].join(" ")}>
|
||||||
<span>资源组</span>
|
<span>资源组</span>
|
||||||
<span>权重</span>
|
<span>权重</span>
|
||||||
|
{quantityEnabled ? <span>数量</span> : null}
|
||||||
<span>展示名</span>
|
<span>展示名</span>
|
||||||
<span>图标</span>
|
<span>图标</span>
|
||||||
<span />
|
<span />
|
||||||
</div>
|
</div>
|
||||||
{rewards.map((reward, rewardIndex) => (
|
{rewards.map((reward, rewardIndex) => (
|
||||||
<div className={styles.rewardTableRow} key={reward.rewardItemId || rewardIndex}>
|
<div
|
||||||
|
className={[styles.rewardTableRow, quantityEnabled ? styles.rewardTableWithQuantity : ""].join(" ")}
|
||||||
|
key={reward.rewardItemId || rewardIndex}
|
||||||
|
>
|
||||||
<ResourceGroupSelectField
|
<ResourceGroupSelectField
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
groups={resourceGroups}
|
groups={resourceGroups}
|
||||||
@ -328,6 +334,21 @@ function RewardTable({ disabled, levelIndex, poolKey, resourceGroups, rewards, s
|
|||||||
updateReward(setForm, levelIndex, poolKey, rewardIndex, { weight: event.target.value })
|
updateReward(setForm, levelIndex, poolKey, rewardIndex, { weight: event.target.value })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
{quantityEnabled ? (
|
||||||
|
<TextField
|
||||||
|
required
|
||||||
|
disabled={disabled}
|
||||||
|
inputProps={{ min: 1, step: 1 }}
|
||||||
|
label="数量"
|
||||||
|
type="number"
|
||||||
|
value={reward.quantity}
|
||||||
|
onChange={(event) =>
|
||||||
|
updateReward(setForm, levelIndex, poolKey, rewardIndex, {
|
||||||
|
quantity: event.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
<TextField
|
<TextField
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
label="展示名"
|
label="展示名"
|
||||||
@ -509,6 +530,7 @@ function addReward(setForm, levelIndex, poolKey) {
|
|||||||
{
|
{
|
||||||
displayName: "",
|
displayName: "",
|
||||||
iconUrl: "",
|
iconUrl: "",
|
||||||
|
quantity: "1",
|
||||||
resourceGroupId: "",
|
resourceGroupId: "",
|
||||||
rewardItemId: `level_${level.level}_${poolKey}_${Date.now()}`,
|
rewardItemId: `level_${level.level}_${poolKey}_${Date.now()}`,
|
||||||
weight: "100",
|
weight: "100",
|
||||||
|
|||||||
@ -144,6 +144,7 @@ function rewardsToForm(rewards = []) {
|
|||||||
return rewards.map((reward, index) => ({
|
return rewards.map((reward, index) => ({
|
||||||
displayName: reward.displayName || "",
|
displayName: reward.displayName || "",
|
||||||
iconUrl: reward.iconUrl || "",
|
iconUrl: reward.iconUrl || "",
|
||||||
|
quantity: String(reward.quantity || 1),
|
||||||
resourceGroupId: reward.resourceGroupId ? String(reward.resourceGroupId) : "",
|
resourceGroupId: reward.resourceGroupId ? String(reward.resourceGroupId) : "",
|
||||||
rewardItemId: reward.rewardItemId || `reward_${index + 1}`,
|
rewardItemId: reward.rewardItemId || `reward_${index + 1}`,
|
||||||
weight: String(reward.weight || 100),
|
weight: String(reward.weight || 100),
|
||||||
@ -188,9 +189,13 @@ function rewardsFromForm(rewards = [], level, role) {
|
|||||||
.map((reward, index) => ({
|
.map((reward, index) => ({
|
||||||
displayName: String(reward.displayName || "").trim(),
|
displayName: String(reward.displayName || "").trim(),
|
||||||
iconUrl: String(reward.iconUrl || "").trim(),
|
iconUrl: String(reward.iconUrl || "").trim(),
|
||||||
|
quantity: Number(reward.quantity || 1),
|
||||||
resourceGroupId: Number(reward.resourceGroupId || 0),
|
resourceGroupId: Number(reward.resourceGroupId || 0),
|
||||||
rewardItemId: String(reward.rewardItemId || "").trim() || `level_${level}_${role}_${index + 1}`,
|
rewardItemId: String(reward.rewardItemId || "").trim() || `level_${level}_${role}_${index + 1}`,
|
||||||
weight: Number(reward.weight || 0),
|
weight: Number(reward.weight || 0),
|
||||||
}))
|
}))
|
||||||
.filter((reward) => reward.resourceGroupId || reward.weight || reward.displayName || reward.iconUrl);
|
.filter(
|
||||||
|
(reward) =>
|
||||||
|
reward.resourceGroupId || reward.weight || reward.quantity || reward.displayName || reward.iconUrl,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,14 +30,16 @@ const columns = [
|
|||||||
label: "在房奖励",
|
label: "在房奖励",
|
||||||
width: "minmax(220px, 1fr)",
|
width: "minmax(220px, 1fr)",
|
||||||
render: (level, _index, context) => (
|
render: (level, _index, context) => (
|
||||||
<RewardSummary groups={context.resourceGroups} rewards={level.inRoomRewards} />
|
<RewardSummary showQuantity groups={context.resourceGroups} rewards={level.inRoomRewards} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "top1Rewards",
|
key: "top1Rewards",
|
||||||
label: "贡献第一",
|
label: "贡献第一",
|
||||||
width: "minmax(220px, 1fr)",
|
width: "minmax(220px, 1fr)",
|
||||||
render: (level, _index, context) => <RewardSummary groups={context.resourceGroups} rewards={level.top1Rewards} />,
|
render: (level, _index, context) => (
|
||||||
|
<RewardSummary groups={context.resourceGroups} rewards={level.top1Rewards} />
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "igniterRewards",
|
key: "igniterRewards",
|
||||||
@ -67,7 +69,11 @@ export function RoomRocketPage() {
|
|||||||
if (editing) {
|
if (editing) {
|
||||||
return (
|
return (
|
||||||
<AdminListPage>
|
<AdminListPage>
|
||||||
<DataState error={page.configError} loading={page.configLoading && !page.config} onRetry={page.reloadConfig}>
|
<DataState
|
||||||
|
error={page.configError}
|
||||||
|
loading={page.configLoading && !page.config}
|
||||||
|
onRetry={page.reloadConfig}
|
||||||
|
>
|
||||||
<RoomRocketConfigEditor
|
<RoomRocketConfigEditor
|
||||||
abilities={page.abilities}
|
abilities={page.abilities}
|
||||||
configLoading={page.configLoading}
|
configLoading={page.configLoading}
|
||||||
@ -92,7 +98,11 @@ export function RoomRocketPage() {
|
|||||||
onEdit={enterEdit}
|
onEdit={enterEdit}
|
||||||
onRefresh={page.reloadConfig}
|
onRefresh={page.reloadConfig}
|
||||||
/>
|
/>
|
||||||
<DataState error={page.configError} loading={page.configLoading && !page.config} onRetry={page.reloadConfig}>
|
<DataState
|
||||||
|
error={page.configError}
|
||||||
|
loading={page.configLoading && !page.config}
|
||||||
|
onRetry={page.reloadConfig}
|
||||||
|
>
|
||||||
<AdminListBody>
|
<AdminListBody>
|
||||||
<DataTable
|
<DataTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@ -117,7 +127,10 @@ function MaterialState({ level }) {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.assetBadges}>
|
<div className={styles.assetBadges}>
|
||||||
{items.map(([label, url]) => (
|
{items.map(([label, url]) => (
|
||||||
<span className={["status-badge", url ? "status-badge--running" : "status-badge--stopped"].join(" ")} key={label}>
|
<span
|
||||||
|
className={["status-badge", url ? "status-badge--running" : "status-badge--stopped"].join(" ")}
|
||||||
|
key={label}
|
||||||
|
>
|
||||||
<span className="status-point" />
|
<span className="status-point" />
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
@ -126,18 +139,33 @@ function MaterialState({ level }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function RewardSummary({ groups, rewards = [] }) {
|
function RewardSummary({ groups, rewards = [], showQuantity = false }) {
|
||||||
if (!rewards.length) {
|
if (!rewards.length) {
|
||||||
return <span className={styles.meta}>未配置</span>;
|
return <span className={styles.meta}>未配置</span>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className={styles.stack}>
|
<div className={styles.stack}>
|
||||||
<span className={styles.name}>{rewards.length} 个奖项</span>
|
<span className={styles.name}>
|
||||||
<span className={styles.meta}>{rewards.slice(0, 2).map((reward) => resourceGroupName(groups, reward.resourceGroupId)).join("、")}</span>
|
{rewards.length} 个奖项{showQuantity ? ` · ${rewardQuantityTotal(rewards)} 份` : ""}
|
||||||
|
</span>
|
||||||
|
<span className={styles.meta}>
|
||||||
|
{rewards
|
||||||
|
.slice(0, 2)
|
||||||
|
.map((reward) =>
|
||||||
|
showQuantity
|
||||||
|
? `${resourceGroupName(groups, reward.resourceGroupId)} *${Number(reward.quantity || 1)}`
|
||||||
|
: resourceGroupName(groups, reward.resourceGroupId),
|
||||||
|
)
|
||||||
|
.join("、")}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function rewardQuantityTotal(rewards) {
|
||||||
|
return rewards.reduce((total, reward) => total + Number(reward.quantity || 1), 0);
|
||||||
|
}
|
||||||
|
|
||||||
function resourceGroupName(groups, id) {
|
function resourceGroupName(groups, id) {
|
||||||
const group = groups.find((item) => Number(item.groupId) === Number(id));
|
const group = groups.find((item) => Number(item.groupId) === Number(id));
|
||||||
return group?.name || group?.groupCode || (id ? `#${id}` : "-");
|
return group?.name || group?.groupCode || (id ? `#${id}` : "-");
|
||||||
|
|||||||
@ -288,6 +288,10 @@
|
|||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rewardTableWithQuantity {
|
||||||
|
grid-template-columns: minmax(200px, 1.2fr) 88px 88px minmax(150px, 1fr) minmax(170px, 1fr) var(--control-height);
|
||||||
|
}
|
||||||
|
|
||||||
.rewardTableHead,
|
.rewardTableHead,
|
||||||
.fuelRuleTableHead {
|
.fuelRuleTableHead {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@ -5,6 +5,7 @@ const numberInput = z.union([z.string(), z.number()]);
|
|||||||
const rewardSchema = z.object({
|
const rewardSchema = z.object({
|
||||||
displayName: z.string().optional(),
|
displayName: z.string().optional(),
|
||||||
iconUrl: z.string().optional(),
|
iconUrl: z.string().optional(),
|
||||||
|
quantity: numberInput,
|
||||||
resourceGroupId: numberInput,
|
resourceGroupId: numberInput,
|
||||||
rewardItemId: z.string().optional(),
|
rewardItemId: z.string().optional(),
|
||||||
weight: numberInput,
|
weight: numberInput,
|
||||||
@ -81,7 +82,12 @@ export const roomRocketConfigFormSchema = z
|
|||||||
path: ["giftFuelRules", index, "giftId"],
|
path: ["giftFuelRules", index, "giftId"],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!Number.isInteger(multiplierPpm) || multiplierPpm < 0 || !Number.isInteger(fixedFuel) || fixedFuel < 0) {
|
if (
|
||||||
|
!Number.isInteger(multiplierPpm) ||
|
||||||
|
multiplierPpm < 0 ||
|
||||||
|
!Number.isInteger(fixedFuel) ||
|
||||||
|
fixedFuel < 0
|
||||||
|
) {
|
||||||
context.addIssue({
|
context.addIssue({
|
||||||
code: "custom",
|
code: "custom",
|
||||||
message: "礼物燃料规则数值必须大于等于 0",
|
message: "礼物燃料规则数值必须大于等于 0",
|
||||||
@ -100,11 +106,17 @@ export const roomRocketConfigFormSchema = z
|
|||||||
|
|
||||||
export type RoomRocketConfigForm = z.infer<typeof roomRocketConfigFormSchema>;
|
export type RoomRocketConfigForm = z.infer<typeof roomRocketConfigFormSchema>;
|
||||||
|
|
||||||
function validateRewards(context: z.RefinementCtx, level: number, key: string, rewards: z.infer<typeof rewardSchema>[]) {
|
function validateRewards(
|
||||||
|
context: z.RefinementCtx,
|
||||||
|
level: number,
|
||||||
|
key: string,
|
||||||
|
rewards: z.infer<typeof rewardSchema>[],
|
||||||
|
) {
|
||||||
rewards.forEach((reward, index) => {
|
rewards.forEach((reward, index) => {
|
||||||
const empty =
|
const empty =
|
||||||
!Number(reward.resourceGroupId || 0) &&
|
!Number(reward.resourceGroupId || 0) &&
|
||||||
!Number(reward.weight || 0) &&
|
!Number(reward.weight || 0) &&
|
||||||
|
!Number(reward.quantity || 0) &&
|
||||||
!String(reward.displayName || "").trim() &&
|
!String(reward.displayName || "").trim() &&
|
||||||
!String(reward.iconUrl || "").trim();
|
!String(reward.iconUrl || "").trim();
|
||||||
if (empty) {
|
if (empty) {
|
||||||
@ -124,6 +136,13 @@ function validateRewards(context: z.RefinementCtx, level: number, key: string, r
|
|||||||
path: ["levels", level - 1, key, index, "weight"],
|
path: ["levels", level - 1, key, index, "weight"],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (!positiveInteger(reward.quantity)) {
|
||||||
|
context.addIssue({
|
||||||
|
code: "custom",
|
||||||
|
message: `第 ${level} 级奖励数量必须大于 0`,
|
||||||
|
path: ["levels", level - 1, key, index, "quantity"],
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user