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
132 lines
5.1 KiB
TypeScript
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>
|
|
);
|
|
}
|