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 -10
View File
@@ -14,17 +14,8 @@ function App() {
colorPrimary: '#8b5cf6',
// 全局圆角 10px,输入框/按钮/卡片等同步生效
borderRadius: 10,
// 全局正文字号 14px;输入类控件经 components 单独放大
// 全局正文字号沿用 Ant Design 默认值
fontSize: 14,
controlHeight: 34,
controlHeightSM: 26,
},
components: {
// 文本框:文本 16px、高度同步加高;按钮与表格/菜单等保持全局默认
Input: { fontSize: 16, controlHeight: 40 },
Select: { fontSize: 16, controlHeight: 40 },
InputNumber: { fontSize: 16, controlHeight: 40 },
DatePicker: { fontSize: 16, controlHeight: 40 },
},
}}
>
@@ -0,0 +1,758 @@
.product-editor {
max-width: 1500px;
margin: 0 auto;
padding-bottom: 92px;
}
.editor-header {
margin: -4px 0 20px;
}
.editor-back {
margin-left: -8px;
color: #667085;
}
.editor-header-main {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
margin-top: 2px;
}
.editor-header h2.ant-typography {
margin: 0;
font-size: 24px;
letter-spacing: -0.02em;
}
.editor-eyebrow {
color: #7c3aed;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.editor-header-context {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
white-space: nowrap;
}
.editor-workspace {
display: grid;
grid-template-columns: minmax(0, 1fr) 410px;
align-items: start;
gap: 20px;
margin-top: 16px;
}
.editor-main {
display: flex;
min-width: 0;
flex-direction: column;
gap: 16px;
}
.editor-section.ant-card {
border: 1px solid #e9e7ef;
border-radius: 14px;
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.025);
}
.editor-section .ant-card-body {
padding: 25px 28px;
}
.editor-section-heading {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 20px;
margin-bottom: 22px;
}
.editor-section-heading h4.ant-typography {
margin: 2px 0 3px;
font-size: 20px;
}
.editor-section-number {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 25px;
height: 25px;
margin-right: 9px;
border-radius: 7px;
background: #ede9fe;
color: #7c3aed;
font-size: 13px;
font-weight: 800;
vertical-align: 2px;
}
.attribute-item span,
.field-label {
display: block;
margin-bottom: 4px;
color: #77718a;
font-size: 12px;
}
.editor-form-grid {
display: grid;
grid-template-columns: 1fr;
gap: 0;
}
.editor-form-grid .span-full {
grid-column: 1 / -1;
}
.editor-form-grid .span-two {
grid-column: span 2;
}
.editor-section .ant-form-item-label > label {
color: #344054;
font-weight: 600;
}
.product-editor .ant-form-item-label > label {
font-size: 16px;
}
.product-editor .ant-input,
.product-editor .ant-select-selection-item,
.product-editor .ant-select-selection-placeholder {
font-size: 16px;
}
.product-editor .ant-input:not(textarea) {
min-height: 40px;
}
.product-editor .ant-input-group-addon {
font-size: 16px;
}
.product-editor .ant-select .ant-select-selector {
min-height: 40px !important;
font-size: 16px;
}
.product-editor .ant-select-single .ant-select-selector {
height: 40px !important;
}
.product-editor .ant-select-single .ant-select-selection-item,
.product-editor .ant-select-single .ant-select-selection-placeholder {
line-height: 38px !important;
}
.product-editor .ant-select-multiple .ant-select-selector {
padding-top: 3px;
padding-bottom: 3px;
}
.package-fields {
margin-top: 22px;
padding-top: 20px;
border-top: 1px solid #f0eef4;
}
.package-fields > .ant-form-item:first-child {
width: 150px;
max-width: 100%;
}
.package-dimensions {
margin-bottom: 0;
}
.package-dimensions-controls {
display: grid;
grid-template-columns: 120px 22px 120px 22px 120px auto;
align-items: center;
gap: 7px;
width: max-content;
}
.package-dimension-separator {
color: #98a2b3;
font-size: 23px;
font-weight: 600;
text-align: center;
}
.package-dimension-unit {
color: #667085;
font-size: 16px;
line-height: 40px;
}
.category-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 14px 16px;
border: 1px solid #eae7f0;
border-radius: 10px;
background: #fcfbfd;
}
.category-row strong {
margin-right: 8px;
}
.attribute-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
margin-top: 14px;
}
.attribute-item {
min-width: 0;
padding: 11px 13px;
border: 1px solid #eeeaf3;
border-radius: 8px;
}
.attribute-item strong {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.editor-standard-select {
min-height: 40px !important;
font-size: 16px !important;
}
.editor-standard-select .ant-select-content {
min-height: 38px;
font-size: 16px !important;
}
.editor-standard-select .ant-select-content-has-value,
.editor-standard-select .ant-select-placeholder,
.editor-standard-select .ant-select-input {
font-size: 16px !important;
}
.category-select-footer {
display: flex;
justify-content: flex-end;
padding: 4px 8px 6px;
border-top: 1px solid var(--ant-color-border-secondary, #f0f0f0);
}
.editor-standard-select.ant-select-single {
height: 40px !important;
}
.editor-standard-select.ant-select-single .ant-select-content {
height: 38px;
line-height: 38px;
}
.editor-standard-select.ant-select-single .ant-select-input {
height: 38px;
line-height: 38px !important;
}
.editor-standard-select.ant-select-multiple .ant-select-content {
min-height: 40px !important;
padding: 3px 0;
}
.editor-standard-select.ant-select-multiple .ant-select-content-item {
min-height: 32px;
display: flex;
align-items: center;
}
.editor-standard-select.ant-select-multiple .ant-select-placeholder,
.editor-standard-select.ant-select-multiple .ant-select-input {
font-size: 16px !important;
}
.editor-standard-select.ant-select-multiple .ant-select-placeholder,
.editor-standard-select.ant-select-multiple .ant-select-input {
line-height: 32px !important;
}
.dynamic-attributes {
margin-top: 24px;
padding-top: 22px;
border-top: 1px solid #eeeaf4;
}
.dynamic-attributes h5.ant-typography {
margin: 0 0 14px;
font-size: 16px;
}
.dynamic-attribute-list {
display: grid;
grid-template-columns: 1fr;
}
.dynamic-attribute-item .ant-space-compact {
width: 100%;
}
.dynamic-attribute-item .ant-btn-dangerous.ant-btn-text {
min-width: 56px;
}
.copy-subsection {
margin-top: 24px;
padding-top: 24px;
border-top: 1px solid #eeeaf4;
}
.copy-subsection .editor-section-heading {
margin-bottom: 16px;
}
.copy-store-row {
display: grid;
grid-template-columns: minmax(240px, 1fr) 170px;
gap: 16px;
}
.sku-section .ant-input {
border-radius: 8px;
font-size: 14px;
}
.sku-section textarea.ant-input {
min-height: 58px;
resize: none;
}
.sku-section .ant-input-group-addon {
border-radius: 0 8px 8px 0;
font-size: 13px;
}
.sku-section .ant-input-group .ant-input {
border-radius: 8px 0 0 8px;
}
.sku-section .ant-input:not(textarea) {
height: 32px;
min-height: 32px;
}
.sku-card-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.sku-card.ant-card {
border: 1px solid #e8e4ee;
border-radius: 12px;
box-shadow: none;
}
.sku-card .ant-card-body {
padding: 16px 18px;
}
.sku-card-top {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 28px;
}
.sku-card-index {
display: inline-flex;
width: 28px;
height: 28px;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #eee9fe;
color: #7151df;
font-weight: 700;
}
.sku-card-summary {
display: grid;
grid-template-columns: minmax(210px, 1.55fr) 58px minmax(132px, 1fr) 104px 112px 82px auto;
gap: 14px;
align-items: start;
margin-top: 12px;
}
.sku-card-summary .ant-form-item {
margin: 0;
}
.sku-card-field,
.sku-card-value {
min-width: 0;
}
.sku-main-thumbnail {
display: flex;
width: 42px;
aspect-ratio: 3 / 4;
align-items: center;
justify-content: center;
overflow: hidden;
border: 1px solid #ece8f0;
border-radius: 8px;
background: #f4f2f7;
color: #90889c;
font-size: 10px;
text-align: center;
}
.sku-main-thumbnail .ant-image,
.sku-main-thumbnail img {
width: 100%;
height: 100%;
}
.sku-main-thumbnail img {
object-fit: cover;
}
.sku-image-field {
justify-self: center;
}
.sku-card-field > span,
.sku-card-value > span {
display: block;
margin-bottom: 4px;
color: #77718a;
font-size: 12px;
}
.sku-card-value strong {
display: block;
overflow: hidden;
color: #40394f;
font-size: 14px;
text-overflow: ellipsis;
white-space: nowrap;
}
.sku-card-value strong {
min-height: 32px;
padding-top: 5px;
line-height: 22px;
}
.sku-expand-button {
align-self: start;
justify-self: end;
margin-top: 23px;
}
.sku-card-expanded {
display: grid;
grid-template-columns: minmax(0, 1.08fr) minmax(390px, 0.92fr);
gap: 32px;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #f0edf3;
}
.sku-media-panel h5.ant-typography,
.sku-spec-panel h5.ant-typography {
margin: 0 0 12px;
}
.sku-media-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
align-items: start;
}
.sku-media-secondary {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.sku-media-item {
min-width: 0;
aspect-ratio: 3 / 4;
overflow: hidden;
border: 1px solid #ece8f0;
border-radius: 8px;
background: #f5f3f7;
}
.sku-media-item .ant-image,
.sku-media-item img {
width: 100%;
height: 100%;
}
.sku-media-item img {
object-fit: contain;
}
.sku-media-empty {
display: flex;
grid-column: 1 / -1;
min-height: 190px;
align-items: center;
justify-content: center;
border: 1px dashed #d9d4df;
border-radius: 8px;
color: #90889c;
font-size: 13px;
}
.sku-spec-panel {
padding: 18px 18px 6px;
border: 1px solid #eeeaf3;
border-radius: 10px;
background: #fcfbfd;
}
.sku-spec-panel .ant-form-item {
margin-bottom: 16px;
}
.sku-spec-panel .ant-form-item-label {
flex: 0 0 86px !important;
}
.sku-spec-panel .ant-form-item-label > label {
height: 32px;
font-size: 14px;
font-weight: 600;
}
.sku-spec-panel .ant-form-item-control {
min-width: 0;
}
.sku-spec-panel > .ant-form-item:not(:has(.sku-inline-dimensions)) .ant-input-group-wrapper {
max-width: 220px;
}
.sku-inline-dimensions {
display: grid;
grid-template-columns: minmax(54px, 1fr) 16px minmax(54px, 1fr) 16px minmax(54px, 1fr) 28px;
gap: 6px;
align-items: center;
}
.sku-dimension-separator {
color: #98a2b3;
font-size: 16px;
font-weight: 600;
text-align: center;
}
.sku-dimension-unit {
color: #77718a;
font-size: 13px;
text-align: right;
}
.add-sku-row {
display: flex;
justify-content: center;
padding-top: 4px;
}
.add-sku-button {
min-width: 180px;
}
.editor-aside {
display: flex;
flex-direction: column;
gap: 16px;
}
.collected-info-panel.ant-card {
border: 1px solid #e9e7ef;
border-radius: 14px;
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.025);
}
.collected-info-panel .ant-card-body {
min-height: 820px;
padding: 20px;
}
.collected-info-panel h4.ant-typography {
margin: 0;
}
.collected-info-heading {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-bottom: 17px;
}
.collected-info-heading .ant-typography-secondary {
font-size: 12px;
}
.source-update-alert {
margin-bottom: 16px;
}
.source-update-alert .ant-alert-description {
font-size: 12px;
}
.collected-info-panel .ant-input {
font-size: 14px;
}
.collected-info-panel .ant-input:not(textarea) {
height: 32px;
min-height: 32px;
}
.collected-field {
margin-top: 16px;
}
.collected-field:first-of-type {
margin-top: 0;
}
.collected-field > span {
display: block;
margin-bottom: 5px;
color: #77718a;
font-size: 12px;
}
.collected-field-label {
display: flex;
min-height: 24px;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 5px;
color: #77718a;
font-size: 12px;
}
.collected-field-label .ant-btn {
height: 22px;
padding: 0;
font-size: 12px;
}
.source-diff-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.source-diff-list strong,
.source-diff-list span {
display: block;
}
.source-diff-list span {
margin-top: 2px;
color: #667085;
font-size: 13px;
}
.collected-field > strong {
display: block;
color: #352f43;
line-height: 1.55;
}
.collected-field .ant-typography {
margin: 0;
white-space: pre-wrap;
color: #50485d;
font-size: 13px;
line-height: 1.65;
}
.collected-summary {
display: grid;
grid-template-columns: 1fr 1fr;
overflow: hidden;
border: 1px solid #ece9f1;
border-radius: 8px;
}
.collected-summary > div {
min-width: 0;
padding: 9px 10px;
border-right: 1px solid #f0edf3;
border-bottom: 1px solid #f0edf3;
}
.collected-summary > div:nth-child(2n) {
border-right: 0;
}
.collected-summary > div:nth-last-child(-n + 2) {
border-bottom: 0;
}
.collected-summary b {
display: block;
margin-bottom: 3px;
color: #77718a;
font-size: 12px;
font-weight: 500;
}
.collected-summary strong {
display: block;
overflow: hidden;
color: #352f43;
font-size: 13px;
text-overflow: ellipsis;
white-space: nowrap;
}
.collected-attributes {
border: 1px solid #ece9f1;
border-radius: 8px;
}
.collected-attributes > div {
display: grid;
grid-template-columns: minmax(100px, 0.9fr) minmax(0, 1.3fr);
gap: 12px;
padding: 9px 10px;
border-bottom: 1px solid #f0edf3;
font-size: 12px;
line-height: 1.45;
}
.collected-attributes > div:last-child {
border-bottom: 0;
}
.collected-attributes b {
color: #6b6376;
font-weight: 600;
}
.collected-attributes span {
color: #352f43;
word-break: break-word;
}
.other-section .editor-section-heading {
margin-bottom: 8px;
}
.editor-footer {
position: sticky;
bottom: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-top: 16px;
padding: 13px 16px;
border: 1px solid #e4e0eb;
border-radius: 12px;
background: rgba(255, 255, 255, 0.96);
box-shadow: 0 -8px 24px rgba(38, 27, 56, 0.08);
backdrop-filter: blur(8px);
}
.footer-ai-actions {
display: flex;
}
@media (max-width: 1180px) {
.editor-workspace {
grid-template-columns: 1fr;
}
.editor-aside {
position: static;
}
.collected-info-panel {
max-width: 100%;
}
.collected-info-panel .ant-card-body {
min-height: 0;
}
}
@media (max-width: 860px) {
.editor-header-main,
.editor-section-heading,
.editor-footer {
align-items: flex-start;
flex-direction: column;
}
.editor-form-grid {
grid-template-columns: 1fr;
}
.editor-form-grid .span-two {
grid-column: span 1;
}
.package-fields > .ant-form-item:first-child,
.package-dimensions-controls {
width: 100%;
}
.package-dimensions-controls {
grid-template-columns: 1fr;
}
.package-dimension-separator {
display: none;
}
.copy-store-row {
grid-template-columns: 1fr;
}
.attribute-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.sku-card-summary {
grid-template-columns: minmax(150px, 1fr) 54px repeat(2, minmax(115px, 1fr)) auto;
}
.sku-card-value {
display: none;
}
.sku-card-expanded {
grid-template-columns: 1fr;
}
}
@media (max-width: 560px) {
.editor-section .ant-card-body {
padding: 20px 16px;
}
.category-row,
.editor-footer {
align-items: stretch;
}
.category-row {
flex-direction: column;
}
.attribute-grid {
grid-template-columns: 1fr;
}
.sku-card-summary {
grid-template-columns: minmax(0, 1fr) 48px auto;
gap: 8px;
}
.sku-card-field {
display: none;
}
.sku-main-thumbnail {
width: 48px;
height: 48px;
}
.sku-media-grid {
grid-auto-rows: 72px;
}
.sku-inline-dimensions {
grid-template-columns: 1fr;
}
.sku-inline-dimensions > span {
display: none;
}
.editor-footer .ant-space {
display: flex;
width: 100%;
}
.editor-footer .ant-space-item {
flex: 1;
}
.editor-footer .ant-btn {
width: 100%;
}
}
File diff suppressed because it is too large Load Diff
+98 -11
View File
@@ -1,6 +1,10 @@
import type {
ProductAttributeDictionaryValue,
ProductAttributeSchema,
ProductEditorCategoryOption,
ProductEditorContext,
ProductEditorSnapshot,
Operation,
SaveProductEditorDraftRequest,
SaveProductEditorDraftResponse,
} from '@atlas/contracts';
@@ -8,9 +12,16 @@ import { api } from './api';
export const productEditorApi = {
enter: (collectionItemId: string, platformId: string) =>
api.post<ProductEditorContext>(`/collection-items/${collectionItemId}/editor-contexts`, undefined, {
headers: { 'X-Atlas-Platform-Id': platformId, 'Idempotency-Key': crypto.randomUUID() },
}),
api.post<ProductEditorContext>(
`/collection-items/${collectionItemId}/editor-contexts`,
undefined,
{
headers: {
'X-Atlas-Platform-Id': platformId,
'Idempotency-Key': crypto.randomUUID(),
},
}
),
read: (productId: string, platformId: string) =>
api.get<ProductEditorSnapshot>(`/products/${productId}/editor`, {
headers: { 'X-Atlas-Platform-Id': platformId },
@@ -19,12 +30,88 @@ export const productEditorApi = {
productId: string,
platformId: string,
revisions: { product: number; profile: number },
payload: SaveProductEditorDraftRequest,
) => api.put<SaveProductEditorDraftResponse>(`/products/${productId}/editor-draft`, payload, {
headers: {
'X-Atlas-Platform-Id': platformId,
'Idempotency-Key': crypto.randomUUID(),
'If-Match': `"${revisions.product}:${revisions.profile}"`,
},
}),
payload: SaveProductEditorDraftRequest
) =>
api.put<SaveProductEditorDraftResponse>(`/products/${productId}/editor-draft`, payload, {
headers: {
'X-Atlas-Platform-Id': platformId,
'Idempotency-Key': crypto.randomUUID(),
'If-Match': `"${revisions.product}:${revisions.profile}"`,
},
}),
categoryOptions: (profileId: string, platformId: string, search?: string) =>
api.get<ProductEditorCategoryOption[]>(
`/product-platform-profiles/${profileId}/category-options`,
{
params: search ? { q: search } : undefined,
headers: { 'X-Atlas-Platform-Id': platformId },
}
),
syncCategoryTree: (profileId: string, platformId: string) =>
api.post<{ operation: Operation }>(
`/product-platform-profiles/${profileId}/category-syncs`,
undefined,
{
headers: {
'X-Atlas-Platform-Id': platformId,
'Idempotency-Key': crypto.randomUUID(),
},
}
),
attributeSchema: (profileId: string, platformId: string, categoryId?: string | null) =>
api.get<ProductAttributeSchema[]>(
`/product-platform-profiles/${profileId}/attribute-schema`,
{
params: categoryId ? { category_id: categoryId } : undefined,
headers: { 'X-Atlas-Platform-Id': platformId },
}
),
syncAttributeSchema: (profileId: string, platformId: string, categoryId: string) =>
api.post<{ operation: Operation }>(
`/product-platform-profiles/${profileId}/attribute-schema-syncs`,
undefined,
{
params: { category_id: categoryId },
headers: {
'X-Atlas-Platform-Id': platformId,
'Idempotency-Key': crypto.randomUUID(),
},
}
),
attributeDictionary: (
profileId: string,
platformId: string,
dictionaryId: string,
search?: string,
categoryId?: string | null
) =>
api.get<{ items: ProductAttributeDictionaryValue[]; next_cursor: string | null }>(
`/product-platform-profiles/${profileId}/attribute-dictionaries/${encodeURIComponent(dictionaryId)}`,
{
params: {
...(categoryId ? { category_id: categoryId } : {}),
...(search ? { q: search } : {}),
},
headers: { 'X-Atlas-Platform-Id': platformId },
}
),
syncAttributeDictionary: (
profileId: string,
platformId: string,
dictionaryId: string,
categoryId: string,
query: string
) =>
api.post<{ operation: Operation }>(
`/product-platform-profiles/${profileId}/attribute-dictionaries/${encodeURIComponent(dictionaryId)}/syncs`,
undefined,
{
params: { category_id: categoryId, q: query },
headers: {
'X-Atlas-Platform-Id': platformId,
'Idempotency-Key': crypto.randomUUID(),
},
}
),
operation: (operationId: string) => api.get<Operation>(`/operations/${operationId}`),
};