feat(category): 类目树、属性、属性字典维护,新增系统管理员管理类目数据

This commit is contained in:
R524809
2026-09-18 18:55:16 +08:00
parent fcddb11da0
commit 388429dbd0
58 changed files with 4347 additions and 640 deletions
+75 -8
View File
@@ -87,18 +87,85 @@ export async function fetchOzonDictionaryValues(
},
) {
const query = input.query?.trim() ?? '';
const path = query
? '/v1/description-category/attribute/values/search'
: '/v1/description-category/attribute/values';
const body = await requestOzon(clientId, apiKey, path, {
const request = {
attribute_id: numberOrText(input.attributeId),
description_category_id: numberOrText(input.categoryId),
type_id: numberOrText(input.typeId),
language: 'ZH_HANS',
limit: query ? 100 : 1_000,
...(query ? { value: query } : { last_value_id: 0 }),
});
return readResultArray<OzonDictionaryValue>(body, '属性字典');
};
if (query) {
const body = await requestOzon(
clientId, apiKey, '/v1/description-category/attribute/values/search',
{ ...request, limit: 100, value: query },
);
return readResultArray<OzonDictionaryValue>(body, '属性字典');
}
const values: OzonDictionaryValue[] = [];
const seen = new Set<string>();
let lastValueId: number | string = 0;
// 大字典仅预取首批 1000 项;后续值由关键词搜索补充,重复游标不得造成无限循环。
for (let pageIndex = 0; pageIndex < 20 && values.length < 1_000; pageIndex += 1) {
const body = await requestOzon(
clientId, apiKey, '/v1/description-category/attribute/values',
{ ...request, limit: 1_000 - values.length, last_value_id: lastValueId },
);
const page = readResultArray<OzonDictionaryValue>(body, '属性字典');
let added = 0;
for (const value of page) {
const id = String(value.id ?? '');
if (!id || seen.has(id)) continue;
seen.add(id);
values.push(value);
added += 1;
}
const nextId = page.at(-1)?.id;
if (
(body as { has_next?: unknown }).has_next !== true || !page.length ||
!added || nextId == null || String(nextId) === String(lastValueId)
) break;
lastValueId = typeof nextId === 'string' ? numberOrText(nextId) : nextId;
}
return values;
}
/** 平台级字典按页获取,明确返回完整性;大字典不会伪装成已全量同步。 */
export async function fetchOzonDictionaryBatch(
clientId: string,
apiKey: string,
input: { attributeId: string; categoryId: string; typeId: string },
): Promise<{ values: OzonDictionaryValue[]; isComplete: boolean }> {
const values: OzonDictionaryValue[] = [];
const seen = new Set<string>();
let lastValueId: number | string = 0;
for (let pageIndex = 0; pageIndex < 5; pageIndex += 1) {
const body = await requestOzon(
clientId, apiKey, '/v1/description-category/attribute/values',
{
attribute_id: numberOrText(input.attributeId),
description_category_id: numberOrText(input.categoryId),
type_id: numberOrText(input.typeId),
language: 'ZH_HANS',
limit: 2000,
last_value_id: lastValueId,
},
);
const page = readResultArray<OzonDictionaryValue>(body, '属性字典');
for (const value of page) {
const id = String(value.id ?? '').trim();
if (id && !seen.has(id)) {
seen.add(id);
values.push(value);
}
}
const hasNext = (body as { has_next?: unknown }).has_next === true;
if (!hasNext) return { values, isComplete: true };
const nextId = page.at(-1)?.id;
if (!page.length || nextId == null || String(nextId) === String(lastValueId)) {
throw new OzonRetryableError('Ozon 字典分页游标没有前进');
}
lastValueId = nextId;
}
return { values, isComplete: false };
}
async function requestOzon(