Files
Genarrative/apps/admin-web/src/pages/AdminEditorGenerationModel3dPricingSection.tsx
T
k88936 9bcd8f728b 后台各页的整数解析统一到共享助手
- apps/admin-web/src/pages/pageUtils.ts:新增 parsePositiveInteger,用 Number + Number.isSafeInteger,空串 / 非数字 / 小数 / 0 / 负数一律收敛成 0
- 删除 AdminTaskConfigPage、AdminProfileWalletConfigPage、AdminRechargeProductPage、AdminRedeemCodePage 与 adminEditorGenerationPricing 里各自的一份实现,5 个页面统一从 pageUtils 引入,修掉「钱包页拒 1.5、任务页把 1.5 当 1」这类口径不一致
- adminEditorGenerationPricing.test.ts 的引入路径同步改到 pageUtils
2026-09-24 19:07:32 +08:00

132 lines
5.1 KiB
TypeScript

import type {
EditorGenerationModel3dPricingPayload,
EditorGenerationModel3dVersionPricePayload,
} from '../api/adminApiTypes';
import {
EDITOR_MODEL3D_ENDPOINT_SECTIONS,
editorModel3dAddOnLabel,
updateEditorModel3dEndpointPricing,
} from './adminEditorGenerationPricing';
import { parsePositiveInteger } from './pageUtils';
/**
* 一个模型版本的两个底价字段。两栏只有字段键与展示名不同,用描述表驱动渲染,
* 避免两段近乎相同的 JSX 各自演化。
*/
const VERSION_PRICE_FIELDS = [
{ key: 'noTexture', label: '无贴图' },
{ key: 'texture', label: '带贴图' },
] as const satisfies ReadonlyArray<{
key: keyof EditorGenerationModel3dVersionPricePayload;
label: string;
}>;
interface AdminEditorGenerationModel3dPricingSectionProps {
model3d: EditorGenerationModel3dPricingPayload;
onChange: (next: EditorGenerationModel3dPricingPayload) => void;
}
/**
* 3D 生成定价区块:按端点分成「文生 3D」与「图生 3D」两组,每组是该端点自己的
* 「模型版本 × {无贴图, 带贴图}」底价表与加价项单值输入。
*
* 键集合全部来自接口返回,后台只能改数值:不能新增、删除键,也不能停用整段。
*/
export function AdminEditorGenerationModel3dPricingSection({
model3d,
onChange,
}: AdminEditorGenerationModel3dPricingSectionProps) {
function updateEndpoint(
section: (typeof EDITOR_MODEL3D_ENDPOINT_SECTIONS)[number]['key'],
updater: Parameters<typeof updateEditorModel3dEndpointPricing>[2],
) {
onChange(updateEditorModel3dEndpointPricing(model3d, section, updater));
}
return (
<section className="admin-panel admin-pricing-model-card">
<div className="admin-pricing-model-heading">
<strong>3D 生成定价</strong>
<span className="admin-pricing-unit">按次</span>
</div>
<p className="admin-pricing-model3d-hint">
模型版本与加价项由后端契约给出,只能改数值;两段必须完整给出,缺失即 3D
生成不可提交。
</p>
<div className="admin-pricing-grid">
{EDITOR_MODEL3D_ENDPOINT_SECTIONS.map(({ key, label }) => (
<section className="admin-panel admin-pricing-model-card" key={key}>
<div className="admin-pricing-model-heading">
<strong>{label}</strong>
</div>
<div className="admin-pricing-model3d-list">
{Object.entries(model3d[key].versionPrices).map(
([version, price]) => (
<div className="admin-pricing-model3d-row" key={version}>
<span className="admin-pricing-model3d-row-label">
{version}
</span>
{VERSION_PRICE_FIELDS.map(
({ key: field, label: fieldLabel }) => (
<label className="admin-field" key={field}>
<span>{fieldLabel}</span>
<input
aria-label={`${label} ${version} ${fieldLabel}`}
min={1}
step={1}
type="number"
value={price[field]}
onChange={(event) =>
updateEndpoint(key, (current) => ({
...current,
versionPrices: {
...current.versionPrices,
[version]: {
...price,
[field]: parsePositiveInteger(
event.target.value,
),
},
},
}))
}
/>
</label>
),
)}
</div>
),
)}
</div>
<div className="admin-form-row">
{Object.entries(model3d[key].addOnPrices).map(
([addOn, price]) => (
<label className="admin-field" key={addOn}>
<span>{editorModel3dAddOnLabel(addOn)}</span>
<input
aria-label={`${label} ${editorModel3dAddOnLabel(addOn)}`}
min={1}
step={1}
type="number"
value={price}
onChange={(event) =>
updateEndpoint(key, (current) => ({
...current,
addOnPrices: {
...current.addOnPrices,
[addOn]: parsePositiveInteger(event.target.value),
},
}))
}
/>
</label>
),
)}
</div>
</section>
))}
</div>
</section>
);
}