feat(edit): 商品编辑页开发

This commit is contained in:
R524809
2026-09-10 17:45:46 +08:00
parent 130a005231
commit 946af649dd
22 changed files with 7034 additions and 518 deletions
@@ -1,6 +1,21 @@
import { Body, Controller, Get, Headers, Inject, Param, Post, Put, Req, Res } from '@nestjs/common';
import {
Body,
Controller,
Get,
Headers,
Inject,
Param,
Post,
Put,
Query,
Req,
Res,
} from '@nestjs/common';
import { z } from 'zod';
import { saveProductEditorDraftRequestSchema, type Workspace } from '@atlas/contracts';
import {
saveProductEditorDraftRequestSchema,
type Workspace,
} from '@atlas/contracts';
import { ApiError } from '../common/api-error.js';
import type { AuthenticatedRequest } from '../auth/auth.guard.js';
import { CurrentWorkspace } from '../workspace/current-workspace.decorator.js';
@@ -50,6 +65,137 @@ export class ProductEditorController {
};
}
@Get('product-platform-profiles/:profileId/category-options')
async categoryOptions(
@Param('profileId') profileId: string,
@Query('q') query: string | undefined,
@Headers('x-atlas-platform-id') platformId: string | undefined,
@CurrentWorkspace() workspace: Workspace,
) {
return {
data: await this.editor.categoryOptions(
workspace.id,
requiredUuid('profile_id', profileId),
requiredUuid('X-Atlas-Platform-Id', platformId),
query,
),
};
}
@Post('product-platform-profiles/:profileId/category-syncs')
async categorySync(
@Param('profileId') profileId: string,
@Headers('x-atlas-platform-id') platformId: string | undefined,
@Headers('idempotency-key') key: string | undefined,
@Req() request: AuthenticatedRequest,
@CurrentWorkspace() workspace: Workspace,
@Res({ passthrough: true }) response: Response,
) {
const result = await this.editor.requestCategoryTreeSync(
workspace.id,
request.auth.userId,
requiredUuid('profile_id', profileId),
requiredUuid('X-Atlas-Platform-Id', platformId),
requiredUuid('Idempotency-Key', key),
);
response.status(result.status);
return result.body;
}
@Get('product-platform-profiles/:profileId/attribute-schema')
async attributeSchema(
@Param('profileId') profileId: string,
@Query('category_id') categoryId: string | undefined,
@Headers('x-atlas-platform-id') platformId: string | undefined,
@CurrentWorkspace() workspace: Workspace,
) {
return {
data: await this.editor.attributeSchema(
workspace.id,
requiredUuid('profile_id', profileId),
requiredUuid('X-Atlas-Platform-Id', platformId),
optionalUuid('category_id', categoryId),
),
};
}
@Post('product-platform-profiles/:profileId/attribute-schema-syncs')
async attributeSchemaSync(
@Param('profileId') profileId: string,
@Query('category_id') categoryId: string | undefined,
@Headers('x-atlas-platform-id') platformId: string | undefined,
@Headers('idempotency-key') key: string | undefined,
@Req() request: AuthenticatedRequest,
@CurrentWorkspace() workspace: Workspace,
@Res({ passthrough: true }) response: Response,
) {
const result = await this.editor.requestAttributeSchemaSync(
workspace.id,
request.auth.userId,
requiredUuid('profile_id', profileId),
requiredUuid('X-Atlas-Platform-Id', platformId),
requiredUuid('Idempotency-Key', key),
requiredUuid('category_id', categoryId),
);
response.status(result.status);
return result.body;
}
@Get(
'product-platform-profiles/:profileId/attribute-dictionaries/:dictionaryId',
)
async attributeDictionary(
@Param('profileId') profileId: string,
@Param('dictionaryId') dictionaryId: string,
@Query('q') query: string | undefined,
@Query('category_id') categoryId: string | undefined,
@Headers('x-atlas-platform-id') platformId: string | undefined,
@CurrentWorkspace() workspace: Workspace,
) {
if (!dictionaryId.trim() || dictionaryId.length > 200)
throw new ApiError(400, 'VALIDATION_FAILED', '字典标识不合法');
return {
data: await this.editor.attributeDictionary(
workspace.id,
requiredUuid('profile_id', profileId),
requiredUuid('X-Atlas-Platform-Id', platformId),
dictionaryId,
query,
optionalUuid('category_id', categoryId),
),
};
}
@Post(
'product-platform-profiles/:profileId/attribute-dictionaries/:dictionaryId/syncs',
)
async attributeDictionarySync(
@Param('profileId') profileId: string,
@Param('dictionaryId') dictionaryId: string,
@Query('category_id') categoryId: string | undefined,
@Query('q') query: string | undefined,
@Headers('x-atlas-platform-id') platformId: string | undefined,
@Headers('idempotency-key') key: string | undefined,
@Req() request: AuthenticatedRequest,
@CurrentWorkspace() workspace: Workspace,
@Res({ passthrough: true }) response: Response,
) {
if (!dictionaryId.trim() || dictionaryId.length > 200)
throw new ApiError(400, 'VALIDATION_FAILED', '字典标识不合法');
const result = await this.editor.requestAttributeDictionarySync(
workspace.id,
request.auth.userId,
requiredUuid('profile_id', profileId),
requiredUuid('X-Atlas-Platform-Id', platformId),
requiredUuid('Idempotency-Key', key),
requiredUuid('category_id', categoryId),
dictionaryId,
query ?? '',
);
response.status(result.status);
return result.body;
}
@Put('products/:productId/editor-draft')
async save(
@Param('productId') productId: string,
@@ -62,19 +208,28 @@ export class ProductEditorController {
) {
const parsed = saveProductEditorDraftRequestSchema.safeParse(body);
if (!parsed.success) {
throw new ApiError(422, 'VALIDATION_FAILED', '请求参数不符合要求', parsed.error.issues.map((issue) => ({
field: issue.path.join('.'), code: 'INVALID_FIELD', message: issue.message,
})));
throw new ApiError(
422,
'VALIDATION_FAILED',
'请求参数不符合要求',
parsed.error.issues.map((issue) => ({
field: issue.path.join('.'),
code: 'INVALID_FIELD',
message: issue.message,
})),
);
}
return (await this.editor.save(
workspace.id,
request.auth.userId,
requiredUuid('product_id', productId),
requiredUuid('X-Atlas-Platform-Id', platformId),
requiredUuid('Idempotency-Key', key),
parseEditorRevision(ifMatch),
parsed.data,
)).body;
return (
await this.editor.save(
workspace.id,
request.auth.userId,
requiredUuid('product_id', productId),
requiredUuid('X-Atlas-Platform-Id', platformId),
requiredUuid('Idempotency-Key', key),
parseEditorRevision(ifMatch),
parsed.data,
)
).body;
}
}
@@ -85,8 +240,18 @@ function requiredUuid(field: string, value: string | undefined) {
]);
}
function optionalUuid(field: string, value: string | undefined) {
if (value === undefined || value === '') return undefined;
return requiredUuid(field, value);
}
function parseEditorRevision(value: string | undefined) {
const match = value?.match(/^"([1-9]\d*):([1-9]\d*)"$/);
if (!match) throw new ApiError(400, 'IF_MATCH_REQUIRED', '请求缺少或格式错误的 If-Match');
if (!match)
throw new ApiError(
400,
'IF_MATCH_REQUIRED',
'请求缺少或格式错误的 If-Match',
);
return { product: Number(match[1]), profile: Number(match[2]) };
}