feat(extension): 采集插件功能开发
This commit is contained in:
@@ -32,6 +32,14 @@ const LoginPage = () => {
|
||||
});
|
||||
applyAuthSession(session);
|
||||
message.success('登录成功');
|
||||
const continuation = new URLSearchParams(location.search).get('continue');
|
||||
if (continuation) {
|
||||
const target = new URL(continuation);
|
||||
if (target.origin === (import.meta.env.VITE_API_PUBLIC_ORIGIN ?? 'http://localhost:8200') && target.pathname === '/api/extension/authorize') {
|
||||
window.location.assign(target.toString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 守卫记住的来源路径优先,默认进入在线商品首页
|
||||
const from = (location.state as { from?: string } | null)?.from;
|
||||
navigate(from && from.startsWith('/') ? from : '/online-products', {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Alert, Card, Collapse, Descriptions, Spin, Table, Tag, Typography } from 'antd';
|
||||
import type { CollectionItemInspection } from '@atlas/contracts';
|
||||
import { collectionApi } from '@/services/collection';
|
||||
import { apiErrorMessage } from '@/services/api';
|
||||
|
||||
/** Temporary parser inspection surface. This is deliberately read-only: it lets
|
||||
* product and parser work be checked before these values become editor fields. */
|
||||
export default function CollectionInspectionPage() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const [record, setRecord] = useState<CollectionItemInspection>();
|
||||
const [error, setError] = useState<string>();
|
||||
useEffect(() => { if (!id) return; void collectionApi.inspection(id).then(setRecord).catch((reason) => setError(apiErrorMessage(reason))); }, [id]);
|
||||
if (error) return <Alert type="error" message="无法读取采集数据" description={error} showIcon />;
|
||||
if (!record) return <div style={{ padding: 64, textAlign: 'center' }}><Spin size="large" /></div>;
|
||||
return <Card title="采集数据测试页" extra={<Tag color="blue">只读测试</Tag>}>
|
||||
<Alert type="info" showIcon style={{ marginBottom: 16 }} message="这里展示扩展实际提交、服务端实际保存的数据,不代表已确认的商品编辑字段。" />
|
||||
<Descriptions bordered size="small" column={2} items={[
|
||||
{ key: 'title', label: '商品标题', children: record.title ?? '未命名' },
|
||||
{ key: 'source', label: '采集来源', children: `${record.source.platform_code ?? '手动'} · ${record.source.url ?? '—'}` },
|
||||
{ key: 'status', label: '采集状态', children: record.collection_status },
|
||||
{ key: 'parser', label: '解析器版本', children: record.parser_version ?? '—' },
|
||||
{ key: 'sku', label: '已创建 SKU', children: record.variants.length },
|
||||
{ key: 'assets', label: '素材任务', children: record.asset_transfers.length },
|
||||
]} />
|
||||
<Typography.Title level={5} style={{ marginTop: 24 }}>SKU 草稿</Typography.Title>
|
||||
<Table size="small" pagination={false} rowKey="id" dataSource={record.variants} columns={[
|
||||
{ title: '来源 SKU ID', dataIndex: 'source_variant_key', render: (value) => value ?? '默认 SKU(待确认)' },
|
||||
{ title: '来源货号', dataIndex: 'source_sku', render: (value) => value ?? '—' },
|
||||
{ title: '名称', dataIndex: 'display_name', render: (value) => value ?? '—' },
|
||||
]} />
|
||||
<Typography.Title level={5} style={{ marginTop: 24 }}>素材转存任务</Typography.Title>
|
||||
<Table size="small" pagination={false} rowKey="id" dataSource={record.asset_transfers} columns={[
|
||||
{ title: '类型', dataIndex: 'role' }, { title: '状态', dataIndex: 'status' },
|
||||
{ title: '来源 URL', dataIndex: 'source_url', ellipsis: true, render: (value) => <Typography.Link href={value} target="_blank">查看来源</Typography.Link> },
|
||||
{ title: '错误', dataIndex: 'error_payload', render: (value) => value ? <Typography.Text type="danger">{JSON.stringify(value)}</Typography.Text> : '—' },
|
||||
]} />
|
||||
<Collapse style={{ marginTop: 24 }} items={[
|
||||
{ key: 'normalized', label: '规范化采集载荷(normalized_payload)', children: <Json value={record.normalized_payload} /> },
|
||||
{ key: 'evidence', label: '原始证据(raw_evidence)', children: <Json value={record.raw_evidence} /> },
|
||||
]} />
|
||||
</Card>;
|
||||
}
|
||||
function Json({ value }: { value: unknown }) { return <pre style={{ margin: 0, overflow: 'auto', whiteSpace: 'pre-wrap', wordBreak: 'break-word', fontSize: 12 }}>{JSON.stringify(value, null, 2)}</pre>; }
|
||||
@@ -3,6 +3,7 @@ import { Navigate, useLocation, createBrowserRouter } from 'react-router-dom';
|
||||
import MainLayout from '@/layouts/MainLayout';
|
||||
import SessionSplash from '@/components/SessionSplash';
|
||||
import CollectionPage from '@/pages/collection/CollectionPage';
|
||||
import CollectionInspectionPage from '@/pages/collection/CollectionInspectionPage';
|
||||
import LoginPage from '@/pages/auth/LoginPage';
|
||||
import OnlineProductsPage from '@/pages/online-products/OnlineProductsPage';
|
||||
import PasswordSettingsPage from '@/pages/settings/PasswordSettingsPage';
|
||||
@@ -56,6 +57,7 @@ export const router = createBrowserRouter([
|
||||
{ index: true, element: <Navigate to="/online-products" replace /> },
|
||||
{ path: 'online-products', element: <OnlineProductsPage /> },
|
||||
{ path: 'collection', element: <CollectionPage /> },
|
||||
{ path: 'collection-test/:id', element: <CollectionInspectionPage /> },
|
||||
{ path: 'shops', element: <ShopsPage /> },
|
||||
{ path: 'watermarks', element: <WatermarksPage /> },
|
||||
{ path: 'pricing', element: <PricingPage /> },
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { CollectionItem, CreateCollectionItemRequest, CursorPaginationMeta, ListCollectionItemsQuery, UpdateCollectionItemRequest } from '@atlas/contracts';
|
||||
import type { CollectionItem, CollectionItemInspection, CreateCollectionItemRequest, CursorPaginationMeta, ListCollectionItemsQuery, UpdateCollectionItemRequest } from '@atlas/contracts';
|
||||
import { api } from './api';
|
||||
export const collectionApi = {
|
||||
list: (platformId: string, query: Partial<ListCollectionItemsQuery> = {}) => api.getList<CollectionItem, CursorPaginationMeta & { active_count: number; collection_limit: number }>('/collection-items', { headers: { 'X-Atlas-Platform-Id': platformId }, params: { limit: 20, ...query } }),
|
||||
create: (payload: CreateCollectionItemRequest) => api.post<{ collection_item: CollectionItem; product_id: string; editor_entry_path: string }>('/collection-items', payload, { headers: { 'Idempotency-Key': crypto.randomUUID() } }),
|
||||
inspection: (id: string) => api.get<CollectionItemInspection>(`/collection-items/${id}`),
|
||||
update: (id: string, payload: UpdateCollectionItemRequest, revision: number) => api.patch<CollectionItem>(`/collection-items/${id}`, payload, { headers: { 'Idempotency-Key': crypto.randomUUID(), 'If-Match': `"${revision}"` } }),
|
||||
bulkDelete: (ids: string[]) => api.post<{ deleted_count: number }>('/collection-items/bulk-delete', { ids }, { headers: { 'Idempotency-Key': crypto.randomUUID() } }),
|
||||
clear: (expected_active_count: number) => api.post<{ deleted_count: number }>('/collection-items/clear', { confirm: true, expected_active_count }, { headers: { 'Idempotency-Key': crypto.randomUUID() } }),
|
||||
|
||||
Reference in New Issue
Block a user