feat(edit|extension): 编辑页类目采集开发
This commit is contained in:
@@ -1127,6 +1127,16 @@ const ProductEditPage = () => {
|
||||
categoryPaths={categories.paths}
|
||||
categoryNotFoundContent={categoryNotFoundContent}
|
||||
onCategorySearch={setCategorySearch}
|
||||
onSelectCategoryCandidate={(categoryId) => {
|
||||
form.setFieldsValue({
|
||||
profile: {
|
||||
platform_product_category_id: categoryId,
|
||||
category_source: 'manual',
|
||||
},
|
||||
attributes: [],
|
||||
});
|
||||
setDirty(true);
|
||||
}}
|
||||
usesVariantPackageWeight={usesVariantPackageWeight}
|
||||
usesVariantPackageDimensions={usesVariantPackageDimensions}
|
||||
onUseVariantPackageWeight={useVariantPackageWeight}
|
||||
|
||||
@@ -115,6 +115,29 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 来源页层级仅用于展示和诊断,不表示已选中的可发布类目。 */
|
||||
.collected-category-breadcrumb {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px 0;
|
||||
padding: 9px 10px;
|
||||
border: 1px solid #ece9f1;
|
||||
border-radius: 8px;
|
||||
background: #fbfaff;
|
||||
color: #352f43;
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.collected-category-breadcrumb .ant-breadcrumb-link {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.collected-category-hint.ant-typography {
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 规格 / 参数列表 */
|
||||
.collected-attributes {
|
||||
border: 1px solid #ece9f1;
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Alert, Button, Card, Collapse, Image, Input, Space, Tag, Typography } from 'antd';
|
||||
import {
|
||||
Alert,
|
||||
Breadcrumb,
|
||||
Button,
|
||||
Card,
|
||||
Collapse,
|
||||
Image,
|
||||
Input,
|
||||
Space,
|
||||
Tag,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import type {
|
||||
CollectionItemInspection,
|
||||
ProductAiSourceField,
|
||||
@@ -56,6 +67,8 @@ export function CollectedProductInfoPanel({
|
||||
onAcceptLatest,
|
||||
}: Props) {
|
||||
const sourceDescription = collected?.description ?? '';
|
||||
// 类目自动回填始终基于最新采集快照,不能被 AI 输入素材的用户覆盖影响。
|
||||
const capturedCategories = snapshot.source_material_state.latest.categories;
|
||||
const [descriptionDraft, setDescriptionDraft] = useState(sourceDescription);
|
||||
useEffect(() => setDescriptionDraft(sourceDescription), [sourceDescription]);
|
||||
// 采集图片按角色分三组展示;同组内按 URL 去重(同一张图可能同时出现在
|
||||
@@ -185,6 +198,28 @@ export function CollectedProductInfoPanel({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="collected-field">
|
||||
<span>来源类目面包屑</span>
|
||||
{capturedCategories.length ? (
|
||||
<>
|
||||
<Breadcrumb
|
||||
className="collected-category-breadcrumb"
|
||||
separator="›"
|
||||
items={capturedCategories.map((category, index) => ({
|
||||
key: `${category.key}:${index}`,
|
||||
title: category.name,
|
||||
}))}
|
||||
/>
|
||||
<Typography.Text type="secondary" className="collected-category-hint">
|
||||
自动匹配仅使用一级类目与商品类型;后续层级仅作为来源参考。
|
||||
</Typography.Text>
|
||||
</>
|
||||
) : (
|
||||
<Typography.Text type="secondary">
|
||||
本次采集未取得来源类目。
|
||||
</Typography.Text>
|
||||
)}
|
||||
</div>
|
||||
<div className="collected-field">
|
||||
<span>规格 / 参数</span>
|
||||
{collected?.attributes.length ? (
|
||||
|
||||
@@ -108,7 +108,7 @@ export function ProductAttributeSection({
|
||||
</Form.Item>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<PlusCircleOutlined />}
|
||||
icon={<PlusCircleOutlined style={{ fontSize: 20 }} />}
|
||||
aria-label="按 SKU 分别填写主题标签"
|
||||
onClick={onUseVariantTags}
|
||||
/>
|
||||
@@ -123,7 +123,7 @@ export function ProductAttributeSection({
|
||||
</Form.Item>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<PlusCircleOutlined />}
|
||||
icon={<PlusCircleOutlined style={{ fontSize: 20 }} />}
|
||||
aria-label="按 SKU 分别填写产品描述"
|
||||
onClick={onUseVariantDescription}
|
||||
/>
|
||||
|
||||
@@ -50,6 +50,40 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 多候选只提供确认入口;实际发布类目仍由用户保存后的 UUID 决定。 */
|
||||
.captured-category-candidates {
|
||||
margin: -10px 0 14px 100px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid #d8d0ff;
|
||||
border-radius: 8px;
|
||||
background: #faf9ff;
|
||||
}
|
||||
.captured-category-candidates-title {
|
||||
color: #5e5273;
|
||||
font-size: 12px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.captured-category-candidate-list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-top: 9px;
|
||||
}
|
||||
.captured-category-candidate-list .ant-radio-wrapper {
|
||||
align-items: flex-start;
|
||||
margin-inline-end: 0;
|
||||
color: #352f43;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.captured-category-candidate-path,
|
||||
.captured-category-candidate-id {
|
||||
display: block;
|
||||
}
|
||||
.captured-category-candidate-id {
|
||||
margin-top: 2px;
|
||||
color: #77718a;
|
||||
font-size: 12px;
|
||||
}
|
||||
/* 包装尺寸跟随主要信息卡片的实际宽度,和表单 Label 的换行阈值保持一致。 */
|
||||
@container (max-width: 479px) {
|
||||
.package-dimensions-controls {
|
||||
@@ -59,4 +93,7 @@
|
||||
.package-dimension-separator {
|
||||
display: none;
|
||||
}
|
||||
.captured-category-candidates {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, Card, Cascader, Form, Input, InputNumber, Select, Tooltip } from 'antd';
|
||||
import { Button, Card, Cascader, Form, Input, InputNumber, Radio, Select, Tooltip } from 'antd';
|
||||
import type { FormInstance } from 'antd';
|
||||
import { PlusCircleOutlined } from '@ant-design/icons';
|
||||
import type { ProductEditorSnapshot } from '@atlas/contracts';
|
||||
@@ -42,7 +42,7 @@ function PackageFields({
|
||||
<Tooltip title="按 SKU 分别填写包装重量">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<PlusCircleOutlined />}
|
||||
icon={<PlusCircleOutlined style={{ fontSize: 20 }} />}
|
||||
aria-label="按 SKU 分别填写包装重量"
|
||||
onClick={onUseVariantPackageWeight}
|
||||
/>
|
||||
@@ -68,7 +68,7 @@ function PackageFields({
|
||||
<Tooltip title="按 SKU 分别填写包装尺寸">
|
||||
<Button
|
||||
type="text"
|
||||
icon={<PlusCircleOutlined />}
|
||||
icon={<PlusCircleOutlined style={{ fontSize: 20 }} />}
|
||||
aria-label="按 SKU 分别填写包装尺寸"
|
||||
onClick={onUseVariantPackageDimensions}
|
||||
/>
|
||||
@@ -88,6 +88,7 @@ type Props = {
|
||||
categoryPaths: Map<string, string[]>;
|
||||
categoryNotFoundContent: string;
|
||||
onCategorySearch: (query: string) => void;
|
||||
onSelectCategoryCandidate: (categoryId: string) => void;
|
||||
usesVariantPackageWeight: boolean;
|
||||
usesVariantPackageDimensions: boolean;
|
||||
onUseVariantPackageWeight: () => void;
|
||||
@@ -108,6 +109,7 @@ export function ProductMainInfoSection({
|
||||
categoryPaths,
|
||||
categoryNotFoundContent,
|
||||
onCategorySearch,
|
||||
onSelectCategoryCandidate,
|
||||
usesVariantPackageWeight,
|
||||
usesVariantPackageDimensions,
|
||||
onUseVariantPackageWeight,
|
||||
@@ -135,6 +137,31 @@ export function ProductMainInfoSection({
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
{snapshot.captured_category_match.status === 'ambiguous' &&
|
||||
!selectedCategoryId && (
|
||||
<div className="captured-category-candidates span-full">
|
||||
<div className="captured-category-candidates-title">
|
||||
根据来源一级类目“{snapshot.captured_category_match.root_category_name}”
|
||||
与商品类型“{snapshot.captured_category_match.type_name}”找到多个候选,请确认。
|
||||
</div>
|
||||
<Radio.Group
|
||||
className="captured-category-candidate-list"
|
||||
onChange={(event) => onSelectCategoryCandidate(event.target.value)}
|
||||
>
|
||||
{snapshot.captured_category_match.candidates.map((candidate) => (
|
||||
<Radio key={candidate.id} value={candidate.id}>
|
||||
<span className="captured-category-candidate-path">
|
||||
{candidate.path}
|
||||
</span>
|
||||
<span className="captured-category-candidate-id">
|
||||
类目 ID:{candidate.external_category_id} · 类型 ID:
|
||||
{candidate.external_type_id}
|
||||
</span>
|
||||
</Radio>
|
||||
))}
|
||||
</Radio.Group>
|
||||
</div>
|
||||
)}
|
||||
<Form.Item
|
||||
name={['profile', 'platform_product_category_id']}
|
||||
label="产品类目"
|
||||
|
||||
Reference in New Issue
Block a user