feat(category): 类目树、属性、属性字典维护,新增系统管理员管理类目数据
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import { api } from '@/services/api';
|
||||
|
||||
export type AdminPlatform = {
|
||||
id: string; code: string; name: string; status: string;
|
||||
source: { configured: boolean; client_id_hint: string | null; updated_at: string | null };
|
||||
summary: { category_count: number; last_tree_sync_at: string | null; last_error: string | null };
|
||||
};
|
||||
|
||||
export type CatalogCategory = {
|
||||
id: string; parent_id: string | null; external_category_id: string;
|
||||
external_type_id: string | null; name: string; path: string;
|
||||
is_active: boolean; synced_at: string | null;
|
||||
};
|
||||
|
||||
export type CatalogAttribute = {
|
||||
id: string; platform_attribute_id: string; complex_id: string;
|
||||
name: string; description: string | null; group_id: string | null;
|
||||
group_name: string | null; value_type: string; is_required: boolean;
|
||||
is_aspect: boolean; is_collection: boolean; max_value_count: number;
|
||||
dictionary_id: string | null; category_dependent: boolean;
|
||||
is_active: boolean; synced_at: string | null; dictionary_value_count: number;
|
||||
};
|
||||
|
||||
export type CatalogValue = {
|
||||
id: string; external_value_id: string; value: string;
|
||||
info: string | null; picture: string | null;
|
||||
};
|
||||
|
||||
export type CatalogRun = {
|
||||
id: string; platform_id: string; scope: 'tree' | 'attributes' | 'dictionary';
|
||||
scope_key: string; category_id: string | null; attribute_id: string | null;
|
||||
trigger: string; status: string; result: Record<string, unknown>;
|
||||
error_code: string | null; error_message: string | null;
|
||||
attempt_count: number; created_at: string;
|
||||
started_at: string | null; completed_at: string | null;
|
||||
};
|
||||
|
||||
export type SyncRequest =
|
||||
| { scope: 'tree' }
|
||||
| { scope: 'attributes'; category_id: string }
|
||||
| { scope: 'dictionary'; attribute_id: string };
|
||||
|
||||
export const platformCatalogAdminApi = {
|
||||
access: () => api.get<{ is_system_admin: boolean }>('/system/access'),
|
||||
platforms: () => api.get<AdminPlatform[]>('/system/platform-catalog/platforms'),
|
||||
configureSource: (platformId: string, input: { client_id: string; api_key: string }) =>
|
||||
api.put<{ configured: boolean; client_id_hint: string; revision: number; changed: boolean }>(
|
||||
`/system/platform-catalog/platforms/${platformId}/source`, input,
|
||||
),
|
||||
categories: (platformId: string) =>
|
||||
api.get<CatalogCategory[]>(`/system/platform-catalog/platforms/${platformId}/categories`),
|
||||
attributes: (platformId: string, categoryId: string) =>
|
||||
api.get<CatalogAttribute[]>(
|
||||
`/system/platform-catalog/platforms/${platformId}/categories/${categoryId}/attributes`,
|
||||
),
|
||||
values: (platformId: string, attributeId: string, page: number, keyword: string) =>
|
||||
api.getList<CatalogValue, { page: number; page_size: number; total: number; is_complete: boolean }>(
|
||||
`/system/platform-catalog/platforms/${platformId}/attributes/${attributeId}/values`,
|
||||
{ params: { page, q: keyword } },
|
||||
),
|
||||
sync: (platformId: string, input: SyncRequest) =>
|
||||
api.post<{ id: string; status: string }>(
|
||||
`/system/platform-catalog/platforms/${platformId}/syncs`, input,
|
||||
{ headers: { 'Idempotency-Key': crypto.randomUUID() } },
|
||||
),
|
||||
runs: (platformId: string) =>
|
||||
api.get<CatalogRun[]>(`/system/platform-catalog/platforms/${platformId}/syncs?limit=20`),
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
ProductEditorCategoryOption,
|
||||
ProductEditorContext,
|
||||
ProductEditorSnapshot,
|
||||
ProductEditorValidation,
|
||||
Operation,
|
||||
SaveProductEditorDraftRequest,
|
||||
SaveProductEditorDraftResponse,
|
||||
@@ -39,22 +40,42 @@ export const productEditorApi = {
|
||||
'If-Match': `"${revisions.product}:${revisions.profile}"`,
|
||||
},
|
||||
}),
|
||||
categoryOptions: (profileId: string, platformId: string, search?: string, storeId?: string | null) =>
|
||||
categoryOptions: (
|
||||
profileId: string,
|
||||
platformId: string,
|
||||
search?: string,
|
||||
storeId?: string | null
|
||||
) =>
|
||||
api.get<ProductEditorCategoryOption[]>(
|
||||
`/product-platform-profiles/${profileId}/category-options`,
|
||||
{
|
||||
params: { ...(search ? { q: search } : {}), ...(storeId ? { store_id: storeId } : {}) },
|
||||
params: {
|
||||
...(search ? { q: search } : {}),
|
||||
...(storeId ? { store_id: storeId } : {}),
|
||||
},
|
||||
headers: { 'X-Atlas-Platform-Id': platformId },
|
||||
}
|
||||
),
|
||||
attributeSchema: (profileId: string, platformId: string, categoryId?: string | null, storeId?: string | null) =>
|
||||
attributeSchema: (
|
||||
profileId: string,
|
||||
platformId: string,
|
||||
categoryId?: string | null,
|
||||
storeId?: string | null
|
||||
) =>
|
||||
api.get<ProductAttributeSchema[]>(
|
||||
`/product-platform-profiles/${profileId}/attribute-schema`,
|
||||
{
|
||||
params: { ...(categoryId ? { category_id: categoryId } : {}), ...(storeId ? { store_id: storeId } : {}) },
|
||||
params: {
|
||||
...(categoryId ? { category_id: categoryId } : {}),
|
||||
...(storeId ? { store_id: storeId } : {}),
|
||||
},
|
||||
headers: { 'X-Atlas-Platform-Id': platformId },
|
||||
}
|
||||
),
|
||||
validation: (profileId: string, platformId: string) =>
|
||||
api.get<ProductEditorValidation>(`/product-platform-profiles/${profileId}/validation`, {
|
||||
headers: { 'X-Atlas-Platform-Id': platformId },
|
||||
}),
|
||||
syncAttributeSchema: (profileId: string, platformId: string, categoryId: string) =>
|
||||
api.post<{ operation: Operation }>(
|
||||
`/product-platform-profiles/${profileId}/attribute-schema-syncs`,
|
||||
@@ -74,6 +95,7 @@ export const productEditorApi = {
|
||||
search?: string,
|
||||
categoryId?: string | null,
|
||||
storeId?: string | null,
|
||||
cursor?: string | null
|
||||
) =>
|
||||
api.get<{ items: ProductAttributeDictionaryValue[]; next_cursor: string | null }>(
|
||||
`/product-platform-profiles/${profileId}/attribute-dictionaries/${encodeURIComponent(dictionaryId)}`,
|
||||
@@ -82,6 +104,7 @@ export const productEditorApi = {
|
||||
...(categoryId ? { category_id: categoryId } : {}),
|
||||
...(storeId ? { store_id: storeId } : {}),
|
||||
...(search ? { q: search } : {}),
|
||||
...(cursor ? { cursor } : {}),
|
||||
},
|
||||
headers: { 'X-Atlas-Platform-Id': platformId },
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { platformCatalogAdminApi } from './platformCatalogAdmin';
|
||||
import { useSession } from './session';
|
||||
|
||||
type AccessState = 'checking' | 'allowed' | 'denied';
|
||||
|
||||
/** 菜单可见性使用只读探测;后台 API 仍独立校验数据库授权。 */
|
||||
export function useSystemAdminAccess(): AccessState {
|
||||
const { status } = useSession();
|
||||
const [access, setAccess] = useState<AccessState>('checking');
|
||||
useEffect(() => {
|
||||
if (status !== 'authenticated') {
|
||||
setAccess('checking');
|
||||
return;
|
||||
}
|
||||
let active = true;
|
||||
void platformCatalogAdminApi.access()
|
||||
.then((result) => { if (active) setAccess(result.is_system_admin ? 'allowed' : 'denied'); })
|
||||
.catch(() => { if (active) setAccess('denied'); });
|
||||
return () => { active = false; };
|
||||
}, [status]);
|
||||
return access;
|
||||
}
|
||||
Reference in New Issue
Block a user