diff --git a/frontend/src/pages/DevelopmentStylePreview/stylePreview.css b/frontend/src/pages/DevelopmentStylePreview/stylePreview.css
index 1bc882c..95b16f4 100644
--- a/frontend/src/pages/DevelopmentStylePreview/stylePreview.css
+++ b/frontend/src/pages/DevelopmentStylePreview/stylePreview.css
@@ -33,8 +33,7 @@
.preview-insight-card button,
.preview-table-panel button,
.preview-reason-table button,
-.preview-competitor-list button,
-.preview-diagnosis-issue button {
+.preview-competitor-list button {
height: var(--sycm-control-height-sm);
border: var(--sycm-line-width-base) solid var(--sycm-border-base);
border-radius: var(--sycm-radius-md);
@@ -53,7 +52,6 @@
.preview-table-panel button:hover,
.preview-reason-table button:hover,
.preview-competitor-list button:hover,
-.preview-diagnosis-issue button:hover,
.preview-suggestion-card button:hover {
border-color: var(--sycm-color-primary);
background: var(--sycm-bg-control-hover);
@@ -70,8 +68,7 @@
.preview-card__header p,
.preview-growth-card__row,
.preview-insight-card p,
-.preview-suggestion-card p,
-.preview-diagnosis-issue p {
+.preview-suggestion-card p {
margin: var(--sycm-space-xxs) 0 0;
color: var(--sycm-text-tertiary);
font-size: var(--sycm-font-size-caption);
@@ -155,8 +152,7 @@
.preview-card__header h2,
.preview-table-card__title h3,
.preview-insight-card h3,
-.preview-suggestion-card h3,
-.preview-diagnosis-issue h3 {
+.preview-suggestion-card h3 {
margin: 0;
color: var(--sycm-text-strong);
font-size: var(--sycm-font-size-title);
@@ -487,100 +483,6 @@
color: var(--sycm-text-secondary);
}
-.preview-health-risk-loading {
- padding: var(--sycm-space-md);
-}
-
-.preview-health-risk-list {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- padding: var(--sycm-space-sm);
-}
-
-.preview-health-risk-list > div {
- display: grid;
- min-width: 0;
- min-height: 64px;
- grid-template-columns: minmax(0, 1fr) auto;
- align-content: center;
- gap: 4px 10px;
- padding: 10px 12px;
- border-right: var(--sycm-line-width-base) solid var(--sycm-border-light);
- border-bottom: var(--sycm-line-width-base) solid var(--sycm-border-light);
-}
-
-.preview-health-risk-list > div:nth-child(even) {
- border-right: 0;
-}
-
-.preview-health-risk-list > div:nth-last-child(-n + 2) {
- border-bottom: 0;
-}
-
-.preview-health-risk-list span {
- overflow: hidden;
- color: var(--sycm-text-secondary);
- font-size: var(--sycm-font-size-caption);
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.preview-health-risk-list strong {
- color: var(--sycm-text-strong);
- font-family: var(--sycm-font-family-number);
- font-size: 18px;
-}
-
-.preview-health-risk-list strong.is-error {
- color: var(--sycm-color-danger);
-}
-
-.preview-health-risk-list strong.is-warning {
- color: var(--sycm-color-warning);
-}
-
-.preview-health-risk-list em {
- grid-column: 1 / -1;
- color: var(--sycm-text-tertiary);
- font-size: 11px;
- font-style: normal;
-}
-
-.preview-diagnosis-summary {
- display: flex;
- flex-wrap: wrap;
- gap: var(--sycm-space-xs);
- padding: var(--sycm-space-sm);
-}
-
-.preview-diagnosis-summary span {
- border-radius: var(--sycm-radius-sm);
- padding: 5px 8px;
- background: var(--sycm-bg-subtle);
- color: var(--sycm-text-secondary);
- font-size: var(--sycm-font-size-caption);
-}
-
-.preview-diagnosis-issue {
- margin: 0 var(--sycm-space-sm) var(--sycm-space-sm);
- border: var(--sycm-line-width-base) solid var(--sycm-color-warning-bg);
- border-radius: var(--sycm-radius-md);
- padding: 10px 12px;
- background: #fffaf0;
-}
-
-.preview-diagnosis-issue > span {
- color: var(--sycm-color-warning);
- font-size: var(--sycm-font-size-caption);
- font-weight: var(--sycm-font-weight-medium);
-}
-
-.preview-diagnosis-issue button {
- margin-top: var(--sycm-space-sm);
- border-color: var(--sycm-color-warning);
- color: var(--sycm-color-warning);
-}
-
.preview-journey {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
@@ -733,7 +635,7 @@
.preview-hero-grid {
position: relative;
display: grid;
- grid-template-columns: minmax(0, 1fr) 362px;
+ grid-template-columns: minmax(0, 1fr);
gap: 18px;
align-items: stretch;
margin-bottom: 20px;
@@ -743,8 +645,7 @@
content: none;
}
-.preview-overview-panel,
-.preview-side-column {
+.preview-overview-panel {
position: relative;
z-index: 1;
}
@@ -1228,14 +1129,6 @@
text-anchor: start;
}
-.preview-side-column {
- display: grid;
- min-width: 0;
- align-self: stretch;
- grid-template-rows: minmax(0, 1fr);
- gap: 40px;
-}
-
.preview-store-card {
border: 0;
border-radius: var(--sycm-radius-xl);
@@ -1403,750 +1296,6 @@
font-style: normal;
}
-.preview-store-health-card {
- display: flex;
- min-width: 0;
- min-height: 491px;
- height: 100%;
- height: 100%;
- flex-direction: column;
- gap: 12px;
- border-radius: var(--sycm-radius-xl);
- padding: 20px;
- background: var(--sycm-bg-container);
- box-shadow: var(--sycm-shadow-card);
-}
-
-.preview-store-health-card__header {
- display: flex;
- min-height: 32px;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
-}
-
-.preview-store-health-card__header > div {
- display: flex;
- min-width: 0;
- flex-direction: column;
- gap: 2px;
-}
-
-.preview-store-health-card__header h2 {
- margin: 0;
- color: var(--sycm-text-strong);
- font-size: var(--sycm-font-size-section);
- font-weight: var(--sycm-font-weight-semibold);
- line-height: 1.3;
-}
-
-.preview-store-health-card__header span,
-.preview-store-health-card__section-title span,
-.preview-store-health-card__section-title em {
- color: var(--sycm-text-tertiary);
- font-size: var(--sycm-font-size-caption);
- font-style: normal;
-}
-
-.preview-store-health-card__header button {
- display: inline-flex;
- height: 30px;
- flex: 0 0 auto;
- align-items: center;
- gap: 5px;
- border: 0;
- padding: 0;
- background: transparent;
- color: var(--sycm-color-primary);
- font-size: var(--sycm-font-size-caption);
-}
-
-.preview-store-health-card__header button:hover {
- color: var(--sycm-color-primary-hover);
-}
-
-.preview-store-health-card__headline {
- display: grid;
- grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr);
- gap: 16px;
- padding: 14px 0;
- border-top: var(--sycm-line-width-base) solid var(--sycm-border-light);
- border-bottom: var(--sycm-line-width-base) solid var(--sycm-border-light);
-}
-
-.preview-store-health-card__star,
-.preview-store-health-card__coverage {
- display: flex;
- min-width: 0;
- flex-direction: column;
- justify-content: center;
- gap: 5px;
-}
-
-.preview-store-health-card__star > span,
-.preview-store-health-card__coverage > span {
- color: var(--sycm-text-tertiary);
- font-size: var(--sycm-font-size-caption);
-}
-
-.preview-store-health-card__star > div {
- display: flex;
- min-width: 0;
- align-items: baseline;
- gap: 4px;
-}
-
-.preview-store-health-card__star strong,
-.preview-store-health-card__coverage strong {
- color: var(--sycm-text-strong);
- font-family: var(--sycm-font-family-number);
- font-size: 26px;
- font-weight: var(--sycm-font-weight-semibold);
- line-height: 1;
-}
-
-.preview-store-health-card__star small,
-.preview-store-health-card__coverage small {
- color: var(--sycm-text-secondary);
- font-size: var(--sycm-font-size-caption);
-}
-
-.preview-store-health-card__star em {
- margin-left: 3px;
- font-size: var(--sycm-font-size-caption);
- font-style: normal;
-}
-
-.preview-store-health-card__star em.is-up {
- color: var(--sycm-color-success);
-}
-
-.preview-store-health-card__star em.is-down {
- color: var(--sycm-color-danger);
-}
-
-.preview-store-health-card__star em.is-flat {
- color: var(--sycm-text-tertiary);
-}
-
-.preview-store-health-card__coverage {
- display: grid;
- grid-template-columns: auto 1fr;
- align-content: center;
- column-gap: 4px;
-}
-
-.preview-store-health-card__coverage > span,
-.preview-store-health-card__coverage > em {
- grid-column: 1 / -1;
-}
-
-.preview-store-health-card__coverage > em {
- color: var(--sycm-text-tertiary);
- font-size: 11px;
- font-style: normal;
-}
-
-.preview-store-health-card__section-title {
- display: flex;
- min-height: 20px;
- align-items: center;
- justify-content: space-between;
- gap: 10px;
-}
-
-.preview-store-health-card__section-title span {
- color: var(--sycm-text-secondary);
- font-weight: var(--sycm-font-weight-medium);
-}
-
-.preview-store-health-card__section-title em.is-attention {
- color: var(--sycm-color-danger);
-}
-
-.preview-store-health-card__section-title em.is-healthy {
- color: var(--sycm-color-success);
-}
-
-.preview-store-health-card__risk-grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- overflow: hidden;
- border: var(--sycm-line-width-base) solid var(--sycm-border-light);
- border-radius: var(--sycm-radius-md);
-}
-
-.preview-store-health-card__risk-grid > div {
- display: flex;
- min-width: 0;
- min-height: 55px;
- flex-direction: column;
- justify-content: center;
- gap: 4px;
- padding: 9px 12px;
- background: var(--sycm-bg-container);
-}
-
-.preview-store-health-card__risk-grid > div:nth-child(odd) {
- border-right: var(--sycm-line-width-base) solid var(--sycm-border-light);
-}
-
-.preview-store-health-card__risk-grid > div:nth-child(-n + 2) {
- border-bottom: var(--sycm-line-width-base) solid var(--sycm-border-light);
-}
-
-.preview-store-health-card__risk-grid span,
-.preview-store-health-card__score-grid span {
- overflow: hidden;
- color: var(--sycm-text-tertiary);
- font-size: 11px;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.preview-store-health-card__risk-grid strong,
-.preview-store-health-card__score-grid strong {
- color: var(--sycm-text-strong);
- font-family: var(--sycm-font-family-number);
- font-size: var(--sycm-font-size-body);
- font-weight: var(--sycm-font-weight-semibold);
-}
-
-.preview-store-health-card__risk-grid .is-error strong {
- color: var(--sycm-color-danger);
-}
-
-.preview-store-health-card__risk-grid .is-warning strong {
- color: var(--sycm-color-warning);
-}
-
-.preview-store-health-card__score-grid {
- display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
- gap: 0;
- padding: 10px 0;
- border-top: var(--sycm-line-width-base) solid var(--sycm-border-light);
- border-bottom: var(--sycm-line-width-base) solid var(--sycm-border-light);
-}
-
-.preview-store-health-card__score-grid > div {
- display: flex;
- min-width: 0;
- flex-direction: column;
- gap: 4px;
- padding: 0 8px;
- border-right: var(--sycm-line-width-base) solid var(--sycm-border-light);
-}
-
-.preview-store-health-card__score-grid > div:first-child {
- padding-left: 0;
-}
-
-.preview-store-health-card__score-grid > div:last-child {
- border-right: 0;
- padding-right: 0;
-}
-
-.preview-store-health-card__error {
- display: flex;
- min-height: 180px;
- flex: 1;
- align-items: center;
- justify-content: center;
- gap: 8px;
- color: var(--sycm-color-danger);
- font-size: var(--sycm-font-size-caption);
-}
-
-.preview-review-section {
- margin-bottom: 18px;
- border-radius: var(--sycm-radius-lg);
- background: var(--sycm-bg-container);
- box-shadow: var(--sycm-shadow-card);
- padding: 18px 20px 20px;
-}
-
-.preview-review-section__header {
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- gap: 20px;
- margin-bottom: 18px;
-}
-
-.preview-review-section__header h2,
-.preview-review-subtitle h3 {
- margin: 0;
- color: var(--sycm-text-strong);
- font-weight: var(--sycm-font-weight-semibold);
-}
-
-.preview-review-section__header h2 {
- font-size: var(--sycm-font-size-section);
-}
-
-.preview-review-section__header p {
- margin: 4px 0 0;
- color: var(--sycm-text-tertiary);
- font-size: var(--sycm-font-size-caption);
-}
-
-.preview-review-section__header > span {
- color: var(--sycm-chart-axis-text);
- font-size: var(--sycm-font-size-caption);
- white-space: nowrap;
-}
-
-.preview-review-section__body {
- display: grid;
- grid-template-columns: minmax(310px, 0.85fr) minmax(410px, 1.2fr) minmax(340px, 1fr);
- min-height: 218px;
-}
-
-.preview-review-kpis {
- display: grid;
- grid-template-columns: minmax(132px, 0.9fr) minmax(170px, 1.1fr);
- align-items: stretch;
- padding-right: 24px;
-}
-
-.preview-review-kpis__primary {
- display: flex;
- flex-direction: column;
- justify-content: center;
- border-right: 1px solid var(--sycm-border-light);
- padding-right: 20px;
-}
-
-.preview-review-kpis__primary > span,
-.preview-review-kpis__grid span {
- color: var(--sycm-text-secondary);
- font-size: var(--sycm-font-size-caption);
-}
-
-.preview-review-kpis__primary strong {
- margin: 10px 0 8px;
- color: var(--sycm-color-primary);
- font-family: var(--sycm-font-family-number);
- font-size: 38px;
- font-weight: var(--sycm-font-weight-semibold);
- line-height: 1;
-}
-
-.preview-review-kpis__primary strong small {
- margin-left: 2px;
- font-size: 18px;
-}
-
-.preview-review-kpis__primary em {
- color: var(--sycm-text-tertiary);
- font-size: 11px;
- font-style: normal;
- line-height: 1.5;
-}
-
-.preview-review-kpis__grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
-}
-
-.preview-review-kpis__grid > div {
- display: flex;
- min-width: 0;
- flex-direction: column;
- justify-content: center;
- gap: 6px;
- padding-left: 20px;
-}
-
-.preview-review-kpis__grid > div:nth-child(-n + 2) {
- border-bottom: 1px solid var(--sycm-border-light);
-}
-
-.preview-review-kpis__grid strong {
- color: var(--sycm-text-strong);
- font-family: var(--sycm-font-family-number);
- font-size: 20px;
- font-weight: var(--sycm-font-weight-semibold);
-}
-
-.preview-review-kpis__grid .is-risk strong {
- color: var(--sycm-color-danger);
-}
-
-.preview-review-distribution {
- border-right: 1px solid var(--sycm-border-light);
- border-left: 1px solid var(--sycm-border-light);
- padding: 0 24px;
-}
-
-.preview-review-keywords {
- min-width: 0;
- padding-left: 24px;
-}
-
-.preview-review-subtitle {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- margin-bottom: 14px;
-}
-
-.preview-review-subtitle h3 {
- font-size: var(--sycm-font-size-body);
-}
-
-.preview-review-subtitle span {
- color: var(--sycm-text-tertiary);
- font-size: 11px;
- white-space: nowrap;
-}
-
-.preview-review-stars,
-.preview-review-keyword-list {
- display: flex;
- flex-direction: column;
- gap: 12px;
-}
-
-.preview-review-stars > div {
- display: grid;
- grid-template-columns: 38px minmax(80px, 1fr) 58px 42px;
- align-items: center;
- gap: 10px;
- font-size: 12px;
-}
-
-.preview-review-stars > div > span,
-.preview-review-stars > div > em {
- color: var(--sycm-text-secondary);
- font-style: normal;
-}
-
-.preview-review-stars > div > div,
-.preview-review-keyword-list > div > div {
- height: 7px;
- overflow: hidden;
- border-radius: var(--sycm-radius-pill);
- background: var(--sycm-fill-secondary);
-}
-
-.preview-review-stars i,
-.preview-review-keyword-list i {
- display: block;
- height: 100%;
- border-radius: inherit;
- background: var(--sycm-color-primary);
-}
-
-.preview-review-stars > div:nth-child(n + 3) i,
-.preview-review-keyword-list i {
- background: var(--sycm-color-danger);
-}
-
-.preview-review-stars strong,
-.preview-review-keyword-list strong {
- color: var(--sycm-text-strong);
- font-family: var(--sycm-font-family-number);
- font-size: 12px;
- text-align: right;
-}
-
-.preview-review-keyword-list > div {
- display: grid;
- grid-template-columns: 20px minmax(70px, 110px) minmax(70px, 1fr) 46px;
- align-items: center;
- gap: 10px;
-}
-
-.preview-review-keyword-list b {
- display: inline-flex;
- width: 20px;
- height: 20px;
- align-items: center;
- justify-content: center;
- border-radius: 4px;
- background: #fff1f0;
- color: var(--sycm-color-danger);
- font-size: 11px;
-}
-
-.preview-review-keyword-list span {
- overflow: hidden;
- color: var(--sycm-text-secondary);
- font-size: 12px;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.preview-review-keywords__empty {
- display: flex;
- min-height: 168px;
- align-items: center;
- justify-content: center;
- color: var(--sycm-text-tertiary);
- font-size: var(--sycm-font-size-caption);
-}
-
-.preview-compensation-section {
- margin-bottom: 18px;
- border-radius: var(--sycm-radius-lg);
- background: var(--sycm-bg-container);
- box-shadow: var(--sycm-shadow-card);
- padding: 18px 20px 20px;
-}
-
-.preview-compensation-section__header {
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- gap: 20px;
- margin-bottom: 18px;
-}
-
-.preview-compensation-section__header h2,
-.preview-compensation-subtitle h3 {
- margin: 0;
- color: var(--sycm-text-strong);
- font-weight: var(--sycm-font-weight-semibold);
-}
-
-.preview-compensation-section__header h2 {
- font-size: var(--sycm-font-size-section);
-}
-
-.preview-compensation-section__header p {
- margin: 4px 0 0;
- color: var(--sycm-text-tertiary);
- font-size: var(--sycm-font-size-caption);
-}
-
-.preview-compensation-section__header > span,
-.preview-compensation-subtitle > span {
- color: var(--sycm-text-tertiary);
- font-size: 11px;
- white-space: nowrap;
-}
-
-.preview-compensation-section__header > span {
- color: var(--sycm-chart-axis-text);
- font-size: var(--sycm-font-size-caption);
-}
-
-.preview-compensation-section__body {
- display: grid;
- grid-template-columns: minmax(310px, 0.9fr) minmax(390px, 1.1fr) minmax(420px, 1.2fr);
- min-height: 248px;
-}
-
-.preview-compensation-kpis {
- display: grid;
- grid-template-columns: minmax(150px, 0.9fr) minmax(180px, 1.1fr);
- padding-right: 24px;
-}
-
-.preview-compensation-kpis__primary {
- display: flex;
- flex-direction: column;
- justify-content: center;
- border-right: 1px solid var(--sycm-border-light);
- padding-right: 20px;
-}
-
-.preview-compensation-kpis__primary > span,
-.preview-compensation-kpis__grid span {
- color: var(--sycm-text-secondary);
- font-size: var(--sycm-font-size-caption);
-}
-
-.preview-compensation-kpis__primary strong {
- margin: 10px 0 8px;
- color: var(--sycm-color-danger);
- font-family: var(--sycm-font-family-number);
- font-size: 30px;
- font-weight: var(--sycm-font-weight-semibold);
- line-height: 1.1;
- white-space: nowrap;
-}
-
-.preview-compensation-kpis__primary em {
- color: var(--sycm-text-tertiary);
- font-size: 11px;
- font-style: normal;
-}
-
-.preview-compensation-kpis__grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
-}
-
-.preview-compensation-kpis__grid > div {
- display: flex;
- min-width: 0;
- flex-direction: column;
- justify-content: center;
- gap: 6px;
- padding-left: 20px;
-}
-
-.preview-compensation-kpis__grid > div:nth-child(-n + 2) {
- border-bottom: 1px solid var(--sycm-border-light);
-}
-
-.preview-compensation-kpis__grid strong {
- overflow: hidden;
- color: var(--sycm-text-strong);
- font-family: var(--sycm-font-family-number);
- font-size: 18px;
- font-weight: var(--sycm-font-weight-semibold);
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.preview-compensation-reasons {
- border-right: 1px solid var(--sycm-border-light);
- border-left: 1px solid var(--sycm-border-light);
- padding: 0 24px;
-}
-
-.preview-compensation-goods {
- min-width: 0;
- padding-left: 24px;
-}
-
-.preview-compensation-subtitle {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- margin-bottom: 14px;
-}
-
-.preview-compensation-subtitle h3 {
- font-size: var(--sycm-font-size-body);
-}
-
-.preview-compensation-reason-list {
- display: flex;
- flex-direction: column;
- gap: 11px;
-}
-
-.preview-compensation-reason-list > div {
- display: grid;
- grid-template-columns: minmax(100px, 1.15fr) minmax(75px, 1fr) 86px 46px;
- align-items: center;
- gap: 10px;
- font-size: 12px;
-}
-
-.preview-compensation-reason-list > div > span {
- overflow: hidden;
- color: var(--sycm-text-secondary);
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.preview-compensation-reason-list > div > div {
- height: 7px;
- overflow: hidden;
- border-radius: var(--sycm-radius-pill);
- background: var(--sycm-fill-secondary);
-}
-
-.preview-compensation-reason-list i {
- display: block;
- height: 100%;
- border-radius: inherit;
- background: var(--sycm-color-danger);
-}
-
-.preview-compensation-reason-list strong,
-.preview-compensation-reason-list em {
- color: var(--sycm-text-strong);
- font-family: var(--sycm-font-family-number);
- font-size: 12px;
- font-style: normal;
- text-align: right;
-}
-
-.preview-compensation-reason-list em {
- color: var(--sycm-text-tertiary);
-}
-
-.preview-compensation-goods-list {
- display: flex;
- flex-direction: column;
- gap: 8px;
-}
-
-.preview-compensation-goods-list > div {
- display: grid;
- grid-template-columns: 20px minmax(0, 1fr) 78px;
- align-items: center;
- gap: 10px;
- min-width: 0;
-}
-
-.preview-compensation-goods-list > div > b {
- display: inline-flex;
- width: 20px;
- height: 20px;
- align-items: center;
- justify-content: center;
- border-radius: 4px;
- background: #fff1f0;
- color: var(--sycm-color-danger);
- font-size: 11px;
-}
-
-.preview-compensation-goods-list__info,
-.preview-compensation-goods-list__amount {
- display: flex;
- min-width: 0;
- flex-direction: column;
- gap: 3px;
-}
-
-.preview-compensation-goods-list__info strong {
- overflow: hidden;
- color: var(--sycm-text-strong);
- font-family: var(--sycm-font-family-number);
- font-size: 12px;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.preview-compensation-goods-list__info span,
-.preview-compensation-goods-list__amount span {
- overflow: hidden;
- color: var(--sycm-text-tertiary);
- font-size: 11px;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.preview-compensation-goods-list__amount {
- align-items: flex-end;
-}
-
-.preview-compensation-goods-list__amount strong {
- color: var(--sycm-color-danger);
- font-family: var(--sycm-font-family-number);
- font-size: 12px;
- white-space: nowrap;
-}
-
-.preview-compensation-empty {
- display: flex;
- min-height: 190px;
- align-items: center;
- justify-content: center;
- color: var(--sycm-text-tertiary);
- font-size: var(--sycm-font-size-caption);
-}
-
.preview-growth-section {
margin-bottom: var(--sycm-space-md);
border-radius: 12px;
@@ -2347,7 +1496,7 @@
@media (max-width: 1600px) {
.preview-hero-grid {
- grid-template-columns: minmax(0, 1fr) 362px;
+ grid-template-columns: minmax(0, 1fr);
}
.preview-overview-actions {
@@ -2385,7 +1534,6 @@
grid-template-columns: repeat(2, minmax(0, 1fr));
}
- .preview-side-column,
.preview-growth-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@@ -2415,7 +1563,6 @@
.preview-overview-header,
.preview-metrics,
- .preview-side-column,
.preview-growth-grid {
grid-template-columns: 1fr;
}
@@ -2589,13 +1736,6 @@
width: min(360px, calc(100% - 28px));
}
- .preview-side-column {
- grid-template-columns: minmax(0, 1fr);
- grid-template-rows: minmax(223px, 1fr);
- align-self: start;
- gap: 14px;
- }
-
.preview-lower-grid {
grid-template-columns: minmax(0, 1fr);
}
@@ -2615,35 +1755,6 @@
}
@container preview-workbench (max-width: 1100px) {
- .preview-review-section__body {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
-
- .preview-review-kpis {
- grid-column: 1 / -1;
- margin-bottom: 20px;
- border-bottom: 1px solid var(--sycm-border-light);
- padding: 0 0 20px;
- }
-
- .preview-review-distribution {
- border-left: 0;
- }
-
- .preview-compensation-section__body {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
-
- .preview-compensation-kpis {
- grid-column: 1 / -1;
- margin-bottom: 20px;
- border-bottom: 1px solid var(--sycm-border-light);
- padding: 0 0 20px;
- }
-
- .preview-compensation-reasons {
- border-left: 0;
- }
.preview-suggestion-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -2666,82 +1777,10 @@
padding: 16px;
}
- .preview-side-column,
.preview-growth-grid {
grid-template-columns: minmax(0, 1fr);
}
- .preview-side-column {
- grid-template-rows: auto auto;
- }
-
- .preview-review-section {
- padding: 16px;
- }
-
- .preview-review-section__header,
- .preview-review-subtitle,
- .preview-compensation-section__header {
- align-items: flex-start;
- flex-direction: column;
- }
-
- .preview-review-section__body,
- .preview-review-kpis {
- grid-template-columns: minmax(0, 1fr);
- }
-
- .preview-review-kpis__primary {
- border-right: 0;
- border-bottom: 1px solid var(--sycm-border-light);
- padding: 0 0 18px;
- }
-
- .preview-review-kpis__grid {
- min-height: 150px;
- }
-
- .preview-review-distribution,
- .preview-review-keywords {
- border: 0;
- padding: 20px 0 0;
- }
-
- .preview-review-distribution {
- border-bottom: 1px solid var(--sycm-border-light);
- padding-bottom: 20px;
- }
-
- .preview-compensation-section {
- padding: 16px;
- }
-
- .preview-compensation-section__body,
- .preview-compensation-kpis {
- grid-template-columns: minmax(0, 1fr);
- }
-
- .preview-compensation-kpis__primary {
- border-right: 0;
- border-bottom: 1px solid var(--sycm-border-light);
- padding: 0 0 18px;
- }
-
- .preview-compensation-kpis__grid {
- min-height: 150px;
- }
-
- .preview-compensation-reasons,
- .preview-compensation-goods {
- border: 0;
- padding: 20px 0 0;
- }
-
- .preview-compensation-reasons {
- border-bottom: 1px solid var(--sycm-border-light);
- padding-bottom: 20px;
- }
-
.preview-suggestion-grid {
grid-template-columns: minmax(0, 1fr);
}
@@ -3235,25 +2274,6 @@
padding: 0 16px;
}
-.preview-diagnosis-summary {
- gap: 10px;
- padding: 0 0 12px;
-}
-
-.preview-diagnosis-summary span {
- padding: 6px 10px;
-}
-
-.preview-diagnosis-issue {
- margin: 0;
- padding: 14px 16px;
-}
-
-.preview-diagnosis-issue p {
- margin-top: 8px;
- line-height: 1.6;
-}
-
.preview-journey-card {
margin-bottom: 16px;
}
diff --git a/frontend/src/pages/PricingCalculator/DefaultsConfigModal.jsx b/frontend/src/pages/PricingCalculator/DefaultsConfigModal.jsx
deleted file mode 100644
index 0c3cc8e..0000000
--- a/frontend/src/pages/PricingCalculator/DefaultsConfigModal.jsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import React, { useState } from 'react';
-import { Button, Modal } from 'antd';
-import { DiscountInput, MoneyInput, PercentInput } from './inputs';
-import { SYSTEM_DEFAULTS } from './constants';
-
-const FIELD_DEFS = [
- { key: 'coupon', label: '优惠券金额', Input: MoneyInput },
- { key: 'activityDiscount', label: '活动折扣', Input: DiscountInput },
- { key: 'installFee', label: '安装费', Input: MoneyInput },
- { key: 'targetMargin', label: '营业利润率', Input: PercentInput },
-];
-
-const formatDefaultTip = (key, value) => {
- if (key === 'targetMargin') return `系统默认 ${Number(value * 100).toFixed(0)}%`;
- if (key === 'activityDiscount') return `系统默认 ${value}(即 ${Number(value * 10).toFixed(0)} 折)`;
- return `系统默认 ${value}`;
-};
-
-// 表单主体:每次打开弹窗时重新挂载,初始值取 个性化配置 > 系统默认
-const DefaultsForm = ({ defaults, systemDefaults, saving, onSave }) => {
- const [form, setForm] = useState(() => ({ ...systemDefaults, ...(defaults || {}) }));
-
- return (
-
-
- 配置保存到你的账号,仅自己生效。保存后页面中仍使用旧默认值的行会自动更新为新值,手动修改过的行不受影响;之后的新的测算也按此填充。
-
- {FIELD_DEFS.map(({ key, label, Input }) => (
-
- {label}
- {React.createElement(Input, {
- style: { width: 160 },
- value: form[key],
- onChange: (next) => setForm((prev) => ({ ...prev, [key]: next })),
- })}
-
- {formatDefaultTip(key, systemDefaults[key])}
-
-
- ))}
-
-
-
-
-
- );
-};
-
-/**
- * 个性化默认值配置弹窗:
- * - 每个运营维护自己的默认值,保存到本人账号(服务端按用户隔离)
- * - 用于"新测算/新条码行"的默认值填充;已生成的行与历史快照不受影响
- */
-const DefaultsConfigModal = ({
- open,
- onClose,
- defaults,
- systemDefaults = SYSTEM_DEFAULTS,
- saving,
- onSave,
- title = '默认值配置',
-}) => (
-
- {open ? (
-
- ) : null}
-
-);
-
-export default DefaultsConfigModal;
diff --git a/frontend/src/pages/PricingCalculator/DropshipCalculator.jsx b/frontend/src/pages/PricingCalculator/DropshipCalculator.jsx
deleted file mode 100644
index 3f37e0b..0000000
--- a/frontend/src/pages/PricingCalculator/DropshipCalculator.jsx
+++ /dev/null
@@ -1,909 +0,0 @@
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
-import {
- Alert,
- Button,
- Empty,
- Image,
- Input,
- InputNumber,
- Modal,
- Popover,
- Select,
- Table,
- Tag,
- Tooltip,
- message,
-} from 'antd';
-import {
- CalculatorOutlined,
- ControlOutlined,
- DownloadOutlined,
- ExportOutlined,
- HistoryOutlined,
- QuestionCircleOutlined,
- ReloadOutlined,
- SaveOutlined,
- SearchOutlined,
- SettingOutlined,
- ShopOutlined,
-} from '@ant-design/icons';
-import { SycmDashboardMetric } from '../../components/Common';
-import { getPlanningAlibaba1688ProductDetail } from '../../api/planningUpload';
-import {
- exportPricingTable,
- fetchPricingHistoryDetail,
- fetchPricingPrefs,
- savePricingHistory,
- savePricingPrefs,
-} from '../../api/pricingCalculator';
-import { computeRow, formatMoney, formatPercent } from './calc';
-import { DiscountInput, MoneyInput, PercentInput } from './inputs';
-import DefaultsConfigModal from './DefaultsConfigModal';
-import FieldConfigDrawer from './FieldConfigDrawer';
-import HistoryDrawer from './HistoryDrawer';
-import {
- applyDropshipDefaultsToRows,
- createDropshipRows,
- DROPSHIP_FIXED_RATES,
- DROPSHIP_SYSTEM_DEFAULTS,
- getDropshipDimensions,
- getDropshipSpecValue,
- mergeDropshipSelectionPricingRows,
- parseAlibaba1688ProductId,
-} from './dropship';
-import {
- createDefaultDropshipFieldConfig,
- DROPSHIP_FIELD_LABELS,
- loadDropshipFieldConfig,
- saveDropshipFieldConfig,
-} from './dropshipFieldConfig';
-import { PERCENT_FIELD_KEYS } from './fieldConfig';
-import './style.css';
-
-const DEFAULT_FILTERS = { available: undefined, keyword: '', specs: {} };
-
-const LEGEND_ITEMS = [
- { className: 'is-input', label: '可填写(到手价 / 安装费 / 营业利润率)' },
- { className: 'is-auto', label: '1688 自动获取 / 固定比例' },
- { className: 'is-formula', label: '公式' },
- { className: 'is-pdd', label: '拼多多活动(优惠券 / 折扣可填)' },
- { className: 'is-danger', label: '营业利润额为负(亏本)' },
-];
-
-const ProductThumb = ({ url, size = 36 }) => {
- const [failedUrl, setFailedUrl] = useState('');
- const normalizedUrl = String(url || '').trim().replace(/^\/\//, 'https://');
-
- if (!normalizedUrl || failedUrl === normalizedUrl) {
- return (
-
- 无图
-
- );
- }
- return (
-
(
-
- ),
- }}
- onError={() => setFailedUrl(normalizedUrl)}
- />
- );
-};
-
-const AutoHeaderTitle = ({ children, tip }) => (
-
- {children}
- {tip ? (
-
-
-
- ) : null}
-
-);
-
-const numericSorter = (field) => (leftRow, rightRow) => {
- const left = Number.isFinite(leftRow[field]) ? leftRow[field] : Number.NEGATIVE_INFINITY;
- const right = Number.isFinite(rightRow[field]) ? rightRow[field] : Number.NEGATIVE_INFINITY;
- return left - right;
-};
-
-const getFriendlyError = (error, fallback) => (
- error?.response?.data?.error || error?.message || fallback
-);
-
-const normalizeRestoredRows = (rows, defaults) => (Array.isArray(rows) ? rows : []).map((row, index) => ({
- ...row,
- id: row.id || `1688:restored:${row.skuCode || index}:${index}`,
- specs: Array.isArray(row.specs) ? row.specs : [],
- specsText: row.specsText || (Array.isArray(row.specs)
- ? row.specs.map((spec) => `${spec.name}:${spec.value}`).join(' / ')
- : ''),
- price: row.price ?? row.groupPrice ?? null,
- coupon: row.coupon ?? defaults.coupon,
- activityDiscount: row.activityDiscount ?? defaults.activityDiscount,
- installFee: row.installFee ?? defaults.installFee,
- targetMargin: row.targetMargin ?? defaults.targetMargin,
- ...DROPSHIP_FIXED_RATES,
-}));
-
-const DropshipCalculator = ({
- embedded = false,
- productId: embeddedProductId = '',
- selectionPlanId = '',
- selectionItemId = '',
- selectionRows = [],
- onSelectionPricingSaved,
-} = {}) => {
- const [input, setInput] = useState(embeddedProductId);
- const [productDetail, setProductDetail] = useState(null);
- const [rows, setRows] = useState([]);
- const [searching, setSearching] = useState(false);
- const [searchError, setSearchError] = useState('');
- const [filters, setFilters] = useState(DEFAULT_FILTERS);
- const [fieldConfig, setFieldConfig] = useState(loadDropshipFieldConfig);
- const [fieldConfigOpen, setFieldConfigOpen] = useState(false);
- const [defaults, setDefaults] = useState(null);
- const [defaultsLoaded, setDefaultsLoaded] = useState(false);
- const [defaultsOpen, setDefaultsOpen] = useState(false);
- const [batchDiscount, setBatchDiscount] = useState(null);
- const [defaultsSaving, setDefaultsSaving] = useState(false);
- const [exporting, setExporting] = useState(false);
- const [historyOpen, setHistoryOpen] = useState(false);
- const [historyRefreshKey, setHistoryRefreshKey] = useState(0);
- const [restoring, setRestoring] = useState(false);
- const [saveModalOpen, setSaveModalOpen] = useState(false);
- const [saveNote, setSaveNote] = useState('');
- const [saving, setSaving] = useState(false);
- const autoLoadedProductRef = React.useRef('');
- const isSelectionPricingMode = Boolean(selectionPlanId && selectionItemId && embeddedProductId);
-
- useEffect(() => {
- let cancelled = false;
- fetchPricingPrefs('alibaba1688')
- .then((data) => {
- if (!cancelled) setDefaults(data?.prefs || null);
- })
- .catch(() => {})
- .finally(() => {
- if (!cancelled) setDefaultsLoaded(true);
- });
- return () => { cancelled = true; };
- }, []);
-
- const effectiveDefaults = useMemo(
- () => ({ ...DROPSHIP_SYSTEM_DEFAULTS, ...(defaults || {}) }),
- [defaults]
- );
-
- const computedRows = useMemo(
- () => rows.map((row) => ({ ...row, ...computeRow(row) })),
- [rows]
- );
-
- const dimensions = useMemo(
- () => getDropshipDimensions(productDetail, rows),
- [productDetail, rows]
- );
-
- const dimensionOptions = useMemo(() => Object.fromEntries(dimensions.map((dimension) => [
- dimension,
- [...new Set(rows.map((row) => getDropshipSpecValue(row, dimension)).filter(Boolean))]
- .map((value) => ({ value, label: value })),
- ])), [dimensions, rows]);
-
- const filteredRows = useMemo(() => {
- const keyword = filters.keyword.trim().toLowerCase();
- return computedRows.filter((row) => {
- if (filters.available !== undefined && row.available !== filters.available) return false;
- if (Object.entries(filters.specs).some(([name, value]) => (
- value && getDropshipSpecValue(row, name) !== value
- ))) return false;
- if (keyword && ![row.skuCode, row.name, row.specsText]
- .some((value) => String(value || '').toLowerCase().includes(keyword))) return false;
- return true;
- });
- }, [computedRows, filters]);
-
- const summaryMetrics = useMemo(() => {
- const margins = filteredRows.map((row) => row.marginRate).filter(Number.isFinite);
- const profits = filteredRows.map((row) => row.profitAmount).filter(Number.isFinite);
- const average = (values) => (values.length
- ? values.reduce((sum, value) => sum + value, 0) / values.length
- : null);
- return [
- { key: 'count', label: '测算 SKU 数', value: `${filteredRows.length}` },
- { key: 'margin', label: '平均毛利率', value: margins.length ? formatPercent(average(margins), 1) : '--' },
- { key: 'profit', label: '平均营业利润额', value: profits.length ? `¥${formatMoney(average(profits))}` : '--' },
- {
- key: 'negativeProfit',
- label: '营业利润额为负(亏本)',
- value: `${filteredRows.filter((row) => Number.isFinite(row.profitAmount) && row.profitAmount < 0).length} 行`,
- },
- ];
- }, [filteredRows]);
-
- const runSearch = useCallback(async (rawInput) => {
- const parsed = parseAlibaba1688ProductId(rawInput);
- if (!parsed.productId) {
- setSearchError(parsed.error);
- return;
- }
- setSearching(true);
- setSearchError('');
- try {
- const response = await getPlanningAlibaba1688ProductDetail(parsed.productId);
- if (response?.success === false) throw new Error(response.error || '1688 商品查询失败');
- const detail = response?.data || response;
- const nextRows = mergeDropshipSelectionPricingRows(
- createDropshipRows(detail, effectiveDefaults, {
- useSourceSkuCode: isSelectionPricingMode,
- }),
- selectionRows
- );
- if (!nextRows.length) {
- throw new Error(`商品「${parsed.productId}」没有可用于测算的 SKU 明细`);
- }
- setInput(parsed.productId);
- setProductDetail(detail);
- setRows(nextRows);
- setFilters(DEFAULT_FILTERS);
- } catch (error) {
- setProductDetail(null);
- setRows([]);
- setSearchError(getFriendlyError(error, '1688 商品查询失败,请稍后重试'));
- } finally {
- setSearching(false);
- }
- }, [effectiveDefaults, isSelectionPricingMode, selectionRows]);
-
- useEffect(() => {
- const productId = String(embeddedProductId || '').trim();
- if (!defaultsLoaded || !productId || autoLoadedProductRef.current === productId) return;
- autoLoadedProductRef.current = productId;
- setInput(productId);
- runSearch(productId);
- }, [defaultsLoaded, embeddedProductId, runSearch]);
-
- const updateRow = (id, patch) => {
- setRows((current) => current.map((row) => (row.id === id ? { ...row, ...patch } : row)));
- };
-
- // 一键批量设置折扣:作用于当前筛选后的全部行
- const applyBatchDiscount = () => {
- if (!batchDiscount || batchDiscount <= 0) return;
- const ids = new Set(filteredRows.map((row) => row.id));
- setRows((current) => current.map((row) => (ids.has(row.id) ? { ...row, activityDiscount: batchDiscount } : row)));
- message.success(`已把 ${ids.size} 行的活动折扣统一设置为 ${batchDiscount}(即 ${batchDiscount * 10} 折)`);
- };
-
- const resetToHero = () => {
- setProductDetail(null);
- setRows([]);
- setSearchError('');
- setFilters(DEFAULT_FILTERS);
- };
-
- const applyFilter = (patch) => {
- setFilters((current) => ({ ...current, ...patch }));
- };
-
- const applySpecFilter = (dimension, value) => {
- setFilters((current) => ({
- ...current,
- specs: { ...current.specs, [dimension]: value },
- }));
- };
-
- const resetFilters = () => {
- setFilters(DEFAULT_FILTERS);
- };
-
- const handleSaveDefaults = async (form) => {
- setDefaultsSaving(true);
- try {
- const data = await savePricingPrefs(form, 'alibaba1688');
- const nextDefaults = { ...DROPSHIP_SYSTEM_DEFAULTS, ...(data?.prefs || form) };
- setRows((current) => applyDropshipDefaultsToRows(current, defaults, nextDefaults));
- setDefaults(data?.prefs || form);
- setDefaultsOpen(false);
- message.success('1688 默认值已保存,未手动修改的行已同步更新');
- } catch (error) {
- message.error(getFriendlyError(error, '保存默认值配置失败'));
- } finally {
- setDefaultsSaving(false);
- }
- };
-
- const product = productDetail?.product || productDetail || {};
- const productId = String(productDetail?.productId || product.productId || '');
- const productTitle = productDetail?.title || product.title || '';
- const productMainImage = productDetail?.mainImage || product.mainImage || rows[0]?.imageUrl || '';
- const productSourceUrl = productDetail?.sourceUrl || product.sourceUrl || '';
-
- const formulaCell = { className: 'pricing-calc-td--formula' };
- const inputCell = { className: 'pricing-calc-td--input' };
- const pddCell = { className: 'pricing-calc-td--pdd' };
- const pddInputCell = { className: 'pricing-calc-td--pdd-input' };
- const autoHeader = { className: 'pricing-calc-th--auto' };
- const inputHeader = { className: 'pricing-calc-th--input' };
- const formulaHeader = { className: 'pricing-calc-th--formula' };
- const pddHeader = { className: 'pricing-calc-th--pdd' };
-
- const columns = [
- {
- title: '图片',
- dataIndex: 'imageUrl',
- width: 64,
- fixed: 'left',
- align: 'center',
- render: (value) => ,
- },
- {
- title: '规格组合',
- dataIndex: 'specsText',
- width: 260,
- render: (_, record) => (
-
- {record.specs.length ? record.specs.map((spec, index) => (
-
- {spec.name}
- {spec.value}
-
- )) : -}
-
- ),
- },
- {
- title: 采购成本,
- dataIndex: 'cost',
- width: 100,
- align: 'right',
- sorter: numericSorter('cost'),
- onHeaderCell: () => autoHeader,
- render: (value) => {formatMoney(value)},
- },
- {
- title: 到手价,
- dataIndex: 'price',
- width: 110,
- sorter: numericSorter('price'),
- onHeaderCell: () => inputHeader,
- onCell: () => inputCell,
- render: (value, record) => (
- updateRow(record.id, { price: next })} />
- ),
- },
- {
- title: 安装费,
- dataIndex: 'installFee',
- width: 96,
- onHeaderCell: () => inputHeader,
- onCell: () => inputCell,
- render: (value, record) => (
- updateRow(record.id, { installFee: next })} />
- ),
- },
- {
- title: 运费,
- dataIndex: 'shipping',
- width: 96,
- align: 'right',
- sorter: numericSorter('shipping'),
- onHeaderCell: () => autoHeader,
- render: (value) => {formatMoney(value)},
- },
- {
- title: '毛利率',
- dataIndex: 'marginRate',
- width: 92,
- align: 'right',
- sorter: numericSorter('marginRate'),
- onHeaderCell: () => formulaHeader,
- onCell: () => formulaCell,
- render: (value) => {formatPercent(value)},
- },
- {
- title: 平台佣金,
- dataIndex: 'commission',
- width: 96,
- align: 'right',
- onHeaderCell: () => autoHeader,
- render: (value) => {formatPercent(value)},
- },
- {
- title: 推广占比,
- dataIndex: 'promoRate',
- width: 104,
- align: 'right',
- sorter: numericSorter('promoRate'),
- onHeaderCell: () => formulaHeader,
- onCell: () => formulaCell,
- render: (value) => {formatPercent(value)},
- },
- {
- title: 营销广告费,
- dataIndex: 'adFee',
- width: 100,
- align: 'right',
- onHeaderCell: () => autoHeader,
- render: (value) => {formatPercent(value)},
- },
- {
- title: 种菜占比,
- dataIndex: 'seeding',
- width: 96,
- align: 'right',
- onHeaderCell: () => autoHeader,
- render: (value) => {formatPercent(value)},
- },
- {
- title: 售后费用,
- dataIndex: 'afterSale',
- width: 96,
- align: 'right',
- onHeaderCell: () => autoHeader,
- render: (value) => {formatPercent(value)},
- },
- {
- title: 保险费,
- dataIndex: 'insurance',
- width: 92,
- align: 'right',
- onHeaderCell: () => autoHeader,
- render: (value) => {formatPercent(value)},
- },
- {
- title: 品牌使用费,
- dataIndex: 'brandFee',
- width: 100,
- align: 'right',
- onHeaderCell: () => autoHeader,
- render: (value) => {formatPercent(value)},
- },
- {
- title: 营业利润率,
- dataIndex: 'targetMargin',
- width: 100,
- align: 'right',
- onHeaderCell: () => inputHeader,
- onCell: () => inputCell,
- render: (value, record) => (
- updateRow(record.id, { targetMargin: next })} />
- ),
- },
- {
- title: '营业利润额',
- dataIndex: 'profitAmount',
- width: 110,
- align: 'right',
- fixed: 'right',
- sorter: numericSorter('profitAmount'),
- onHeaderCell: () => formulaHeader,
- onCell: (record) => ({
- className: Number.isFinite(record.profitAmount) && record.profitAmount < 0
- ? 'pricing-calc-td--danger'
- : 'pricing-calc-td--formula',
- }),
- render: (value) => {formatMoney(value)},
- },
- {
- title: 优惠券金额,
- dataIndex: 'coupon',
- width: 104,
- align: 'right',
- onHeaderCell: () => pddHeader,
- onCell: () => pddInputCell,
- render: (value, record) => (
- updateRow(record.id, { coupon: next })} />
- ),
- },
- {
- title: 活动折扣,
- dataIndex: 'activityDiscount',
- width: 96,
- align: 'right',
- onHeaderCell: () => pddHeader,
- onCell: () => pddInputCell,
- render: (value, record) => (
- updateRow(record.id, { activityDiscount: next })} />
- ),
- },
- {
- title: 拼单价,
- dataIndex: 'pddPrice',
- width: 100,
- align: 'right',
- fixed: 'right',
- sorter: numericSorter('pddPrice'),
- onHeaderCell: () => pddHeader,
- onCell: () => pddCell,
- render: (value) => {formatMoney(value)},
- },
- {
- title: 最终拼单价,
- dataIndex: 'pddFinalPrice',
- width: 110,
- align: 'right',
- fixed: 'right',
- sorter: numericSorter('pddFinalPrice'),
- onHeaderCell: () => pddHeader,
- onCell: () => pddCell,
- render: (value) => {formatMoney(value)},
- },
- ];
-
- const columnsByKey = new Map(columns.map((column) => [column.dataIndex, {
- ...column,
- key: column.dataIndex,
- }]));
- const visibleColumns = fieldConfig.order
- .filter((key) => !fieldConfig.hidden.includes(key) && columnsByKey.has(key))
- .map((key) => columnsByKey.get(key));
- const tableScrollX = visibleColumns.reduce((sum, column) => sum + (column.width || 100), 0) + 40;
-
- const handleExport = async () => {
- if (!productId || filteredRows.length === 0) {
- message.warning('没有可导出的数据');
- return;
- }
- setExporting(true);
- try {
- const exportColumns = visibleColumns.map((column) => ({
- key: column.dataIndex,
- label: DROPSHIP_FIELD_LABELS[column.dataIndex] || column.dataIndex,
- percent: PERCENT_FIELD_KEYS.has(column.dataIndex),
- }));
- const exportRows = filteredRows.map((row) => Object.fromEntries(
- exportColumns.map(({ key }) => [key, row[key]])
- ));
- const blob = await exportPricingTable({ spu: productId, columns: exportColumns, rows: exportRows });
- const stamp = new Date();
- const pad = (value) => String(value).padStart(2, '0');
- const link = document.createElement('a');
- link.href = URL.createObjectURL(blob);
- link.download = `1688代发测算_${productId}_${stamp.getFullYear()}${pad(stamp.getMonth() + 1)}${pad(stamp.getDate())}-${pad(stamp.getHours())}${pad(stamp.getMinutes())}.xlsx`;
- link.click();
- URL.revokeObjectURL(link.href);
- message.success(`已导出 ${filteredRows.length} 行 × ${exportColumns.length} 列`);
- } catch (error) {
- message.error(getFriendlyError(error, '导出失败,请稍后重试'));
- } finally {
- setExporting(false);
- }
- };
-
- const handleSave = async () => {
- setSaving(true);
- try {
- await savePricingHistory({
- calculatorType: 'alibaba1688',
- spuCode: productId,
- selectionContext: isSelectionPricingMode ? {
- planId: selectionPlanId,
- itemId: selectionItemId,
- workflowProfile: 'alibaba_1688_pending',
- } : undefined,
- brand: productDetail?.storeName || product.storeName || '',
- note: saveNote.trim(),
- product: {
- productId,
- title: productTitle,
- mainImage: productMainImage,
- sourceUrl: productSourceUrl,
- storeName: productDetail?.storeName || product.storeName || '',
- category: productDetail?.category || product.category || '',
- skuDimensions: product.skuDimensions || [],
- },
- summary: {
- rowCount: computedRows.length,
- negativeProfitCount: computedRows.filter((row) => (
- Number.isFinite(row.profitAmount) && row.profitAmount < 0
- )).length,
- },
- rows: computedRows,
- });
- setSaveModalOpen(false);
- setHistoryRefreshKey((key) => key + 1);
- if (isSelectionPricingMode && typeof onSelectionPricingSaved === 'function') {
- await onSelectionPricingSaved();
- }
- message.success(isSelectionPricingMode ? '1688 定价表已保存' : '1688 代发测算记录已保存,可在测算历史中还原');
- } catch (error) {
- message.error(getFriendlyError(error, '保存测算记录失败'));
- } finally {
- setSaving(false);
- }
- };
-
- const handleRestore = async (record) => {
- setRestoring(true);
- try {
- const detail = await fetchPricingHistoryDetail(record.id);
- const payload = detail?.payload;
- if (!payload || payload.calculatorType !== 'alibaba1688' || !Array.isArray(payload.rows)) {
- throw new Error('该记录不是 1688 代发测算记录,无法在当前页签还原');
- }
- const restoredProduct = payload.product || { productId: payload.spuCode || detail.spuCode };
- setProductDetail({
- ...restoredProduct,
- productId: restoredProduct.productId || payload.spuCode || detail.spuCode,
- restoredAt: detail.createdAt,
- product: restoredProduct,
- });
- setRows(normalizeRestoredRows(payload.rows, effectiveDefaults));
- setInput(restoredProduct.productId || payload.spuCode || detail.spuCode || '');
- setFilters(DEFAULT_FILTERS);
- setHistoryOpen(false);
- message.success(`已还原商品 ${detail.spuCode} 的测算记录`);
- } catch (error) {
- message.error(getFriendlyError(error, '还原测算记录失败'));
- } finally {
- setRestoring(false);
- }
- };
-
- const renderHero = () => (
-
-
-
-
-
1688代发拼多多测算
-
- 输入 1688 商品 ID 或商品详情链接,读取全部 SKU 的采购价、规格、图片与运费,按非黑标拼多多店铺规则完成售价与利润测算。
-
-
setInput(event.target.value)}
- onSearch={runSearch}
- />
- {searchError ? (
-
- ) : null}
-
- 平台佣金 1.5% · 营销广告 2.5% · 营业利润率默认 12%
- {!embedded ? } loading={restoring} onClick={() => setHistoryOpen(true)}>
- 测算历史
- : null}
-
-
- );
-
- const renderResult = () => (
-
-
-
-
-
-
- {productTitle || '1688 商品'}
-
-
- {productId}
- {rows.length} 个 SKU
- {dimensions.length} 个规格组
- {productDetail?.storeName || product.storeName ? (
- 厂家:{productDetail?.storeName || product.storeName}
- ) : null}
- {productDetail?.restoredAt ? 历史还原 : null}
- 佣金 1.5%
- 种菜 0%
- 品牌费 0%
-
-
-
-
-
- setInput(event.target.value)}
- onSearch={runSearch}
- />
-
-
- {!embedded ?
} onClick={resetToHero}>新的测算 : null}
- {!embedded ?
} loading={restoring} onClick={() => setHistoryOpen(true)}>测算历史 : null}
-
- setBatchDiscount(value)}
- />
-
-
- )}
- >
-
}>批量折扣
-
-
} onClick={() => setDefaultsOpen(true)}>默认值
-
} loading={exporting} onClick={handleExport}>导出
-
} onClick={() => setFieldConfigOpen(true)}>字段配置
- {productSourceUrl ? (
-
} href={productSourceUrl} target="_blank" rel="noreferrer">查看 1688 商品
- ) : null}
-
}
- disabled={rows.length === 0}
- onClick={() => {
- setSaveNote('');
- setSaveModalOpen(true);
- }}
- >
- {isSelectionPricingMode ? '保存定价表' : '保存测算记录'}
-
-
-
-
- {summaryMetrics.map((item) => (
-
- ))}
-
-
-
-
-
-
-
- {dimensions.map((dimension) => (
-
-
-
- ))}
- }
- placeholder="SKU 名称 / 规格"
- value={filters.keyword}
- onChange={(event) => applyFilter({ keyword: event.target.value })}
- />
- } onClick={resetFilters}>重置
-
- {LEGEND_ITEMS.map((item) => (
-
-
- {item.label}
-
- ))}
-
-
-
- }}
- />
-
- );
-
- return (
- <>
- {productDetail ? renderResult() : renderHero()}
-
-