ad2b37c6d3
- shared-contracts:删掉 `EDITOR_GENERATION_PRICING_VERSION_CONFLICT` 常量 - spacetime-module:`EditorGenerationPricingConfigUpsertInput` 去掉期望版本字段,删掉版本比对方法与它的用例,入库只按请求内容整段覆盖 - spacetime-client:重新生成定价 upsert input 绑定(少一个字段),记录映射同步去掉版本 - api-server:进程内缓存去掉版本字段与 409 冲突错误,`editor_generation_pricing()` / `save_editor_generation_pricing()` 只返回配置本身,`EditorGenerationPricingStore::replace` 注释写明「只是缓存刷新、不做版本校验、不是拦截点」 - api-server admin/app:后台 GET 不再返回 `updatedAtMicros`,POST 不再读 `expectedUpdatedAtMicros`,删掉 409 映射与「旧版本被拒」用例,新增「连续保存两次以后一次为准」 - admin-web:定价类型去掉版本字段,保存请求不再回传版本,删掉 409 分支与「重新读取」提示及其用例 - docs:主规范新增「保存语义」段(整段覆盖、同时只有一个管理员在操作、缓存替换不是拦截点),后端数据契约、ADR 0005、里程碑 / 实施计划、决策记录与 pitfalls 同步为删除版本比对
116 lines
4.1 KiB
TypeScript
116 lines
4.1 KiB
TypeScript
import type {
|
|
AdminUpsertEditorGenerationPricingRequest,
|
|
EditorGenerationModel3dEndpointPricingPayload,
|
|
EditorGenerationModel3dPricingPayload,
|
|
EditorGenerationPricingConfigPayload,
|
|
} from '../api/adminApiTypes';
|
|
|
|
/** 3D 端点在两个并列段里的字段名与后台展示名。 */
|
|
export const EDITOR_MODEL3D_ENDPOINT_SECTIONS = [
|
|
{ key: 'textToModelPricing', label: '文生 3D' },
|
|
{ key: 'imageToModelPricing', label: '图生 3D' },
|
|
] as const satisfies ReadonlyArray<{
|
|
key: keyof EditorGenerationModel3dPricingPayload;
|
|
label: string;
|
|
}>;
|
|
|
|
/**
|
|
* 3D 加价项键到中文标签。键来自接口返回,这里只负责展示;遇到未登记的键回退显示原键名,
|
|
* 不因为标签缺失就把该项漏掉。
|
|
*/
|
|
const EDITOR_MODEL3D_ADD_ON_LABELS: Record<string, string> = {
|
|
hdTexture: '高清贴图',
|
|
ultraTexture: '超高清贴图',
|
|
hdGeometry: '高清几何',
|
|
quadMesh: '四边形网格',
|
|
smartLowPoly: '智能低面',
|
|
generateParts: '分件生成',
|
|
};
|
|
|
|
export function editorModel3dAddOnLabel(addOn: string) {
|
|
return EDITOR_MODEL3D_ADD_ON_LABELS[addOn] ?? addOn;
|
|
}
|
|
|
|
export function isPositiveMudPoints(value: number | null | undefined) {
|
|
return typeof value === 'number' && Number.isInteger(value) && value >= 1;
|
|
}
|
|
|
|
/**
|
|
* 提交前校验:所有泥点数值都必须是不小于 1 的整数,返回不合法项的展示名。
|
|
*
|
|
* 后端仍会独立校验一遍;这里只是不把明显非法的 payload 发出去,避免保存后才报错。
|
|
*/
|
|
export function collectInvalidEditorGenerationPricingLabels(
|
|
pricing: EditorGenerationPricingConfigPayload,
|
|
) {
|
|
const labels: string[] = [];
|
|
for (const [model, modelPricing] of Object.entries(pricing.models)) {
|
|
const tieredPrices = modelPricing.prices;
|
|
// `prices: {}` 没有档位可校验,不能算「这一项走分层计价」——否则底价会被顺手跳过校验。
|
|
if (tieredPrices && Object.keys(tieredPrices).length > 0) {
|
|
for (const [tier, price] of Object.entries(tieredPrices)) {
|
|
if (!isPositiveMudPoints(price)) {
|
|
labels.push(`${model} ${tier}`);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
if (!isPositiveMudPoints(modelPricing.price)) {
|
|
labels.push(`${model} 泥点`);
|
|
}
|
|
}
|
|
|
|
const model3d = pricing.model3d;
|
|
if (model3d) {
|
|
for (const { key, label } of EDITOR_MODEL3D_ENDPOINT_SECTIONS) {
|
|
// 段与子表都来自接口返回:缺段或缺子表时只跳过这一段,不能让整页校验崩掉。
|
|
const endpointPricing = model3d[key];
|
|
if (!endpointPricing) {
|
|
continue;
|
|
}
|
|
for (const [version, price] of Object.entries(
|
|
endpointPricing.versionPrices ?? {},
|
|
)) {
|
|
if (!isPositiveMudPoints(price.noTexture)) {
|
|
labels.push(`${label} ${version} 无贴图`);
|
|
}
|
|
if (!isPositiveMudPoints(price.texture)) {
|
|
labels.push(`${label} ${version} 带贴图`);
|
|
}
|
|
}
|
|
for (const [addOn, price] of Object.entries(
|
|
endpointPricing.addOnPrices ?? {},
|
|
)) {
|
|
if (!isPositiveMudPoints(price)) {
|
|
labels.push(`${label} ${editorModel3dAddOnLabel(addOn)}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return labels;
|
|
}
|
|
|
|
/**
|
|
* 组装保存请求:`models` 与 3D 两段整段回传,不带定价版本(保存即覆盖,服务端不比对版本)。
|
|
* 3D 段缺失时回传 `null`——服务端会以 400 拒绝,后台不允许在这里静默新建或丢弃整段。
|
|
*/
|
|
export function buildAdminEditorGenerationPricingRequest(
|
|
pricing: EditorGenerationPricingConfigPayload,
|
|
): AdminUpsertEditorGenerationPricingRequest {
|
|
return {
|
|
models: pricing.models,
|
|
model3d: pricing.model3d ?? null,
|
|
};
|
|
}
|
|
|
|
/** 单个端点段的不可变更新:底价与加价项各改一处,其余原样保留。 */
|
|
export function updateEditorModel3dEndpointPricing(
|
|
model3d: EditorGenerationModel3dPricingPayload,
|
|
section: keyof EditorGenerationModel3dPricingPayload,
|
|
updater: (
|
|
current: EditorGenerationModel3dEndpointPricingPayload,
|
|
) => EditorGenerationModel3dEndpointPricingPayload,
|
|
): EditorGenerationModel3dPricingPayload {
|
|
return { ...model3d, [section]: updater(model3d[section]) };
|
|
}
|