feat(web): 增加参数即时估价与显式免费编辑

估价请求统一使用 350ms 防抖、AbortController 和请求序号,区分免费、计算中、价格不可用与失败状态,并展示候选冻结上限。价格未就绪时阻止生产提交。\n\n计价规则编辑器新增显式免费开关和 9 位价格步进。\n\n验证:Web 88 项测试和生产构建通过。
This commit is contained in:
2026-07-20 23:22:50 +08:00
parent 5114686c35
commit 7cea21f765
5 changed files with 193 additions and 21 deletions
@@ -221,7 +221,17 @@ function ModeRuleEditor(props: {
<Select size="sm" value={rule.status ?? 'active'} onChange={(event) => patch({ status: event.target.value })}>{statuses.map((item) => <option key={item} value={item}>{item}</option>)}</Select>
</PricingFormRow>
<PricingFormRow label={`基础单价/${unitLabel(rule.unit)}`}>
<Input size="sm" min="0" step="0.0001" type="number" value={rule.basePrice} onChange={(event) => patch({ basePrice: Number(event.target.value) })} />
<Input disabled={rule.isFree} size="sm" min="0" step="0.000000001" type="number" value={rule.basePrice} onChange={(event) => patch({ basePrice: Number(event.target.value) })} />
</PricingFormRow>
<PricingFormRow label="显式免费">
<label>
<input
type="checkbox"
checked={rule.isFree ?? false}
onChange={(event) => patch({ isFree: event.target.checked, basePrice: event.target.checked ? 0 : rule.basePrice })}
/>
<span></span>
</label>
</PricingFormRow>
<PricingReadonlyRow label="计价单位" value={unitLabel(rule.unit)} />
<PricingReadonlyRow label="计算方式" value={calculatorLabel(rule.calculatorType)} />
@@ -311,11 +321,32 @@ function TextRuleEditor(props: {
<PricingFormRow label="状态">
<Select size="sm" value={props.rule.status ?? 'active'} onChange={(event) => props.onChange({ ...props.rule, status: event.target.value })}>{statuses.map((item) => <option key={item} value={item}>{item}</option>)}</Select>
</PricingFormRow>
<PricingFormRow label="显式免费">
<label>
<input
type="checkbox"
checked={props.rule.isFree ?? false}
onChange={(event) => {
const isFree = event.target.checked;
props.onChange({
...props.rule,
isFree,
basePrice: isFree ? 0 : props.rule.basePrice,
formulaConfig: isFree
? { ...(props.rule.formulaConfig ?? {}), inputTokenPrice: 0, cachedInputTokenPrice: 0, outputTokenPrice: 0 }
: props.rule.formulaConfig,
});
}}
/>
<span></span>
</label>
</PricingFormRow>
<PricingFormRow label="输入单价/1K tokens">
<Input
disabled={props.rule.isFree}
size="sm"
min="0"
step="0.0001"
step="0.000000001"
type="number"
value={prices.inputTokenPrice}
onChange={(event) =>
@@ -329,9 +360,10 @@ function TextRuleEditor(props: {
</PricingFormRow>
<PricingFormRow label="缓存输入单价/1K tokens">
<Input
disabled={props.rule.isFree}
size="sm"
min="0"
step="0.0001"
step="0.000000001"
type="number"
value={prices.cachedInputTokenPrice}
onChange={(event) =>
@@ -345,9 +377,10 @@ function TextRuleEditor(props: {
</PricingFormRow>
<PricingFormRow label="输出单价/1K tokens">
<Input
disabled={props.rule.isFree}
size="sm"
min="0"
step="0.0001"
step="0.000000001"
type="number"
value={prices.outputTokenPrice}
onChange={(event) =>
@@ -225,6 +225,7 @@ function ruleToInput(rule: PricingRule): PricingRuleInput {
resourceType: rule.resourceType,
unit: rule.unit,
basePrice: rule.basePrice,
isFree: rule.isFree,
currency: rule.currency,
baseWeight: rule.baseWeight ?? {},
dynamicWeight: rule.dynamicWeight ?? {},
@@ -234,6 +235,8 @@ function ruleToInput(rule: PricingRule): PricingRuleInput {
priority: rule.priority,
status: rule.status,
metadata: rule.metadata ?? {},
effectiveFrom: rule.effectiveFrom,
effectiveTo: rule.effectiveTo,
};
}