From 2b65206b84df4f28d53b6aa3e87b8146aa1c5410 Mon Sep 17 00:00:00 2001 From: ludan <251918489@qq.com> Date: Thu, 4 Jun 2026 17:27:40 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E7=94=B5=E5=95=86=E5=85=8B?= =?UTF-8?q?=E9=9A=86=E4=B8=8A=E4=BC=A0=E4=BA=A4=E4=BA=92=E5=8D=87=E7=BA=A7?= =?UTF-8?q?=E3=80=81=E8=A7=86=E9=A2=91=E6=A8=A1=E5=9E=8B=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E5=99=A8=E5=9B=BE=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 【电商克隆 - 商品图上传交互重构】 - 新增上传预览大图区(clone-ai-upload-preview-wrap),点击缩略图可切换预览 - 选中缩略图增加 is-active 绿色边框高亮 - 预览区显示商品图编号 + 尺寸/比例/格式信息(formatProductImageSpec) - 上传区到达 7 张上限时显示"已达上限"、阻止拖拽上传、输入框禁用 - 上传图片自动异步读取尺寸(width/height),无需等待上传完成即可展示 - 已上传素材区重构为列表头(标题+计数)+ 缩略图栈式布局 - 缩略图增加序号角标(1-7),删除按钮独立于缩略图下方 - selectedProductImageId 状态自动管理:删除/新增时自动切换到有效图片 【工作台 - 视频模型选择器图标】 - 新增 VIDEO_MODEL_ICON_URLS 映射(HappyHorse/Pixverse/Vidu/Wan/Kling) - SelectChip 组件在 chipId=video-model 时显示模型品牌图标 - getVideoModelIconUrl 支持中英文模糊匹配 【样式】 - ecommerce.css: 预览区/素材栈/缩略图选中态/上限态完整样式 - dark-green.css: 主题层微调 --- src/features/ecommerce/EcommercePage.tsx | 129 ++++++-- .../workbench/WorkbenchSelectChips.tsx | 26 +- src/styles/pages/ecommerce.css | 288 +++++++++++++++++- src/styles/themes/dark-green.css | 34 +++ 4 files changed, 450 insertions(+), 27 deletions(-) diff --git a/src/features/ecommerce/EcommercePage.tsx b/src/features/ecommerce/EcommercePage.tsx index 6f9a3ab..093f1bc 100644 --- a/src/features/ecommerce/EcommercePage.tsx +++ b/src/features/ecommerce/EcommercePage.tsx @@ -523,6 +523,12 @@ const formatUploadedImageRatio = (image?: CloneImageItem) => { if (!image.width || !image.height) return `上传图片\u00a0\u00a0\u00a0原图比例${format}`; return `上传图片 ${image.width}×${image.height}px\u00a0\u00a0\u00a0${formatAspectRatio(image.width, image.height)}${format}`; }; +const formatProductImageSpec = (image?: CloneImageItem | null) => { + if (!image) return "等待上传"; + const format = image.format ? ` · ${image.format}` : ""; + if (!image.width || !image.height) return `正在识别尺寸${format}`; + return `${image.width}×${image.height}px · ${formatAspectRatio(image.width, image.height)}${format}`; +}; const defaultMarketLanguageOption = marketLanguageOptions[0]!; const normalizeMarket = (value: string) => marketLanguageOptions.some((option) => option.country === value) ? value : defaultMarketLanguageOption.country; @@ -778,6 +784,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const [selectedProductSetPreview, setSelectedProductSetPreview] = useState<{ src: string; label: string } | null>(null); const [showHostingModal, setShowHostingModal] = useState(false); const [productImages, setProductImages] = useState([]); + const [selectedProductImageId, setSelectedProductImageId] = useState(null); const [isProductUploadDragging, setIsProductUploadDragging] = useState(false); const [cloneOutput, setCloneOutput] = useState("detail"); const [openCloneBasicSelect, setOpenCloneBasicSelect] = useState(null); @@ -862,6 +869,13 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const selectedProductSetOutput = productSetOutputOptions.find((option) => option.key === productSetOutput) ?? productSetOutputOptions[0]!; const selectedCloneOutput = cloneOutputOptions.find((option) => option.key === cloneOutput) ?? cloneOutputOptions[1]!; + const selectedProductImage = productImages.find((image) => image.id === selectedProductImageId) ?? productImages[0] ?? null; + const selectedProductImageIndex = selectedProductImage + ? productImages.findIndex((image) => image.id === selectedProductImage.id) + : -1; + const selectedProductImageLabel = selectedProductImageIndex >= 0 ? `商品图 ${selectedProductImageIndex + 1}` : "商品图"; + const selectedProductImageSpec = formatProductImageSpec(selectedProductImage); + const isProductImageLimitReached = productImages.length >= maxCloneProductImages; const productSetPreviewReady = productSetStatus === "done"; const cloneSetTotal = Object.values(cloneSetCounts).reduce((sum, value) => sum + value, 0); const canGenerateSet = setImages.length > 0 && productSetStatus !== "generating"; @@ -890,6 +904,30 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { }); }; + useEffect(() => { + if (!productImages.length) { + if (selectedProductImageId !== null) setSelectedProductImageId(null); + return; + } + if (!selectedProductImageId || !productImages.some((image) => image.id === selectedProductImageId)) { + setSelectedProductImageId(productImages[0].id); + } + }, [productImages, selectedProductImageId]); + + useEffect(() => { + productImages + .filter((item) => !item.width || !item.height) + .forEach((item) => { + readImageDimensions(item.src) + .then(({ width, height }) => { + setProductImages((current) => + current.map((currentItem) => (currentItem.id === item.id ? { ...currentItem, width, height } : currentItem)), + ); + }) + .catch(() => undefined); + }); + }, [productImages]); + const addSetImages = (files: File[]) => { if (setImages.length >= 3) return; const imageFiles = files.filter((file) => file.type.startsWith("image/")); @@ -945,6 +983,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const handleProductDrop = (event: DragEvent) => { event.preventDefault(); setIsProductUploadDragging(false); + if (isProductImageLimitReached) return; const files = Array.from(event.dataTransfer.files); if (files.length) addProductImages(files); }; @@ -1815,6 +1854,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { setSelectedProductSetPreview(null); setShowHostingModal(false); setProductImages([]); + setSelectedProductImageId(null); setIsProductUploadDragging(false); setCloneOutput("detail"); setRatio((current) => normalizeRatioForPlatform(platform, current, "detail")); @@ -2061,18 +2101,24 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
productInputRef.current?.click()} + tabIndex={isProductImageLimitReached ? -1 : 0} + aria-disabled={isProductImageLimitReached} + className={`clone-ai-upload-zone${isProductUploadDragging ? " is-dragging" : ""}${isProductImageLimitReached ? " is-full" : ""}`} + onClick={() => { + if (isProductImageLimitReached) return; + productInputRef.current?.click(); + }} onKeyDown={(event) => { if (event.target !== event.currentTarget) return; if (event.key === "Enter" || event.key === " ") { event.preventDefault(); + if (isProductImageLimitReached) return; productInputRef.current?.click(); } }} onDragEnter={(event) => { event.preventDefault(); + if (isProductImageLimitReached) return; setIsProductUploadDragging(true); }} onDragOver={(event) => event.preventDefault()} @@ -2085,35 +2131,68 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { 拖拽或点击上传 - - 上传图片 + {isProductImageLimitReached ? ( + "已达上限" + ) : ( + <> + + 上传图片 + + )} 同一产品,最多 7 张
{productImages.length ? ( -
- {productImages.map((item) => ( -
- {item.name} - - -
- ))} +
event.stopPropagation()} aria-live="polite"> +
+ {`当前预览:${selectedProductImageLabel}`} +
+
+ + {selectedProductImageLabel} + {selectedProductImageSpec} + +
+
+ ) : null} + {productImages.length ? ( +
+
+ 已上传素材 + {productImages.length}/{maxCloneProductImages} +
+
+ {productImages.map((item, index) => ( +
+ + +
+ ))} +
) : null}
- +
diff --git a/src/features/workbench/WorkbenchSelectChips.tsx b/src/features/workbench/WorkbenchSelectChips.tsx index 593b185..6987c69 100644 --- a/src/features/workbench/WorkbenchSelectChips.tsx +++ b/src/features/workbench/WorkbenchSelectChips.tsx @@ -3,6 +3,24 @@ import type { ReactNode } from "react"; import type { WorkbenchOption, WorkbenchFieldGroup } from "./workbenchConstants"; import { getRatioOptionClassName, getSettingsGridColumnsClassName } from "./workbenchReferenceUtils"; +const VIDEO_MODEL_ICON_URLS = { + happyHorse: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/static/model-icons/HappyHorse.svg", + pixverse: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/static/model-icons/Pixverse.svg", + vidu: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/static/model-icons/viduQ3.svg", + wanxiang: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/static/model-icons/wan.svg", + kling: "https://stringtest.oss-cn-hangzhou.aliyuncs.com/static/model-icons/kling.svg", +} as const; + +function getVideoModelIconUrl(option: WorkbenchOption): string | null { + const text = `${option.value} ${option.label}`.toLowerCase(); + if (text.includes("happyhorse")) return VIDEO_MODEL_ICON_URLS.happyHorse; + if (text.includes("pixverse")) return VIDEO_MODEL_ICON_URLS.pixverse; + if (text.includes("vidu")) return VIDEO_MODEL_ICON_URLS.vidu; + if (text.includes("wan") || text.includes("万相")) return VIDEO_MODEL_ICON_URLS.wanxiang; + if (text.includes("kling") || text.includes("可灵")) return VIDEO_MODEL_ICON_URLS.kling; + return null; +} + export function SelectChip({ chipId, value, @@ -56,6 +74,7 @@ export function SelectChip({ > {options.map((option, index) => { const active = option.value === value; + const iconUrl = chipId === "video-model" ? getVideoModelIconUrl(option) : null; return ( -
+
{accountPanel === "credits" ? ( <> - + 当前账号 {displayName} - + 积分剩余 {(usage.balanceCents / 100).toFixed(2)} ) : ( <> - - 任务总数 - {tasks.length} + + 任务概览 + {tasks.length} 个任务 - + 已完成 {completedTasks.length} diff --git a/src/styles/themes/dark-green.css b/src/styles/themes/dark-green.css index f50f9d3..bd52151 100644 --- a/src/styles/themes/dark-green.css +++ b/src/styles/themes/dark-green.css @@ -7017,6 +7017,75 @@ white-space: nowrap; } +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 10px; + align-items: stretch; + min-width: 0; + padding: 10px; + border: 1px solid rgba(255, 255, 255, 0.055); + border-radius: 11px; + background: + linear-gradient(135deg, rgba(var(--accent-rgb), 0.055), transparent 62%), + rgba(255, 255, 255, 0.022); +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary-main, +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary-metric { + display: grid; + min-width: 0; + align-content: center; +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary-main { + gap: 3px; +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary-metric { + min-width: 86px; + justify-items: end; + padding-left: 10px; + border-left: 1px solid rgba(255, 255, 255, 0.06); + text-align: right; +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary small { + overflow: hidden; + color: rgba(225, 235, 231, 0.52); + font-size: 10px; + font-weight: 800; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary strong { + overflow: hidden; + color: var(--fg); + font-size: 16px; + font-weight: 850; + line-height: 1.25; + text-overflow: ellipsis; + white-space: nowrap; +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary-metric strong { + color: var(--accent); + font-variant-numeric: tabular-nums; +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary em { + overflow: hidden; + color: rgba(225, 235, 231, 0.42); + font-size: 10px; + font-style: normal; + font-weight: 650; + line-height: 1.35; + text-overflow: ellipsis; + white-space: nowrap; +} + .web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-card .profile-page__upload-card--meta { grid-template-columns: 1fr; gap: 8px; @@ -7526,6 +7595,93 @@ } } +/* Profile center: align with the ecommerce generator's flatter dark-green tone. */ +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard { + background: var(--bg-base); +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__banner { + background: var(--bg-panel); + box-shadow: none; +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__banner.has-image { + background-color: var(--bg-panel); + background-blend-mode: luminosity; +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__banner-overlay { + background: + linear-gradient(180deg, rgba(13, 15, 15, 0.7), rgba(13, 15, 15, 0.88)), + linear-gradient(90deg, rgba(13, 15, 15, 0.62), rgba(13, 15, 15, 0.28) 44%, rgba(13, 15, 15, 0.68)); +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__sidebar, +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-card, +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__main-tabs, +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__section, +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__list-card, +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__review-item, +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__empty-state, +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__upload-card { + border-color: var(--border-weak); + background: var(--bg-panel); + box-shadow: none; +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__main-tabs, +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-card .profile-page__list-tabs { + background: var(--bg-inset); +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__main-tabs button, +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-card .profile-page__list-tabs button { + border: 1px solid transparent; + background: transparent; + color: var(--fg-soft); + box-shadow: none; +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__main-tabs button.is-active, +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-card .profile-page__list-tabs button.is-active { + border-color: rgba(var(--accent-rgb), 0.42); + background: rgba(var(--accent-rgb), 0.12); + color: var(--accent); +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__bio-display, +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__bio, +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__count, +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-summary, +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__account-card .profile-page__meta-item { + border-color: rgba(255, 255, 255, 0.065); + background: rgba(255, 255, 255, 0.024); + box-shadow: none; +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__list-card-preview, +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__list-card-preview:not(.has-media) { + border-color: rgba(255, 255, 255, 0.065); + background: rgba(255, 255, 255, 0.024); +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__list-card-placeholder { + border-color: rgba(var(--accent-rgb), 0.2); + background: rgba(var(--accent-rgb), 0.08); + color: var(--accent); +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__media-badge { + background: rgba(8, 14, 12, 0.78); + box-shadow: none; +} + +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__share-btn, +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__avatar, +.web-shell[data-ui-theme="dark-green"] .profile-page--dashboard .profile-page__avatar-ring .profile-page__avatar { + box-shadow: none; +} + /* Ecommerce generation page: keep its carousel and composer independent from the community carousel rules that share class names. */ .web-shell[data-ui-theme="dark-green"] .ecommerce-landing-page {