@@ -15,6 +15,7 @@
SettingOutlined ,
SkinOutlined ,
TableOutlined ,
ThunderboltOutlined ,
} from "@ant-design/icons" ;
import { useEffect , useMemo , useRef , useState , type CSSProperties , type ChangeEvent , type DragEvent , type KeyboardEvent as ReactKeyboardEvent , type MouseEvent as ReactMouseEvent , type PointerEvent as ReactPointerEvent , type ReactNode } from "react" ;
import { useTypewriter } from "../../hooks/useTypewriter" ;
@@ -324,8 +325,8 @@ const platformSpecOptions: Array<{
} > = [
{
label : "淘宝/天猫" ,
ratios : [ "娣樺疂涓诲浘 / SKU 鍥?800脳800px" , "璇︽儏椤靛 750px" , "璇︽儏椤靛 790px" ] ,
defaultRatio : "娣樺疂涓诲浘 / SKU 鍥?800脳800px" ,
ratios : [ "淘宝主图 / SKU 鍥?800脳800px" , "详情页宽 750px" , "详情页宽 790px" ] ,
defaultRatio : "淘宝主图 / SKU 鍥?800脳800px" ,
ratioGroups : {
set : {
ratios : [ "1000脳1000px\u00a0\u00a0\u00a01锛?" , "800脳800px\u00a0\u00a0\u00a01锛?" ] ,
@@ -353,13 +354,13 @@ const platformSpecOptions: Array<{
defaultRatio : "800脳800px\u00a0\u00a0\u00a01锛?" ,
} ,
} ,
specs : [ "涓诲浘 / SKU 鍥?800脳800px锛屸墹3MB" , "璇︽儏椤靛 750px 鎴 ?790px锛屽崟寮犻珮鈮?546px" ] ,
tip : "寤鸿涓诲浘 200-400KB JPG锛岃秴杩?500KB 浼氬奖鍝嶅姞杞介€熷害銆?" ,
specs : [ "主图 / SKU 鍥?800脳800px锛屸墹3MB" , "详情页宽 750px 或 ?790px锛屽崟寮犻珮鈮?546px" ] ,
tip : "建议主图 200-400KB JPG锛岃秴杩?500KB 浼氬奖鍝嶅姞杞介€熷害銆?" ,
} ,
{
label : "京东" ,
ratios : [ "浜笢涓诲浘 / SKU 鍥?800脳800px" , "璇︽儏椤靛 750px" , "棣栧浘涓讳綋鍗犳瘮 鈮?0%" ] ,
defaultRatio : "浜笢涓诲浘 / SKU 鍥?800脳800px" ,
ratios : [ "京东主图 / SKU 鍥?800脳800px" , "详情页宽 750px" , "首图主体占比 鈮?0%" ] ,
defaultRatio : "京东主图 / SKU 鍥?800脳800px" ,
ratioGroups : {
set : {
ratios : [ "1000脳1000px\u00a0\u00a0\u00a01锛?" ] ,
@@ -387,12 +388,12 @@ const platformSpecOptions: Array<{
defaultRatio : "800脳800px\u00a0\u00a0\u00a01锛?" ,
} ,
} ,
specs : [ "涓诲浘 / SKU 鍥?800脳800px锛岀櫧搴曪紝鈮?MB" , "璇︽儏椤靛 750px锛岄鍥句富浣撳崰姣?鈮?0%" ] ,
specs : [ "主图 / SKU 鍥?800脳800px锛岀櫧搴曪紝鈮?MB" , "详情页宽 750px锛岄鍥句富浣撳崰姣?鈮?0%" ] ,
} ,
{
label : "拼多多" ,
ratios : [ "涓诲浘 750脳352px" , "涓诲浘 800脳800px" , "璇︽儏椤靛 750px" ] ,
defaultRatio : "涓诲浘 750脳352px" ,
ratios : [ "主图 750脳352px" , "主图 800脳800px" , "详情页宽 750px" ] ,
defaultRatio : "主图 750脳352px" ,
ratioGroups : {
set : {
ratios : [ "800脳800px\u00a0\u00a0\u00a01锛?" , "750脳1000px\u00a0\u00a0\u00a03锛?" ] ,
@@ -415,7 +416,7 @@ const platformSpecOptions: Array<{
defaultRatio : "800脳800px\u00a0\u00a0\u00a01锛?" ,
} ,
} ,
specs : [ "涓诲浘 750脳352px 鎴 ?800脳800px锛屸墹1MB" , "璇︽儏椤靛 750px锛岃姹傜函鐧藉簳銆佹棤姘村嵃銆佹棤鎷兼帴 " ] ,
specs : [ "主图 750脳352px 或 ?800脳800px锛屸墹1MB" , "详情页宽 750px,要求纯白底、无水印、无拼接 " ] ,
} ,
{
label : "抖音电商" ,
@@ -431,12 +432,12 @@ const platformSpecOptions: Array<{
defaultRatio : "800脳800px\u00a0\u00a0\u00a01锛?" ,
} ,
} ,
specs : [ "鐭棰?1080脳1920px锛?:16" , "30s 鍐 呮渶浣?" ] ,
specs : [ "鐭棰?1080脳1920px锛?:16" , "30s 图 呮渶浣?" ] ,
} ,
{
label : "亚马逊 Amazon" ,
ratios : [ "涓诲浘 鈮?600脳1600px" , "寤鸿 2000脳2000px+" , "鏈€灏?500脳500px" ] ,
defaultRatio : "涓诲浘 鈮?600脳1600px" ,
ratios : [ "主图 鈮?600脳1600px" , "建议 2000脳2000px+" , "鏈€灏?500脳500px" ] ,
defaultRatio : "主图 鈮?600脳1600px" ,
ratioGroups : {
set : {
ratios : [ "1600脳1600px\u00a0\u00a0\u00a01锛?" ] ,
@@ -459,13 +460,13 @@ const platformSpecOptions: Array<{
defaultRatio : "1600脳1600px\u00a0\u00a0\u00a01锛?" ,
} ,
} ,
specs : [ "涓诲浘 1600脳1600px+锛岀函鐧藉簳锛屸墹 10MB" , "鏈€灏?500脳500px锛屽缓璁?2000px+ 浠ユ敮鎸佺缉鏀?" ] ,
specs : [ "主图 1600脳1600px+,纯白底,≤ 10MB" , "鏈€灏?500脳500px锛屽缓璁?2000px+ 浠ユ敮鎸佺缉鏀?" ] ,
aliases : [ "浜氶┈閫?" ] ,
} ,
{
label : "Shopee" ,
ratios : [ "鍟嗗搧涓诲浘 1024脳1024px" , "鍩虹涓诲浘 800脳800px" ] ,
defaultRatio : "鍟嗗搧涓诲浘 1024脳1024px" ,
ratios : [ "商品主图 1024脳1024px" , "基础主图 800脳800px" ] ,
defaultRatio : "商品主图 1024脳1024px" ,
ratioGroups : {
set : {
ratios : [ "800脳800px\u00a0\u00a0\u00a01锛?" ] ,
@@ -488,13 +489,13 @@ const platformSpecOptions: Array<{
defaultRatio : "800脳800px\u00a0\u00a0\u00a01锛?" ,
} ,
} ,
specs : [ "鍟嗗搧涓诲浘鎺ㄨ崘 1024脳1024px锛屽熀纭€ 800脳800px" , "鈮?MB锛岀櫧搴曟垨娴呰壊搴?" ] ,
aliases : [ "铏剧毊 Shopee/Lazada" , "铏剧毊 " ] ,
specs : [ "商品主图推荐 1024脳1024px,基础 800脳800px" , "鈮?MB锛岀櫧搴曟垨娴呰壊搴?" ] ,
aliases : [ "虾皮 Shopee/Lazada" , "虾皮 " ] ,
} ,
{
label : "Lazada" ,
ratios : [ "鍟嗗搧涓诲浘 800脳800px" ] ,
defaultRatio : "鍟嗗搧涓诲浘 800脳800px" ,
ratios : [ "商品主图 800脳800px" ] ,
defaultRatio : "商品主图 800脳800px" ,
ratioGroups : {
set : {
ratios : [ "800脳800px\u00a0\u00a0\u00a01锛?" ] ,
@@ -517,12 +518,12 @@ const platformSpecOptions: Array<{
defaultRatio : "800脳800px\u00a0\u00a0\u00a01锛?" ,
} ,
} ,
specs : [ "鍟嗗搧涓诲浘 800脳800px锛?:1" ] ,
specs : [ "商品主图 800脳800px锛?:1" ] ,
} ,
{
label : "Instagram" ,
ratios : [ "甯栧瓙 1080脳1350px" , "甯栧瓙 1080脳1080px" , "Stories / Reels 1080脳1920px" , "澶村儚 320脳320px" ] ,
defaultRatio : "甯栧瓙 1080脳1350px" ,
ratios : [ "帖子 1080脳1350px" , "帖子 1080脳1080px" , "Stories / Reels 1080脳1920px" , "头像 320脳320px" ] ,
defaultRatio : "帖子 1080脳1350px" ,
ratioGroups : {
set : {
ratios : [ "1080脳1080px\u00a0\u00a0\u00a01锛?" , "1080脳1350px\u00a0\u00a0\u00a04锛?" ] ,
@@ -541,14 +542,14 @@ const platformSpecOptions: Array<{
defaultRatio : "1080脳1920px\u00a0\u00a0\u00a09锛?6" ,
} ,
} ,
specs : [ "甯栧瓙 1080脳1350px 鎴 ?1080脳1080px" , "Stories / Reels 灏侀潰 1080脳1920px锛屽ご鍍?320脳320px" ] ,
tip : "寤鸿 鈮?MB JPG銆?" ,
specs : [ "帖子 1080脳1350px 或 ?1080脳1080px" , "Stories / Reels 封面 1080脳1920px锛屽ご鍍?320脳320px" ] ,
tip : "建议 鈮?MB JPG銆?" ,
aliases : [ "Instagram Reels" ] ,
} ,
{
label : "速卖通" ,
ratios : [ "涓诲浘 800脳800px" , "涓诲浘 1000脳1000px+" ] ,
defaultRatio : "涓诲浘 800脳800px" ,
ratios : [ "主图 800脳800px" , "主图 1000脳1000px+" ] ,
defaultRatio : "主图 800脳800px" ,
ratioGroups : {
set : {
ratios : [ "1000脳1000px\u00a0\u00a0\u00a01锛?" ] ,
@@ -571,11 +572,11 @@ const platformSpecOptions: Array<{
defaultRatio : "800脳800px\u00a0\u00a0\u00a01锛?" ,
} ,
} ,
specs : [ "涓诲浘寤鸿 800脳800px 鎴栨洿楂橈紝 1:1" , "閫傚悎璺ㄥ鐢靛晢涓诲浘銆丼 KU 鍥惧拰鍦烘櫙鍥?" ] ,
specs : [ "主图建议 800脳800px 或更高, 1:1" , "适合跨境电商主图、S KU 鍥惧拰鍦烘櫙鍥?" ] ,
} ,
{
label : "eBay" ,
ratios : [ "鍟嗗搧鍥?1:1" , "鐧藉簳澶氳搴﹀睍绀哄浘 1:1" ] ,
ratios : [ "鍟嗗搧鍥?1:1" , "白底多角度展示图 1:1" ] ,
defaultRatio : "鍟嗗搧鍥?1:1" ,
ratioGroups : {
set : {
@@ -599,12 +600,12 @@ const platformSpecOptions: Array<{
defaultRatio : "1600脳1600px\u00a0\u00a0\u00a01锛?" ,
} ,
} ,
specs : [ "鍟嗗搧鍥惧缓璁?1:1锛屼富浣撴竻鏅板眳涓?" , "閫傚悎鐧藉簳涓诲浘 鍜屽瑙掑害灞曠ず鍥?" ] ,
specs : [ "鍟嗗搧鍥惧缓璁?1:1锛屼富浣撴竻鏅板眳涓?" , "閫傚悎白底主图 鍜屽瑙掑害灞曠ず鍥?" ] ,
} ,
{
label : "TikTok Shop" ,
ratios : [ "鍟嗗搧涓诲浘 1:1" , "鐭棰?/ 绔栫増灏侀潰 9:16" ] ,
defaultRatio : "鍟嗗搧涓诲浘 1:1" ,
ratios : [ "商品主图 1:1" , "鐭棰?/ 竖版封面 9:16" ] ,
defaultRatio : "商品主图 1:1" ,
ratioGroups : {
set : {
ratios : [ "1280脳1280px\u00a0\u00a0\u00a01锛?" ] ,
@@ -627,16 +628,16 @@ const platformSpecOptions: Array<{
defaultRatio : "800脳800px\u00a0\u00a0\u00a01锛?" ,
} ,
} ,
specs : [ "鍟嗗搧涓诲浘寤鸿 1:1" , "鐭棰?绔栫増灏侀潰寤鸿 9:16" ] ,
specs : [ "商品主图建议 1:1" , "鐭棰?竖版封面建议 9:16" ] ,
} ,
] ;
const platformOptions = platformSpecOptions . map ( ( option ) = > option . label ) ;
const getPlatformLogoSources = ( value : string ) = > {
const normalized = value . toLowerCase ( ) ;
if ( value . includes ( "淘宝" ) || value . includes ( "天猫" ) || value . includes ( "娣樺疂 " ) || value . includes ( "澶╃尗 " ) ) return [ taobaoLogo , tmallLogo ] ;
if ( value . includes ( "京东" ) || value . includes ( "浜笢 " ) ) return [ jdLogo ] ;
if ( value . includes ( "淘宝" ) || value . includes ( "天猫" ) || value . includes ( "淘宝 " ) || value . includes ( "天猫 " ) ) return [ taobaoLogo , tmallLogo ] ;
if ( value . includes ( "京东" ) || value . includes ( "京东 " ) ) return [ jdLogo ] ;
if ( value . includes ( "拼多多" ) || value . includes ( "鎷煎澶" ) ) return [ pinduoduoLogo ] ;
if ( value . includes ( "抖音" ) || value . includes ( "鎶栭煶 " ) ) return [ douyinLogo ] ;
if ( value . includes ( "抖音" ) || value . includes ( "抖音 " ) ) return [ douyinLogo ] ;
if ( normalized . includes ( "amazon" ) ) return [ amazonLogo ] ;
if ( normalized . includes ( "shopee" ) ) return [ shopeeLogo ] ;
if ( normalized . includes ( "lazada" ) ) return [ lazadaLogo ] ;
@@ -686,34 +687,34 @@ const marketLanguageOptions: Array<{ country: string; languages: string[] }> = [
const marketOptions = marketLanguageOptions . map ( ( option ) = > option . country ) ;
const languageOptions = Array . from ( new Set ( marketLanguageOptions . flatMap ( ( option ) = > option . languages ) ) ) ;
const languageAliases : Record < string , string > = {
"鑻辨枃 " : "英文" ,
"涓枃 " : "中文" ,
"鑻辫 " : "英文" ,
"鏃ヨ " : "日文" ,
"鏃ユ枃 " : "日文" ,
"寰疯 " : "德文" ,
"寰锋枃 " : "德文" ,
"娉曡 " : "法文" ,
"娉曟枃 " : "法文" ,
"闊╄ " : "韩文" ,
"闊╂枃 " : "韩文" ,
"瑗挎枃 " : "西班牙语" ,
"瑗跨彮鐗欒 " : "西班牙语" ,
"钁℃枃 " : "葡萄牙语" ,
"钁¤悇鐗欒 " : "葡萄牙语" ,
"英文 " : "英文" ,
"中文 " : "中文" ,
"英语 " : "英文" ,
"日语 " : "日文" ,
"日文 " : "日文" ,
"德语 " : "德文" ,
"德文 " : "德文" ,
"法语 " : "法文" ,
"法文 " : "法文" ,
"韩语 " : "韩文" ,
"韩文 " : "韩文" ,
"西文 " : "西班牙语" ,
"西班牙语 " : "西班牙语" ,
"葡文 " : "葡萄牙语" ,
"葡萄牙语 " : "葡萄牙语" ,
"鍗板凹璇?" : "印度尼西亚语" ,
"鍗板害灏艰タ浜氳 " : "印度尼西亚语" ,
"鑿插緥瀹捐 " : "菲律宾语(他加禄语)" ,
"鑿插緥瀹捐锛堜粬鍔犵璇級 " : "菲律宾语(他加禄语)" ,
"印度尼西亚语 " : "印度尼西亚语" ,
"菲律宾语 " : "菲律宾语(他加禄语)" ,
"菲律宾语(他加禄语) " : "菲律宾语(他加禄语)" ,
} ;
const defaultPlatformSpec = platformSpecOptions [ 0 ] ! ;
const getPlatformSpec = ( value : string ) = >
platformSpecOptions . find ( ( option ) = > option . label === value || option . aliases ? . includes ( value ) ) ? ? defaultPlatformSpec ;
const legacyPlatformAliases : Record < string , string > = {
"娣樺疂/澶╃尗 " : "淘宝/天猫" ,
"浜笢 " : "京东" ,
"淘宝/天猫 " : "淘宝/天猫" ,
"京东 " : "京东" ,
"鎷煎澶?" : "拼多多" ,
"鎶栭煶鐢靛晢 " : "抖音电商" ,
"抖音电商 " : "抖音电商" ,
"浜氶┈閫?Amazon" : "亚马逊 Amazon" ,
"閫熷崠閫?" : "速卖通" ,
} ;
@@ -731,7 +732,7 @@ const getPlatformRatioGroup = (value: string, mode?: PlatformRatioModeKey): Plat
const getPlatformRatioOptions = ( value : string , mode? : PlatformRatioModeKey ) = > getPlatformRatioGroup ( value , mode ) . ratios ;
const getPlatformDefaultRatio = ( value : string , mode? : PlatformRatioModeKey ) = > getPlatformRatioGroup ( value , mode ) . defaultRatio ;
const getUniqueRatioOptions = ( ratios : string [ ] ) = > Array . from ( new Set ( ratios ) ) ;
const normalizeRatioToken = ( value : string ) = > value . replaceAll ( "锛?" , ":" ) . replaceAll ( " : ", ":" ) . replaceAll ( "脳 " , "× " ) . trim ( ) ;
const normalizeRatioToken = ( value : string ) = > value . replaceAll ( ": " , ":" ) . replaceAll ( "× " , "× " ) . trim ( ) ;
const normalizeRatioForPlatform = ( platformValue : string , ratioValue : string , mode? : PlatformRatioModeKey ) = > {
const platformRatios = getPlatformRatioOptions ( platformValue , mode ) ;
if ( platformRatios . includes ( ratioValue ) ) return ratioValue ;
@@ -739,7 +740,7 @@ const normalizeRatioForPlatform = (platformValue: string, ratioValue: string, mo
const matchedRatio = platformRatios . find ( ( option ) = > normalizeRatioToken ( option ) . includes ( normalizedRatio ) ) ;
return matchedRatio ? ? getPlatformDefaultRatio ( platformValue , mode ) ;
} ;
const quickSetRatioOptions = [ "1:1" , "3:4" , "9:16" , "16:9" ] ;
const quickSetRatioOptions = [ "1:1" , "3:4" , "4:3" , "9:16" , "16:9" ] ;
const getQuickSetRatioValue = ( value : string ) = > {
const normalizedValue = normalizeRatioToken ( value ) ;
if ( quickSetRatioOptions . includes ( normalizedValue ) ) return normalizedValue ;
@@ -768,12 +769,12 @@ const formatRatioDisplayValue = (value: string) => {
return ` ${ width } × ${ height } px \ u00a0 \ u00a0 \ u00a0 ${ formatAspectRatio ( width , height ) } ` ;
}
return normalizedValue
. replace ( "娣樺疂涓诲浘 / SKU 鍥?" , "淘宝主图 / SKU 图 " )
. replace ( "浜笢涓诲浘 / SKU 鍥?" , "京东主图 / SKU 图 " )
. replace ( "璇︽儏椤靛 " , "详情页宽" )
. replace ( "淘宝主图 / SKU 鍥?" , "淘宝主图 / SKU 图 " )
. replace ( "京东主图 / SKU 鍥?" , "京东主图 / SKU 图 " )
. replace ( "详情页宽 " , "详情页宽" )
. replace ( "鐭棰?" , "短视频" )
. replace ( "涓诲浘 " , "主图" )
. replace ( "鍟嗗搧涓诲浘 " , "商品主图" )
. replace ( "主图 " , "主图" )
. replace ( "商品主图 " , "商品主图" )
. replace ( "鍟嗗搧鍥?" , "商品图" ) ;
} ;
/** Extract CSS aspect-ratio from a ratio string like "1000x1000px 1:1" -> "1 / 1" */
@@ -893,29 +894,29 @@ const sampleResults = [
] ;
const productSetAssets = ossAssets . ecommerce . productSet ;
const productSetPreviewCards = [
{ id : "main" , label : "01 涓诲浘 (鐧藉簳/鍚堣 )" , src : productSetAssets.main } ,
{ id : "scene" , label : "02 鍦烘櫙灞曠ず " , src : productSetAssets.scene } ,
{ id : "main" , label : "01 主图 (白底/合规 )" , src : productSetAssets.main } ,
{ id : "scene" , label : "02 场景展示 " , src : productSetAssets.scene } ,
{ id : "model" , label : "03 妯$壒鍦烘櫙鍥?" , src : productSetAssets.model } ,
{ id : "detail" , label : "04 缁嗚妭璇存槑 " , src : productSetAssets.detail } ,
{ id : "selling" , label : "05 鍗栫偣璇﹁В " , src : productSetAssets.selling } ,
{ id : "detail" , label : "04 细节说明 " , src : productSetAssets.detail } ,
{ id : "selling" , label : "05 卖点详解 " , src : productSetAssets.selling } ,
] ;
const tryOnAssets = ossAssets . ecommerce . tryOn ;
const tryOnCards = [
{
title : "澶氫欢娣锋惌鑷姩铻嶅悎 " ,
title : "多件混搭自动融合 " ,
tone : "red" ,
inputs : [ tryOnAssets . dressA , tryOnAssets . dressB , tryOnAssets . modelWoman ] ,
results : [ tryOnAssets . tryA , tryOnAssets . tryB ] ,
} ,
{
title : "涓€浠朵篃鑳藉嚭澶х墖 " ,
title : "一件也能出大片 " ,
tone : "brown" ,
inputs : [ tryOnAssets . jacket , tryOnAssets . modelMan ] ,
results : [ tryOnAssets . jacketResultA , tryOnAssets . jacketResultB ] ,
} ,
{
title : "闉嬪附楗板搧瀹岀編閫傞厤 " ,
title : "鞋帽饰品完美适配 " ,
tone : "gold" ,
inputs : [ tryOnAssets . hat , tryOnAssets . modelAsian ] ,
results : [ tryOnAssets . hatResultA , tryOnAssets . hatResultB ] ,
@@ -968,7 +969,7 @@ const blobToDataUrl = (blob: Blob): Promise<string> =>
new Promise ( ( resolve , reject ) = > {
const reader = new FileReader ( ) ;
reader . onload = ( ) = > resolve ( String ( reader . result || "" ) ) ;
reader . onerror = ( ) = > reject ( reader . error || new Error ( "鏂囦欢璇诲彇澶辫触 " ) ) ;
reader . onerror = ( ) = > reject ( reader . error || new Error ( "文件读取失败 " ) ) ;
reader . readAsDataURL ( blob ) ;
} ) ;
@@ -1218,6 +1219,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const [ showHostingModal , setShowHostingModal ] = useState ( false ) ;
const [ productImages , setProductImages ] = useState < CloneImageItem [ ] > ( [ ] ) ;
const [ activeQuickTool , setActiveQuickTool ] = useState < "cutout" | "set" | "detail" | "watermark" | "image-edit" | "hot-video" | null > ( null ) ;
const [ quickPageTransition , setQuickPageTransition ] = useState < { title : string ; subtitle : string } | null > ( null ) ;
const quickPageTransitionTimeoutRef = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
const [ smartCutoutImage , setSmartCutoutImage ] = useState < SmartCutoutImageItem | null > ( null ) ;
const [ smartCutoutBatchImages , setSmartCutoutBatchImages ] = useState < SmartCutoutImageItem [ ] > ( [ ] ) ;
const [ smartCutoutBackgroundColor , setSmartCutoutBackgroundColor ] = useState ( "#ffffff" ) ;
@@ -1477,7 +1480,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const [ requirement , setRequirement ] = useState ( "" ) ;
const [ requirementImageMentionQuery , setRequirementImageMentionQuery ] = useState < string | null > ( null ) ;
const [ cloneSettingName , setCloneSettingName ] = useState ( "鏂板缓鍒涗綔 " ) ;
const [ cloneSettingName , setCloneSettingName ] = useState ( "新建创作 " ) ;
const [ platform , setPlatform ] = useState ( defaultEcommercePlatform ) ;
const [ market , setMarket ] = useState ( marketOptions [ 0 ] ) ;
const [ language , setLanguage ] = useState ( getPlatformDefaultLanguage ( defaultEcommercePlatform , marketOptions [ 0 ] ) ) ;
@@ -1681,6 +1684,24 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
items . forEach ( revokeSmartCutoutItem ) ;
} ;
const clearQuickPageTransition = ( ) = > {
if ( quickPageTransitionTimeoutRef . current !== null ) {
window . clearTimeout ( quickPageTransitionTimeoutRef . current ) ;
quickPageTransitionTimeoutRef . current = null ;
}
setQuickPageTransition ( null ) ;
} ;
const runQuickPageTransition = ( message : { title : string ; subtitle : string } , action : ( ) = > void , delay = 400 ) = > {
clearQuickPageTransition ( ) ;
setQuickPageTransition ( message ) ;
quickPageTransitionTimeoutRef . current = window . setTimeout ( ( ) = > {
quickPageTransitionTimeoutRef . current = null ;
action ( ) ;
setQuickPageTransition ( null ) ;
} , delay ) ;
} ;
const clearSmartCutoutTransition = ( ) = > {
if ( smartCutoutTransitionTimeoutRef . current !== null ) {
window . clearTimeout ( smartCutoutTransitionTimeoutRef . current ) ;
@@ -2393,7 +2414,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
setStatus ( "ready" ) ;
setResults ( [ ] ) ;
}
if ( unsupportedCount > 0 ) toast . info ( "浠呮敮鎸佷笂浼犲浘鐗囨垨瑙嗛鏂囦欢 " ) ;
if ( unsupportedCount > 0 ) toast . info ( "仅支持上传图片或视频文件 " ) ;
} ;
const handleComposerAssetUpload = ( event : ChangeEvent < HTMLInputElement > ) = > {
@@ -2453,7 +2474,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
} ) ;
hydrateCloneReferenceImageMeta ( nextImages ) ;
} catch ( err ) {
toast . error ( err instanceof Error ? err . message : "鍙傝€冨浘涓婁紶澶辫触 " ) ;
toast . error ( err instanceof Error ? err . message : "参考图上传失败 " ) ;
}
} ;
@@ -2561,7 +2582,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const normalizedPlatform = normalizePlatform ( nextPlatform ) ;
setPlatform ( normalizedPlatform ) ;
setRatio ( ( current ) = >
cloneOutput === "hot" && current . startsWith ( "涓婁紶鍥剧墖 " ) && hotUploadedRatioOption
cloneOutput === "hot" && current . startsWith ( "上传图片 " ) && hotUploadedRatioOption
? hotUploadedRatioOption
: normalizeRatioForPlatform ( normalizedPlatform , current , cloneOutput ) ,
) ;
@@ -2571,7 +2592,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const handleCloneOutputChange = ( nextOutput : CloneOutputKey ) = > {
setCloneOutput ( nextOutput ) ;
setRatio ( ( current ) = >
nextOutput === "hot" && current . startsWith ( "涓婁紶鍥剧墖 " ) && hotUploadedRatioOption
nextOutput === "hot" && current . startsWith ( "上传图片 " ) && hotUploadedRatioOption
? hotUploadedRatioOption
: normalizeRatioForPlatform ( platform , current , nextOutput ) ,
) ;
@@ -2717,7 +2738,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
setRatio ( ( current ) = > {
const platformRatios = getPlatformRatioOptions ( platform , cloneOutput ) ;
const availableRatios = hotUploadedRatioOption ? getUniqueRatioOptions ( [ . . . platformRatios , hotUploadedRatioOption ] ) : platformRatios ;
if ( current . startsWith ( "涓婁紶鍥剧墖 " ) && hotUploadedRatioOption ) return hotUploadedRatioOption ;
if ( current . startsWith ( "上传图片 " ) && hotUploadedRatioOption ) return hotUploadedRatioOption ;
if ( availableRatios . includes ( current ) ) return current ;
const normalizedRatio = normalizeRatioToken ( current ) ;
const matchedRatio = availableRatios . find ( ( option ) = > normalizeRatioToken ( option ) . includes ( normalizedRatio ) ) ;
@@ -2923,7 +2944,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
new Promise ( ( resolve , reject ) = > {
const reader = new FileReader ( ) ;
reader . onload = ( ) = > resolve ( String ( reader . result || "" ) ) ;
reader . onerror = ( ) = > reject ( reader . error || new Error ( "鏂囦欢璇诲彇澶辫触 " ) ) ;
reader . onerror = ( ) = > reject ( reader . error || new Error ( "文件读取失败 " ) ) ;
reader . readAsDataURL ( blob ) ;
} ) ;
@@ -2948,7 +2969,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
for ( const item of images ) {
try {
if ( ! item . file && item . src . startsWith ( "blob:" ) ) {
throw new Error ( "鏈湴棰勮鍥剧己灏戝師濮嬫枃浠讹紝鏃犳硶涓婁紶 " ) ;
throw new Error ( "本地预览图缺少原始文件,无法上传 " ) ;
}
const rawBlob = item . file ? ? ( item . src . startsWith ( "data:" ) ? null : await ( await fetch ( item . src ) ) . blob ( ) ) ;
const mimeType = normalizeEcommerceImageMime (
@@ -2969,7 +2990,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const setCountLabels : Record < CloneSetCountKey , { label : string ; promptDesc : string } > = {
selling : { label : "鍗栫偣鍥?" , promptDesc : "selling-point infographic image highlighting core product advantages and detail close-ups" } ,
white : { label : "鐧藉簳 鍥?" , promptDesc : "clean white-background product photo showing the item from its best angle, studio lighting, no props" } ,
white : { label : "白底 鍥?" , promptDesc : "clean white-background product photo showing the item from its best angle, studio lighting, no props" } ,
scene : { label : "鍦烘櫙鍥?" , promptDesc : "lifestyle scene image showing the product in a realistic usage environment with natural surroundings" } ,
} ;
@@ -3000,10 +3021,10 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
parts . push ( "The output must be a selling-point graphic with clear hierarchy, concise copy, and product detail callouts." ) ;
}
if ( totalCount > 1 ) {
parts . push ( ` This is variant ${ index + 1 } of ${ totalCount } 鈥? vary the angle, composition, or emphasis to make each distinct. ` ) ;
parts . push ( ` This is variant ${ index + 1 } of ${ totalCount } — vary the angle, composition, or emphasis to make each distinct. ` ) ;
}
parts . push ( ` Platform: ${ pPlatform } . Aspect ratio: ${ pRatio } . Language/copy: ${ pLanguage } . Market: ${ pMarket } . ` ) ;
parts . push ( "Must comply with platform image guidelines 鈥? proper margins, no watermark, professional quality." ) ;
parts . push ( "Must comply with platform image guidelines — proper margins, no watermark, professional quality." ) ;
return parts . join ( " " ) ;
} ;
@@ -3018,7 +3039,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
parts . push ( "Create a high-impact first-screen visual that combines the product photo with key selling points, usage scenes, and detailed specifications in a cohesive layout." ) ;
parts . push ( ` Platform: ${ pPlatform } . Aspect ratio: ${ pRatio } . Language/copy: ${ pLanguage } . Market: ${ pMarket } . ` ) ;
if ( outputKey === "detail" && tryOnOptions ? . detailModules ) parts . push ( buildDetailModulePrompt ( tryOnOptions . detailModules ) ) ;
parts . push ( "Follow platform A+ page best practices 鈥? clear hierarchy, professional typography, high visual impact." ) ;
parts . push ( "Follow platform A+ page best practices — clear hierarchy, professional typography, high visual impact." ) ;
} else if ( outputKey === "model" ) {
parts . push ( "Generate model/try-on lifestyle images for an e-commerce product listing." ) ;
parts . push ( "Show the product being used or worn by a model in attractive lifestyle settings." ) ;
@@ -3110,7 +3131,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
imageGen . updateTask ( storeId , { status : "completed" , progress : 100 , resultUrl : persistedUrl } ) ;
} else {
generatedUrls . push ( "" ) ;
imageGen . updateTask ( storeId , { status : "failed" , error : "鐢熸垚 鏈繑鍥炵粨鏋?" } ) ;
imageGen . updateTask ( storeId , { status : "failed" , error : "生成 鏈繑鍥炵粨鏋?" } ) ;
}
}
}
@@ -3128,9 +3149,9 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
}
if ( err instanceof ServerRequestError && err . status === 402 ) {
setResultFn ( [ ] ) ;
toast . error ( "浣欓涓嶈冻锛岃鍏呭€煎悗缁х画 " ) ;
toast . error ( "余额不足,请充值后继续 " ) ;
} else {
const msg = err instanceof Error ? err . message : "鐢熸垚澶辫触 " ;
const msg = err instanceof Error ? err . message : "生成失败 " ;
toast . error ( msg ) ;
}
setStatusFn ( "failed" ) ;
@@ -3198,7 +3219,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
imageGen . updateTask ( storeId , { status : "completed" , progress : 100 , resultUrl : persistedUrl } ) ;
} else {
statusFn ? . ( "idle" ) ;
imageGen . updateTask ( storeId , { status : "failed" , error : "鐢熸垚 鏈繑鍥炵粨鏋?" } ) ;
imageGen . updateTask ( storeId , { status : "failed" , error : "生成 鏈繑鍥炵粨鏋?" } ) ;
}
} catch ( err ) {
if ( imageAbortRef . current . current ) {
@@ -3206,10 +3227,10 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
return ;
}
if ( err instanceof ServerRequestError && err . status === 402 ) {
resultFn ? . ( [ { id : ` ecommerce-error-402 ` , src : "" , label : "浣欓涓嶈冻锛岃鍏呭€煎悗缁х画 " } ] ) ;
toast . error ( "浣欓涓嶈冻锛岃鍏呭€煎悗缁х画 " ) ;
resultFn ? . ( [ { id : ` ecommerce-error-402 ` , src : "" , label : "余额不足,请充值后继续 " } ] ) ;
toast . error ( "余额不足,请充值后继续 " ) ;
} else {
const msg = err instanceof Error ? err . message : "鐢熸垚澶辫触 " ;
const msg = err instanceof Error ? err . message : "生成失败 " ;
toast . error ( msg ) ;
}
statusFn ? . ( "failed" ) ;
@@ -3223,7 +3244,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const readAsDataUrl = ( file : File ) : Promise < string > = > new Promise ( ( resolve , reject ) = > {
const reader = new FileReader ( ) ;
reader . onload = ( ) = > resolve ( reader . result as string ) ;
reader . onerror = ( ) = > reject ( new Error ( "鏂囦欢璇诲彇澶辫触 " ) ) ;
reader . onerror = ( ) = > reject ( new Error ( "文件读取失败 " ) ) ;
reader . readAsDataURL ( file ) ;
} ) ;
@@ -3262,7 +3283,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
return ;
}
if ( resultUrl ) {
setResults ( [ { id : crypto.randomUUID ( ) , src : resultUrl , label : "鎹㈣瑙嗛 " } ] ) ;
setResults ( [ { id : crypto.randomUUID ( ) , src : resultUrl , label : "换装视频 " } ] ) ;
}
setStatus ( "done" ) ;
} catch ( err ) {
@@ -3271,7 +3292,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
return ;
}
setStatus ( "failed" ) ;
toast . error ( err instanceof Error ? err . message : "瑙嗛鎹㈣鐢熸垚澶辫触 " ) ;
toast . error ( err instanceof Error ? err . message : "视频换装生成失败 " ) ;
}
} ;
@@ -3279,7 +3300,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
if ( ! canGenerate ) return ;
if ( ( appUsage ? . balanceCents ? ? 0 ) <= 0 ) {
toast . error ( "绉垎涓嶈冻锛岃鍏呭€煎悗缁х画 " ) ;
toast . error ( "积分不足,请充值后继续 " ) ;
return ;
}
@@ -3395,7 +3416,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const handleDetailAiWrite = ( ) = > {
setDetailRequirement (
"1.浜у搧鍚嶇О锛氭棤绾块檷鍣摑鐗欒€虫満 \n2.鏍稿績鍗栫偣锛氫富鍔ㄩ檷鍣€?4H缁埅銆佷綆寤惰繜杩炴帴銆佽垝閫備僵鎴碶n3.閫傜敤浜虹兢锛氶€氬嫟銆佸姙鍏€佽繍鍔ㄥ拰鏃呰鐢ㄦ埛\n4.鏈熸湜鍦烘櫙锛氬湴閾侀€氬嫟銆佸眳瀹跺姙鍏€佹埛澶栬繍鍔╘ n5.鍏蜂綋鍙傛暟 锛氳摑鐗?.3銆両PX4闃叉按銆佸揩鍏?0鍒嗛挓浣跨敤2灏忔椂 " ,
"1.产品名称:无线降噪蓝牙耳机 \n2.鏍稿績鍗栫偣锛氫富鍔ㄩ檷鍣€?4H续航、低延迟连接、舒适佩戴\n3.适用人群:通勤、办公、运动和旅行用户\n4.期望场景:地铁通勤、居家办公、户外运动\ n5.鍏蜂綋参数 锛氳摑鐗?.3銆両PX4闃叉按銆佸揩鍏?0分钟使用2小时 " ,
) ;
} ;
@@ -3447,7 +3468,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
setCloneReferenceImages ( [ ] ) ;
setCloneReplicateLevel ( "high" ) ;
setRequirement ( "" ) ;
setCloneSettingName ( "鏂板缓鍒涗綔 " ) ;
setCloneSettingName ( "新建创作 " ) ;
setResults ( [ ] ) ;
setStatus ( "idle" ) ;
setGarmentImages ( [ ] ) ;
@@ -3475,19 +3496,19 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const isWatermarkTool = isCloneTool && activeQuickTool === "watermark" ;
const isImageEditTool = isCloneTool && activeQuickTool === "image-edit" ;
const isHotVideoTool = isCloneTool && activeQuickTool === "hot-video" ;
const pageLabel = isSetTool ? "鍟嗗搧濂楀浘 " : isDetail ? "A+/璇︽儏椤?" : isTryOn ? "AI鏈嶉グ绌挎埓 " : activeToolMeta ? . label || "鍟嗗搧宸ュ叿 " ;
const pageLabel = isSetTool ? "商品套图 " : isDetail ? "A+/璇︽儏椤?" : isTryOn ? "AI服饰穿戴 " : activeToolMeta ? . label || "商品工具 " ;
const setPrimaryLabel =
setImages . length === 0
? "璇峰厛涓婁紶鍟嗗搧鍘熷浘 "
? "请先上传商品原图 "
: productSetStatus === "generating"
? "鐢熸垚 涓?.."
: "鐢熸垚 " + selectedProductSetOutput . label ;
? "生成 涓?.."
: "生成 " + selectedProductSetOutput . label ;
const tryOnPrimaryLabel =
garmentImages . length === 0 ? "璇峰厛涓婁紶鏈嶈鍥剧墖 " : tryOnStatus === "generating" ? "鐢熸垚 涓?.." : "鐢熸垚鏈嶉グ绌挎埓 鍥?" ;
garmentImages . length === 0 ? "请先上传服装图片 " : tryOnStatus === "generating" ? "生成 涓?.." : "生成服饰穿戴 鍥?" ;
const detailPrimaryLabel =
detailProductImages . length === 0 ? "璇蜂笂浼犱骇鍝佸浘 " : detailStatus === "generating" ? "鐢熸垚 涓?.." : "鐢熸垚 A+璇︽儏椤?" ;
detailProductImages . length === 0 ? "请上传产品图 " : detailStatus === "generating" ? "生成 涓?.." : "生成 A+璇︽儏椤?" ;
const clonePrimaryLabel =
productImages . length === 0 ? "璇峰厛涓婁紶鍟嗗搧鍘熷浘 " : status === "generating" ? "鐢熸垚 涓?.." : "鐢熸垚 " + selectedCloneOutput . label ;
productImages . length === 0 ? "请先上传商品原图 " : status === "generating" ? "生成 涓?.." : "生成 " + selectedCloneOutput . label ;
const setPreviewCards : CloneResult [ ] = [ ] ;
let setIndex = 0 ;
for ( const countKey of cloneSetCountKeys ) {
@@ -3522,7 +3543,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
cloneOutput === "set"
? productSetResultImages
. filter ( Boolean )
. map ( ( src , index ) = > ( { id : "history-set-" + String ( index ) , src , label : clonePreviewCards [ index ] ? . label || "濂楀浘 " + String ( index + 1 ) } ) )
. map ( ( src , index ) = > ( { id : "history-set-" + String ( index ) , src , label : clonePreviewCards [ index ] ? . label || "套图 " + String ( index + 1 ) } ) )
: results . filter ( ( item ) = > item . src ) ;
const buildHistorySignature = ( output : CloneOutputKey , prompt : string , historyResults : CloneResult [ ] , sourceImages : CloneImageItem [ ] ) = >
@@ -3538,7 +3559,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const minute = 60 * 1000 ;
const hour = 60 * minute ;
const day = 24 * hour ;
if ( diff < minute ) return "鍒氬垰 " ;
if ( diff < minute ) return "刚刚 " ;
if ( diff < hour ) return String ( Math . floor ( diff / minute ) ) + " 分钟前" ;
if ( diff < day ) return String ( Math . floor ( diff / hour ) ) + " 小时前" ;
return String ( Math . floor ( diff / day ) ) + " 天前" ;
@@ -3551,7 +3572,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
if ( lastSavedHistorySignatureRef . current === signature && activeHistoryRecordId ) return activeHistoryRecordId ;
const createdAt = Date . now ( ) ;
const outputLabel = cloneOutputOptions . find ( ( option ) = > option . key === cloneOutput ) ? . label || "鐢熸垚璁板綍 " ;
const outputLabel = cloneOutputOptions . find ( ( option ) = > option . key === cloneOutput ) ? . label || "生成记录 " ;
const title = requirement . trim ( ) || outputLabel + " " + new Date ( createdAt ) . toLocaleTimeString ( "zh-CN" , { hour : "2-digit" , minute : "2-digit" } ) ;
const record : EcommerceHistoryRecord = {
id : crypto.randomUUID ( ) ,
@@ -3935,23 +3956,23 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
< section className = "product-set-empty-preview" aria-live = "polite" >
{ productSetStatus === "generating" ? < LoadingOutlined / > : < FileImageOutlined / > }
< strong > { productSetStatus === "generating" ? "正在生成" : "等待生成" } < / strong >
{ productSetStatus === "generating" ? < EcommerceProgressBar status = "generating" label = "商品套图" / > : null }
{ productSetStatus === "generating" ? < EcommerceProgressBar status = "generating" onCancel = { handleCancelGenerate } label = "商品套图" / > : null }
< span > { productSetStatus === "generating" ? "AI 正在整理主图、场景、细节与卖点图。" : "上传商品原图并填写信息后,AI 将为您生成专业的电商商品图。" } < / span >
< / section >
) }
{ productSetStatus === "done" ? < p className = "product-set-generated-note" > 已 生 成 { selectedProductSetOutput . label } 预 览 < / p > : null }
< section className = "product-set-floating-detail" aria-label = "淇℃伅璇︽儏 " >
< section className = "product-set-floating-detail" aria-label = "信息详情 " >
< div className = "product-set-floating-detail__head" >
< strong > 淇 ℃ 伅 璇 ︽ 儏 < / strong >
< strong > 信 息 详 情 < / strong >
< span > { productSetRequirement . length } / 500 < / span >
< / div >
< textarea
value = { productSetRequirement }
onChange = { ( event ) = > setProductSetRequirement ( event . target . value ) }
maxLength = { 500 }
placeholder = "寤鸿 鍖呭惈浠ヤ笅淇℃伅锛氫骇鍝佸悕绉般€佹牳蹇冨崠鐐广€佹湡鏈涘満鏅€佸叿浣撳弬鏁?"
placeholder = "建议 鍖呭惈浠ヤ笅淇℃伅锛氫骇鍝佸悕绉般€佹牳蹇冨崠鐐广€佹湡鏈涘満鏅€佸叿浣撳弬鏁?"
/ >
< button type = "button" className = "product-set-floating-submit" disabled = { ! canGenerateSet } onClick = { handleSetGenerate } >
{ productSetStatus === "generating" ? < LoadingOutlined / > : null }
@@ -3964,7 +3985,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
) : null }
< / section >
< button type = "button" className = "product-clone-help" aria-label = "甯姪 " >
< button type = "button" className = "product-clone-help" aria-label = "帮助 " >
< QuestionCircleOutlined / >
< / button >
< / main >
@@ -4197,7 +4218,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
{ cloneOutput === "video" ? (
< >
< section className = "clone-ai-flow-pipeline" aria-label = "生成流程" >
{ /* Source Node 鈥?鍘熷浘绱犳潗 */ }
{ /* Source Node —原图素材 */ }
< div className = "clone-ai-flow-source" >
< div className = "clone-ai-flow-node clone-ai-flow-node--source" >
{ productImages [ 0 ] ? . src ? (
@@ -4211,7 +4232,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
< span className = "clone-ai-flow-node__label" > 附 件 原 图 < / span >
< / div >
{ /* Connector 鈥? 鍒嗘敮杩炴帴绾?*/ }
{ /* Connector — 鍒嗘敮杩炴帴绾?*/ }
< div className = "clone-ai-flow-connector" aria-hidden = "true" >
< div className = "clone-ai-flow-connector__trunk" / >
< div className = "clone-ai-flow-connector__branches" >
@@ -4221,7 +4242,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
< / div >
< / div >
{ /* Branches 鈥?鐢熸垚璺緞鍒嗘敮 */ }
{ /* Branches —生成路径分支 */ }
{ status === "done" ? (
< div className = "clone-ai-flow-branches" >
{ results [ 0 ] ? . src ? (
@@ -4284,14 +4305,14 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
) }
< / section >
{ /* Status Overlay 鈥?鐢熸垚鐘舵€佽鐩栧眰 */ }
{ /* Status Overlay —生成状态覆盖层 */ }
{ status === "generating" || status === "failed" ? (
< section className = "clone-ai-flow-status" aria-live = "polite" >
{ status === "generating" ? (
< >
< LoadingOutlined style = { { fontSize : 28 } } / >
< strong > 正 在 生 成 < / strong >
< EcommerceProgressBar status = "generating" label = { ` ${ selectedCloneOutput . label } 生成 ` } / >
< EcommerceProgressBar status = "generating" onCancel = { handleCancelGenerate } label = { ` ${ selectedCloneOutput . label } 生成 ` } / >
< span > AI 正 在 为 { platform } / { market } 整 理 { selectedCloneOutput . label } 。 < / span >
< / >
) : status === "failed" ? (
@@ -4301,7 +4322,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
< span > 请 检 查 网 络 后 点 击 下 方 重 试 < / span >
{ lastFailedActionRef . current ? (
< button type = "button" className = "clone-ai-retry-btn" onClick = { lastFailedActionRef . current } >
< ReloadOutlined / > 閲 嶈 瘯
< ReloadOutlined / > 重 试
< / button >
) : null }
< / >
@@ -4348,7 +4369,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
< section className = "clone-ai-empty-state" aria-live = "polite" >
{ status === "generating" ? < LoadingOutlined / > : status === "failed" ? < FrownOutlined / > : < FileImageOutlined / > }
< strong > { status === "generating" ? "正在生成" : status === "failed" ? "生成失败" : "等待生成" } < / strong >
{ status === "generating" ? < EcommerceProgressBar status = "generating" label = { ` ${ selectedCloneOutput . label } 生成 ` } / > : null }
{ status === "generating" ? < EcommerceProgressBar status = "generating" onCancel = { handleCancelGenerate } label = { ` ${ selectedCloneOutput . label } 生成 ` } / > : null }
< span >
{ status === "generating"
? "AI 正在为 " + platform + " / " + market + " 整理" + selectedCloneOutput . label + "。"
@@ -4526,14 +4547,14 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
{ label : "智能抠图" , icon : < FileImageOutlined / > , onClick : openSmartCutoutUpload } ,
{ label : "商品套图" , icon : < AppstoreOutlined / > , onClick : openQuickProductSetPage } ,
{ label : "图片修改" , icon : < SettingOutlined / > , onClick : openImageWorkbenchPage } ,
{ label : "增/ 去水印" , icon : < FileImageOutlined / > , onClick : openWatermarkRemovalPage } ,
{ label : "去除 水印" , icon : < FileImageOutlined / > , onClick : openWatermarkRemovalPage } ,
{ label : "图片批处理" , icon : < FolderOpenOutlined / > } ,
{ label : "一键翻译" , icon : < FileImageOutlined / > } ,
{ label : "A+/详情页" , icon : < FileImageOutlined / > , onClick : openQuickDetailPage } ,
{ label : "变清晰" , icon : < FileImageOutlined / > } ,
{ label : "AI消除" , icon : < SettingOutlined / > } ,
{ label : "证件照" , icon : < SkinOutlined / > } ,
{ label : "爆款 视频" , icon : < CloudUploadOutlined / > , onClick : openHotVideoPage } ,
{ label : "广告 视频" , icon : < CloudUploadOutlined / > , onClick : openHotVideoPage } ,
{ label : "拼图" , icon : < TableOutlined / > } ,
] . map ( ( item ) = > (
< button
@@ -5238,13 +5259,13 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
) ;
const hotVideoPreview = (
< main className = "ecom-hot-video-page" aria-label = "爆款 视频" >
< main className = "ecom-hot-video-page" aria-label = "广告 视频" >
< nav className = "ecom-hot-video-nav" >
< button type = "button" className = "ecom-hot-video-back" onClick = { closeHotVideoPage } >
‹ 返 回 首 页
< / button >
< div className = "ecom-hot-video-nav-title" >
< h1 > 爆 款 视 频 < / h1 >
< h1 > 广 告 视 频 < / h1 >
< span > AI智能策划 · 一 键 生 成 电 商 短 视 频 < / span >
< / div >
< div className = "ecom-hot-video-nav-meta" >
@@ -5406,6 +5427,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
< div className = "ecom-quick-set-body" >
< aside className = "ecom-quick-set-panel" aria-label = "商品套图设置" onWheel = { handleQuickPanelWheel } >
< header className = "ecom-quick-set-panel-head" >
< strong className = "ecom-quick-set-page-title" > 商 品 套 图 < / strong >
< button type = "button" className = "ecom-quick-set-back" onClick = { ( ) = > setActiveQuickTool ( null ) } >
首 页
< / button >
@@ -5563,6 +5585,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
< div className = "ecom-quick-set-body" >
< aside className = "ecom-quick-set-panel" aria-label = "A+详情页设置" onWheel = { handleQuickPanelWheel } >
< header className = "ecom-quick-set-panel-head" >
< strong className = "ecom-quick-set-page-title" > A + / 详 情 < / s t r o n g >
< button type = "button" className = "ecom-quick-set-back" onClick = { ( ) = > setActiveQuickTool ( null ) } >
首 页
< / button >
@@ -5820,6 +5843,20 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
< / main >
) ;
const quickPageSidebar = ( isQuickSetTool || isQuickDetailTool || isHotVideoTool ) ? (
< aside className = "ecom-quick-page-sidebar" aria-label = "快捷页面切换" >
< button type = "button" className = { isQuickSetTool ? "is-active" : "" } onClick = { openQuickProductSetPage } title = "商品套图" aria-label = "商品套图" >
< AppstoreOutlined / > < span > 商 品 套 图 < / span >
< / button >
< button type = "button" className = { isQuickDetailTool ? "is-active" : "" } onClick = { openQuickDetailPage } title = "A+/详情" aria-label = "A+/详情" >
< FileImageOutlined / > < span > A + / 详 情 < / s p a n >
< / button >
< button type = "button" className = { isHotVideoTool ? "is-active" : "" } onClick = { openHotVideoPage } title = "广告视频" aria-label = "广告视频" >
< ThunderboltOutlined / > < span > 广 告 视 频 < / span >
< / button >
< / aside >
) : null ;
const activePreview = isSetTool
? setPreview
: isDetail
@@ -5870,7 +5907,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
id = { isCloneTool ? "ecommerce-clone-settings-panel" : undefined }
className = { ` product-clone-panel tool-panel-enter ` }
key = { activeTool }
aria - label = { ` ${ pageLabel } 鍙傛暟 ` }
aria - label = { ` ${ pageLabel } 参数 ` }
aria - hidden = { isCloneTool && isCloneSettingsCollapsed ? true : undefined }
>
{ isSetTool ? setPanel : isDetail ? detailPanel : isTryOn ? tryOnPanel : isCloneTool ? clonePanel : placeholderPanel }
@@ -5881,10 +5918,10 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
type = "button"
className = "clone-ai-settings-toggle"
onClick = { ( ) = > setIsCloneSettingsCollapsed ( ( current ) = > ! current ) }
aria - label = { isCloneSettingsCollapsed ? "灞曞紑璁剧疆闈㈡澘" : "鏀惰捣璁剧疆闈㈡澘 "}
aria - label = { isCloneSettingsCollapsed ? "展开设置面板" : "收起设置面板 "}
aria - controls = "ecommerce-clone-settings-panel"
aria - expanded = { ! isCloneSettingsCollapsed }
title = { isCloneSettingsCollapsed ? "灞曞紑璁剧疆闈㈡澘" : "鏀惰捣璁剧疆闈㈡澘 "}
title = { isCloneSettingsCollapsed ? "展开设置面板" : "收起设置面板 "}
>
{ isCloneSettingsCollapsed ? < MenuUnfoldOutlined / > : < MenuFoldOutlined / > }
< / button >