pb-sxp-ui 1.20.43 → 1.20.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2224,7 +2224,7 @@ const EditorCore = React.forwardRef(({ children, resolver, isSsr, schema, enable
2224
2224
  * @returns isVisible 是否可见
2225
2225
  */
2226
2226
  function useAdvancedOnScreen(ref, options = {}) {
2227
- const { threshold = 0, delay = 0, onVisible, onHidden } = options;
2227
+ const { threshold = 0, delay = 0, onVisible, onHidden, name = 'unknown' } = options;
2228
2228
  const observerRef = React.useRef(null);
2229
2229
  const [isVisible, setIsVisible] = React.useState(false);
2230
2230
  const timerRef = React.useRef(null);
@@ -2250,7 +2250,7 @@ function useAdvancedOnScreen(ref, options = {}) {
2250
2250
  var _a, _b;
2251
2251
  const isIntersecting = entry.isIntersecting;
2252
2252
  const intersectionRatio = entry.intersectionRatio;
2253
- console.log('[useAdvancedOnScreen] IntersectionObserver callback:', {
2253
+ console.log(`[useAdvancedOnScreen ${name}] IntersectionObserver callback:`, {
2254
2254
  isIntersecting,
2255
2255
  threshold,
2256
2256
  delay,
@@ -2262,18 +2262,18 @@ function useAdvancedOnScreen(ref, options = {}) {
2262
2262
  if (isIntersecting && intersectionRatio >= threshold) {
2263
2263
  // 元素进入可视区域且达到阈值
2264
2264
  setIsVisible(true);
2265
- // 如果已经在等待中,不要重复设置定时器
2265
+ // 如果已经在等待中,清除旧的定时器,重新开始计时
2266
2266
  if (isWaitingRef.current) {
2267
- console.log('[useAdvancedOnScreen] 已在等待中,跳过');
2268
- return;
2267
+ console.log(`[useAdvancedOnScreen ${name}] 已在等待中,清除旧定时器并重新计时`);
2268
+ clearTimer();
2269
2269
  }
2270
2270
  // 如果设置了延迟时间
2271
2271
  if (delay > 0) {
2272
- console.log(`[useAdvancedOnScreen] 设置延迟定时器: ${delay}ms`);
2272
+ console.log(`[useAdvancedOnScreen ${name}] 设置延迟定时器: ${delay}ms`);
2273
2273
  isWaitingRef.current = true;
2274
2274
  timerRef.current = setTimeout(() => {
2275
2275
  var _a;
2276
- console.log('[useAdvancedOnScreen] 延迟时间到,触发 onVisible');
2276
+ console.log(`[useAdvancedOnScreen ${name}] 延迟时间到,触发 onVisible`);
2277
2277
  isWaitingRef.current = false;
2278
2278
  timerRef.current = null;
2279
2279
  (_a = onVisibleRef.current) === null || _a === void 0 ? void 0 : _a.call(onVisibleRef);
@@ -2281,13 +2281,13 @@ function useAdvancedOnScreen(ref, options = {}) {
2281
2281
  }
2282
2282
  else {
2283
2283
  // 没有延迟,立即触发
2284
- console.log('[useAdvancedOnScreen] 无延迟,立即触发 onVisible');
2284
+ console.log(`[useAdvancedOnScreen ${name}] 无延迟,立即触发 onVisible`);
2285
2285
  (_a = onVisibleRef.current) === null || _a === void 0 ? void 0 : _a.call(onVisibleRef);
2286
2286
  }
2287
2287
  }
2288
2288
  else if (!isIntersecting || intersectionRatio < threshold) {
2289
2289
  // 元素离开可视区域或未达到阈值
2290
- console.log('[useAdvancedOnScreen] 元素离开可视区域或未达到阈值,清除定时器');
2290
+ console.log(`[useAdvancedOnScreen ${name}] 元素离开可视区域或未达到阈值,清除定时器`);
2291
2291
  setIsVisible(false);
2292
2292
  clearTimer();
2293
2293
  (_b = onHiddenRef.current) === null || _b === void 0 ? void 0 : _b.call(onHiddenRef);
@@ -19611,45 +19611,71 @@ const CTAButton = ({ ctaData, style, onClick, onExposure }) => {
19611
19611
  });
19612
19612
  return (React.createElement("button", { ref: buttonRef, style: style, onClick: onClick }, ctaData.title));
19613
19613
  };
19614
- const ProductView = ({ children, onExposure }) => {
19614
+ const ProductView = ({ children, onExposure, onLeave }) => {
19615
19615
  const productRef = React.useRef(null);
19616
19616
  // 使用高级曝光检测:2/3区域 + 1000ms延迟
19617
19617
  useAdvancedOnScreen(productRef, {
19618
19618
  threshold: 0.67, // 2/3 区域可见才开始计时
19619
19619
  delay: 1000,
19620
19620
  onVisible: () => {
19621
- // 每次进入2/3可视区域并停留1秒后都触发上报
19621
+ // 每次进入2/3可视区域并停留1秒后记录开始浏览时间
19622
19622
  onExposure();
19623
+ },
19624
+ onHidden: () => {
19625
+ // 离开可视区域时上报productView事件
19626
+ onLeave === null || onLeave === void 0 ? void 0 : onLeave();
19623
19627
  }
19624
19628
  });
19625
19629
  return (React.createElement("div", { ref: productRef, style: { width: '100%' } }, children));
19626
19630
  };
19627
- const PostView = ({ children, videoRef, isVideo, onExposure, onVideoAutoPlay }) => {
19631
+ const PostView = ({ children, videoRef, isVideo, onExposure, autoPlayTriggerRef, onVideoPlay, onVideoPause }) => {
19628
19632
  const postRef = React.useRef(null);
19629
19633
  // 使用高级曝光检测:2/3区域(threshold=0.67)+ 1000ms延迟
19630
19634
  useAdvancedOnScreen(postRef, {
19631
19635
  threshold: 0.67,
19632
19636
  delay: 1000,
19633
19637
  onVisible: () => {
19638
+ console.log('[PostView onVisible] 触发,isVideo:', isVideo, 'hasVideoRef:', !!(videoRef === null || videoRef === void 0 ? void 0 : videoRef.current));
19634
19639
  // 每次进入2/3区域并停留1秒后都触发曝光上报
19635
19640
  if (onExposure) {
19636
19641
  onExposure();
19637
19642
  }
19638
19643
  // 如果是视频,自动播放
19639
19644
  if (isVideo && (videoRef === null || videoRef === void 0 ? void 0 : videoRef.current)) {
19640
- videoRef.current.play().catch(err => {
19645
+ console.log('[PostView onVisible] 准备播放视频');
19646
+ // 标记这次播放是自动触发的
19647
+ if (autoPlayTriggerRef) {
19648
+ autoPlayTriggerRef.current = true;
19649
+ console.log('[PostView onVisible] 已设置自动播放标记');
19650
+ }
19651
+ videoRef.current.play().then(() => {
19652
+ console.log('[PostView onVisible] 视频 play() 成功');
19653
+ // 触发播放回调,更新暂停图标状态
19654
+ if (onVideoPlay) {
19655
+ onVideoPlay();
19656
+ }
19657
+ }).catch(err => {
19641
19658
  console.warn('[PostView] Video autoplay failed:', err);
19659
+ // 播放失败时重置标记
19660
+ if (autoPlayTriggerRef) {
19661
+ autoPlayTriggerRef.current = false;
19662
+ }
19642
19663
  });
19643
- // 触发自动播放回调(用于上报 playVideo 事件)
19644
- onVideoAutoPlay === null || onVideoAutoPlay === void 0 ? void 0 : onVideoAutoPlay();
19645
19664
  }
19646
19665
  },
19647
19666
  onHidden: () => {
19667
+ console.log('[PostView onHidden] 触发,isVideo:', isVideo, 'hasVideoRef:', !!(videoRef === null || videoRef === void 0 ? void 0 : videoRef.current));
19648
19668
  // 如果是视频,离开2/3区域时自动暂停
19649
19669
  if (isVideo && (videoRef === null || videoRef === void 0 ? void 0 : videoRef.current)) {
19670
+ console.log('[PostView onHidden] 暂停视频');
19650
19671
  videoRef.current.pause();
19672
+ // 触发暂停回调,更新暂停图标状态
19673
+ if (onVideoPause) {
19674
+ onVideoPause();
19675
+ }
19651
19676
  }
19652
- }
19677
+ },
19678
+ name: 'PostView(Hero)'
19653
19679
  });
19654
19680
  return (React.createElement("div", { ref: postRef, style: { width: '100%', height: '100%' } }, children));
19655
19681
  };
@@ -20015,6 +20041,12 @@ const StructurePage = (_a) => {
20015
20041
  const isFirstPlay = React.useRef({});
20016
20042
  const videoLoadTime = React.useRef({});
20017
20043
  const videoAutoPlayReported = React.useRef(new Set()); // 追踪哪些视频已通过自动播放上报
20044
+ const videoPlayReportTime = React.useRef({}); // 追踪视频播放上报的时间戳,防止短时间内重复上报
20045
+ // 自动播放触发标记(用于区分自动播放和用户手动播放)
20046
+ const heroAutoPlayTrigger = React.useRef(false);
20047
+ const carouselAutoPlayTriggers = React.useRef({});
20048
+ // 追踪当前正在播放的 Carousel 视频索引(用于 onHidden 回调)
20049
+ const currentPlayingCarouselIndex = React.useRef(-1);
20018
20050
  // 调试日志
20019
20051
  console.log('[StructurePage] Props:', {
20020
20052
  editorMode,
@@ -20251,17 +20283,45 @@ const StructurePage = (_a) => {
20251
20283
  ctaEvent === null || ctaEvent === void 0 ? void 0 : ctaEvent(Object.assign({ eventSubject: 'ctaExposure', eventDescription: 'The cta was shown to the user' }, (viewPosition && { viewPosition })), rec, productData, sectionIndex !== undefined ? sectionIndex : 0);
20252
20284
  }, [ctaEvent, data]);
20253
20285
  // 视频播放事件处理
20254
- const handleVideoPlay = React.useCallback((videoData, viewPosition, sectionIndex, videoRef, isAutoPlay = false) => {
20255
- if (!videoRef || !videoRef.duration)
20286
+ const handleVideoPlay = React.useCallback((videoData, viewPosition, sectionIndex, videoRef) => {
20287
+ console.log('[handleVideoPlay] 调用,viewPosition:', viewPosition, 'sectionIndex:', sectionIndex, 'hasDuration:', !!(videoRef === null || videoRef === void 0 ? void 0 : videoRef.duration));
20288
+ if (!videoRef || !videoRef.duration) {
20289
+ console.log('[handleVideoPlay] 返回:没有 videoRef 或 duration');
20256
20290
  return;
20291
+ }
20257
20292
  const videoId = `${videoData === null || videoData === void 0 ? void 0 : videoData.itemId}-${viewPosition}-${sectionIndex}`;
20258
- // 如果是自动播放调用,标记为已上报
20293
+ const now = Date.now();
20294
+ // 防止短时间内重复上报(play 和 playing 事件可能连续触发)
20295
+ // 如果距离上次上报不到500ms,则跳过
20296
+ const lastReportTime = videoPlayReportTime.current[videoId];
20297
+ if (lastReportTime && (now - lastReportTime) < 500) {
20298
+ console.log('[handleVideoPlay] 跳过上报:距离上次上报时间过短', {
20299
+ timeSinceLastReport: now - lastReportTime
20300
+ });
20301
+ return;
20302
+ }
20303
+ // 检查是否是自动播放触发的
20304
+ let isAutoPlay = false;
20305
+ if (viewPosition === 'heroSection' && heroAutoPlayTrigger.current) {
20306
+ isAutoPlay = true;
20307
+ console.log('[handleVideoPlay] Hero Section 自动播放标记为 true');
20308
+ heroAutoPlayTrigger.current = false; // 重置标记
20309
+ }
20310
+ else if (viewPosition === 'carouselSection' && sectionIndex !== undefined && carouselAutoPlayTriggers.current[sectionIndex]) {
20311
+ isAutoPlay = true;
20312
+ console.log('[handleVideoPlay] Carousel Section 自动播放标记为 true, index:', sectionIndex);
20313
+ carouselAutoPlayTriggers.current[sectionIndex] = false; // 重置标记
20314
+ }
20315
+ console.log('[handleVideoPlay] isAutoPlay:', isAutoPlay, 'videoId:', videoId);
20316
+ // 如果是自动播放,标记为已上报
20259
20317
  if (isAutoPlay) {
20260
20318
  videoAutoPlayReported.current.add(videoId);
20319
+ console.log('[handleVideoPlay] 标记为已通过自动播放上报');
20261
20320
  }
20262
20321
  else {
20263
20322
  // 如果不是自动播放,但已经通过自动播放上报过,则跳过
20264
20323
  if (videoAutoPlayReported.current.has(videoId)) {
20324
+ console.log('[handleVideoPlay] 跳过上报:已经通过自动播放上报过');
20265
20325
  // 只记录播放时间,不上报
20266
20326
  videoStartTime.current[videoId] = videoRef.currentTime || 0;
20267
20327
  return;
@@ -20269,11 +20329,21 @@ const StructurePage = (_a) => {
20269
20329
  }
20270
20330
  // 记录视频开始播放时间
20271
20331
  videoStartTime.current[videoId] = videoRef.currentTime || 0;
20332
+ // 记录本次上报时间
20333
+ videoPlayReportTime.current[videoId] = now;
20272
20334
  // 判断是否首次播放
20273
20335
  const playType = isFirstPlay.current[videoId] === false ? '1' : '0';
20274
20336
  isFirstPlay.current[videoId] = false;
20275
20337
  const videoDuration = videoRef.duration.toFixed(2);
20276
20338
  const startTime = videoRef.currentTime.toFixed(2);
20339
+ console.log('[handleVideoPlay] 准备上报 playVideo 事件:', {
20340
+ videoId,
20341
+ isAutoPlay,
20342
+ playType,
20343
+ viewPosition,
20344
+ sectionIndex,
20345
+ duration: videoDuration
20346
+ });
20277
20347
  bffEventReport === null || bffEventReport === void 0 ? void 0 : bffEventReport({
20278
20348
  eventInfo: Object.assign({ eventSubject: 'playVideo', eventDescription: 'User played the video', sceneId: '', contentId: (videoData === null || videoData === void 0 ? void 0 : videoData.itemId) || '', contentName: (videoData === null || videoData === void 0 ? void 0 : videoData.title) || '', playType,
20279
20349
  startTime,
@@ -20332,20 +20402,40 @@ const StructurePage = (_a) => {
20332
20402
  }
20333
20403
  });
20334
20404
  }, [bffEventReport, data]);
20335
- // 产品浏览事件处理
20405
+ // 产品进入可视区域时记录开始浏览时间
20336
20406
  const handleProductViewExposure = React.useCallback((productData, viewPosition, sectionIndex) => {
20407
+ // 生成唯一标识
20408
+ const productId = `${productData === null || productData === void 0 ? void 0 : productData.itemId}-${viewPosition}-${sectionIndex}`;
20409
+ // 如果已经记录过开始时间,不再重复记录
20410
+ if (productViewStartTime.current[productId]) {
20411
+ return;
20412
+ }
20413
+ // 记录开始浏览时间
20414
+ productViewStartTime.current[productId] = Date.now();
20415
+ console.log('[handleProductViewExposure] 记录产品开始浏览时间:', {
20416
+ productId,
20417
+ startTime: productViewStartTime.current[productId]
20418
+ });
20419
+ }, []);
20420
+ // 产品离开可视区域时上报 productView 事件
20421
+ const handleProductViewLeave = React.useCallback((productData, viewPosition, sectionIndex) => {
20337
20422
  var _a;
20338
- // 生成唯一标识,避免重复上报
20423
+ // 生成唯一标识
20339
20424
  const productId = `${productData === null || productData === void 0 ? void 0 : productData.itemId}-${viewPosition}-${sectionIndex}`;
20340
- // 如果已经上报过,不再上报
20425
+ // 如果已经上报过,不再重复上报
20341
20426
  if (exposedProductRefs.current.has(productId)) {
20342
20427
  return;
20343
20428
  }
20344
- // 标记为已上报并记录开始浏览时间
20429
+ // 获取开始浏览时间
20430
+ const startTime = productViewStartTime.current[productId];
20431
+ if (!startTime) {
20432
+ console.warn('[handleProductViewLeave] 未找到开始浏览时间,跳过上报:', productId);
20433
+ return;
20434
+ }
20435
+ // 计算停留时长(秒)
20436
+ const timeOnSite = Math.floor((Date.now() - startTime) / 1000);
20437
+ // 标记为已上报
20345
20438
  exposedProductRefs.current.add(productId);
20346
- productViewStartTime.current[productId] = Date.now();
20347
- // 计算在页面停留时间(从产品出现到现在)
20348
- const timeOnSite = 0; // 首次曝光时为0
20349
20439
  // 根据 viewPosition 确定 fromKName
20350
20440
  let fromKName = 'productPage';
20351
20441
  if (viewPosition === 'heroSection') {
@@ -20360,6 +20450,11 @@ const StructurePage = (_a) => {
20360
20450
  else if (viewPosition === 'footerSection') {
20361
20451
  fromKName = 'pdpPage';
20362
20452
  }
20453
+ console.log('[handleProductViewLeave] 上报 productView 事件:', {
20454
+ productId,
20455
+ timeOnSite,
20456
+ fromKName
20457
+ });
20363
20458
  bffEventReport === null || bffEventReport === void 0 ? void 0 : bffEventReport({
20364
20459
  eventInfo: {
20365
20460
  eventSubject: 'productView',
@@ -20584,13 +20679,18 @@ const StructurePage = (_a) => {
20584
20679
  // 2. propMultiCTAConfig 只是样式配置,不是数据源
20585
20680
  // 3. 数据获取只应该在组件挂载时执行一次(除非 apiUrl/storyId 等关键参数变化)
20586
20681
  // Hero Section 视频自动播放
20587
- React.useEffect(() => {
20588
- var _a;
20589
- if (heroVideoRef.current && ((_a = data === null || data === void 0 ? void 0 : data.heroSection) === null || _a === void 0 ? void 0 : _a.url)) {
20590
- heroVideoRef.current.play().catch((err) => console.log('Video autoplay failed:', err));
20591
- setIsHeroVideoPaused(false);
20592
- }
20593
- }, [data === null || data === void 0 ? void 0 : data.heroSection]);
20682
+ // 注意:视频的自动播放逻辑由 PostView 组件处理(进入2/3可视区域并停留1秒后播放)
20683
+ // 移除了初始播放逻辑,避免与 PostView 冲突,确保视频加载完成后再播放
20684
+ // useEffect(() => {
20685
+ // if (heroVideoRef.current && data?.heroSection?.url) {
20686
+ // heroAutoPlayTrigger.current = true;
20687
+ // heroVideoRef.current.play().catch((err) => {
20688
+ // console.log('Video autoplay failed:', err);
20689
+ // heroAutoPlayTrigger.current = false;
20690
+ // });
20691
+ // setIsHeroVideoPaused(false);
20692
+ // }
20693
+ // }, [data?.heroSection]);
20594
20694
  // 初始化 carousel 视频暂停状态
20595
20695
  React.useEffect(() => {
20596
20696
  if (data === null || data === void 0 ? void 0 : data.carouselSection) {
@@ -20635,7 +20735,7 @@ const StructurePage = (_a) => {
20635
20735
  }, []);
20636
20736
  // Post 图片/视频曝光上报(用于 Hero Section 等非轮播的 Post)
20637
20737
  const postExposureReported = React.useRef(new Set());
20638
- const handlePostExposure = React.useCallback((postData, viewPosition, sectionIndex) => {
20738
+ React.useCallback((postData, viewPosition, sectionIndex) => {
20639
20739
  var _a;
20640
20740
  const postId = `${postData === null || postData === void 0 ? void 0 : postData.itemId}-${viewPosition}-${sectionIndex}`;
20641
20741
  // 防止重复上报
@@ -20732,26 +20832,87 @@ const StructurePage = (_a) => {
20732
20832
  // Carousel Section 的曝光检测 ref
20733
20833
  const carouselSectionRef = React.useRef(null);
20734
20834
  const hasCarouselReported = React.useRef(false);
20735
- // Carousel Section 进入可视区域时上报第一张图片的浏览事件
20835
+ // Carousel Section 进入可视区域时的回调
20836
+ const handleCarouselVisible = React.useCallback(() => {
20837
+ if (!(data === null || data === void 0 ? void 0 : data.carouselSection) || data.carouselSection.length === 0)
20838
+ return;
20839
+ const currentItem = data.carouselSection[carouselIndex];
20840
+ console.log('[Carousel onVisible] 当前索引:', carouselIndex, '是否是视频:', !!(currentItem === null || currentItem === void 0 ? void 0 : currentItem.url));
20841
+ // 只在首次进入可视区域时上报第一张图片
20842
+ if (!hasCarouselReported.current) {
20843
+ hasCarouselReported.current = true;
20844
+ // 只对图片类型上报(不包括视频)
20845
+ if (!currentItem.url) {
20846
+ handleCarouselImageStartEvent(carouselIndex);
20847
+ }
20848
+ }
20849
+ // 如果当前项是视频,自动播放
20850
+ if ((currentItem === null || currentItem === void 0 ? void 0 : currentItem.url) && carouselVideoRefs.current[carouselIndex]) {
20851
+ const currentVideo = carouselVideoRefs.current[carouselIndex];
20852
+ if (currentVideo) {
20853
+ console.log('[Carousel onVisible] 开始播放视频,索引:', carouselIndex);
20854
+ // 更新当前正在播放的视频索引
20855
+ currentPlayingCarouselIndex.current = carouselIndex;
20856
+ // 标记这次播放是自动触发的
20857
+ carouselAutoPlayTriggers.current[carouselIndex] = true;
20858
+ currentVideo.play().catch(err => {
20859
+ console.warn('[Carousel] Video autoplay failed (onVisible):', err);
20860
+ // 播放失败时重置标记
20861
+ carouselAutoPlayTriggers.current[carouselIndex] = false;
20862
+ });
20863
+ // 更新播放状态
20864
+ setCarouselVideoPausedStates(prev => {
20865
+ const newStates = [...prev];
20866
+ newStates[carouselIndex] = false;
20867
+ return newStates;
20868
+ });
20869
+ }
20870
+ }
20871
+ }, [data === null || data === void 0 ? void 0 : data.carouselSection, carouselIndex, handleCarouselImageStartEvent]);
20872
+ // Carousel Section 离开可视区域时的回调
20873
+ const handleCarouselHidden = React.useCallback(() => {
20874
+ // Carousel Section 离开可视区域时,暂停当前正在播放的视频
20875
+ // 使用 ref 来获取当前正在播放的视频索引,避免闭包问题
20876
+ const playingIndex = currentPlayingCarouselIndex.current;
20877
+ console.log('[Carousel onHidden] 被调用');
20878
+ console.log('[Carousel onHidden] 正在播放的视频索引:', playingIndex);
20879
+ console.log('[Carousel onHidden] carouselVideoRefs.current 长度:', carouselVideoRefs.current.length);
20880
+ console.log('[Carousel onHidden] carouselVideoRefs.current[playingIndex] 存在:', !!carouselVideoRefs.current[playingIndex]);
20881
+ if (playingIndex >= 0 && carouselVideoRefs.current[playingIndex]) {
20882
+ const currentVideo = carouselVideoRefs.current[playingIndex];
20883
+ if (currentVideo) {
20884
+ console.log('[Carousel onHidden] 准备暂停视频,索引:', playingIndex, '当前播放状态:', !currentVideo.paused);
20885
+ currentVideo.pause();
20886
+ console.log('[Carousel onHidden] 已调用 pause()');
20887
+ // 更新暂停状态
20888
+ setCarouselVideoPausedStates(prev => {
20889
+ const newStates = [...prev];
20890
+ newStates[playingIndex] = true;
20891
+ return newStates;
20892
+ });
20893
+ }
20894
+ else {
20895
+ console.log('[Carousel onHidden] currentVideo 为空');
20896
+ }
20897
+ }
20898
+ else {
20899
+ console.log('[Carousel onHidden] 条件不满足,不暂停视频');
20900
+ }
20901
+ }, []);
20902
+ // Carousel Section 进入可视区域时上报第一张图片的浏览事件,并自动播放视频
20736
20903
  useAdvancedOnScreen(carouselSectionRef, {
20737
20904
  threshold: 0.67,
20738
20905
  delay: 1000,
20739
- onVisible: () => {
20740
- // 只在首次进入可视区域时上报第一张图片
20741
- if (!hasCarouselReported.current && (data === null || data === void 0 ? void 0 : data.carouselSection) && data.carouselSection.length > 0) {
20742
- hasCarouselReported.current = true;
20743
- const currentItem = data.carouselSection[carouselIndex];
20744
- // 只对图片类型上报(不包括视频)
20745
- if (!currentItem.url) {
20746
- handleCarouselImageStartEvent(carouselIndex);
20747
- }
20748
- }
20749
- }
20906
+ onVisible: handleCarouselVisible,
20907
+ onHidden: handleCarouselHidden,
20908
+ name: 'Carousel'
20750
20909
  });
20751
- // Carousel 切换时自动播放/暂停视频(Post 2/3/4)
20910
+ // Carousel 切换时暂停其他视频
20911
+ // 注意:不再在切换时立即播放当前视频,而是由 PostView 组件处理(进入2/3可视区域并停留1秒后播放)
20752
20912
  React.useEffect(() => {
20753
20913
  if (!(data === null || data === void 0 ? void 0 : data.carouselSection) || data.carouselSection.length === 0)
20754
20914
  return;
20915
+ console.log('[Carousel useEffect] 切换到索引:', carouselIndex);
20755
20916
  // 暂停所有其他视频
20756
20917
  carouselVideoRefs.current.forEach((videoRef, index) => {
20757
20918
  if (videoRef && index !== carouselIndex) {
@@ -20764,22 +20925,6 @@ const StructurePage = (_a) => {
20764
20925
  });
20765
20926
  }
20766
20927
  });
20767
- // 如果当前项是视频,自动播放
20768
- const currentItem = data.carouselSection[carouselIndex];
20769
- if ((currentItem === null || currentItem === void 0 ? void 0 : currentItem.url) && carouselVideoRefs.current[carouselIndex]) {
20770
- const currentVideo = carouselVideoRefs.current[carouselIndex];
20771
- if (currentVideo) {
20772
- currentVideo.play().catch(err => {
20773
- console.warn('[Carousel] Video autoplay failed:', err);
20774
- });
20775
- // 更新播放状态
20776
- setCarouselVideoPausedStates(prev => {
20777
- const newStates = [...prev];
20778
- newStates[carouselIndex] = false;
20779
- return newStates;
20780
- });
20781
- }
20782
- }
20783
20928
  }, [carouselIndex, data]);
20784
20929
  // 触摸滑动处理
20785
20930
  const handleTouchStart = (e) => {
@@ -20904,27 +21049,41 @@ const StructurePage = (_a) => {
20904
21049
  return textStyle;
20905
21050
  })()) }, data.heroSection.text))),
20906
21051
  React.createElement("div", { style: baseStyles.heroImageContainer },
20907
- React.createElement(PostView, { videoRef: heroVideoRef, isVideo: !!data.heroSection.url, onExposure: () => {
20908
- // 图片或视频的曝光上报
20909
- handlePostExposure(data.heroSection, 'heroSection', 0);
20910
- }, onVideoAutoPlay: () => {
20911
- // 视频自动播放时上报 playVideo 事件(2/3区域 + 300ms后)
20912
- if (heroVideoRef.current) {
20913
- handleVideoPlay(data.heroSection, 'heroSection', 0, heroVideoRef.current, true);
20914
- }
20915
- } }, data.heroSection.url ? (React.createElement("div", { style: { position: 'relative', width: '100%', height: '100%' }, onClick: handleHeroVideoClick },
20916
- React.createElement("video", { ref: heroVideoRef, src: data.heroSection.url, style: baseStyles.heroVideo, muted: true, loop: true, playsInline: true, controls: false, onLoadedMetadata: () => {
21052
+ React.createElement(PostView, { videoRef: heroVideoRef, isVideo: !!data.heroSection.url, autoPlayTriggerRef: heroAutoPlayTrigger }, data.heroSection.url ? (React.createElement("div", { style: { position: 'relative', width: '100%', height: '100%' }, onClick: handleHeroVideoClick },
21053
+ React.createElement("video", { ref: (el) => {
21054
+ if (heroVideoRef) {
21055
+ heroVideoRef.current = el;
21056
+ }
21057
+ // 添加 addEventListener 监听事件,确保网络慢时也能触发
21058
+ if (el && !el.dataset.playListenerAdded) {
21059
+ el.dataset.playListenerAdded = 'true';
21060
+ // play 事件:视频开始播放时触发(可能还在缓冲)
21061
+ el.addEventListener('play', (e) => {
21062
+ console.log('[Hero Video] play event triggered');
21063
+ handleVideoPlay(data.heroSection, 'heroSection', 0, e.currentTarget);
21064
+ });
21065
+ // playing 事件:视频真正开始播放时触发(已缓冲足够数据)
21066
+ // 这是网络慢时更可靠的事件
21067
+ el.addEventListener('playing', (e) => {
21068
+ console.log('[Hero Video] playing event triggered (backup)');
21069
+ // playing 也触发一次,防止 play 事件没触发
21070
+ handleVideoPlay(data.heroSection, 'heroSection', 0, e.currentTarget);
21071
+ });
21072
+ el.addEventListener('pause', (e) => {
21073
+ console.log('[Hero Video] pause event triggered');
21074
+ handleVideoPlayOver(data.heroSection, 'heroSection', 0, e.currentTarget);
21075
+ });
21076
+ el.addEventListener('ended', (e) => {
21077
+ console.log('[Hero Video] ended event triggered');
21078
+ handleVideoPlayOver(data.heroSection, 'heroSection', 0, e.currentTarget);
21079
+ });
21080
+ }
21081
+ }, src: data.heroSection.url, style: baseStyles.heroVideo, muted: true, loop: true, playsInline: true, controls: false, onLoadedMetadata: () => {
20917
21082
  var _a;
20918
21083
  const videoId = `${(_a = data.heroSection) === null || _a === void 0 ? void 0 : _a.itemId}-heroSection-0`;
20919
21084
  const loadTime = Date.now();
20920
21085
  const initTime = videoLoadTime.current[videoId] || loadTime;
20921
21086
  videoLoadTime.current[videoId] = loadTime - initTime;
20922
- }, onPlay: (e) => {
20923
- handleVideoPlay(data.heroSection, 'heroSection', 0, e.currentTarget);
20924
- }, onPause: (e) => {
20925
- handleVideoPlayOver(data.heroSection, 'heroSection', 0, e.currentTarget);
20926
- }, onEnded: (e) => {
20927
- handleVideoPlayOver(data.heroSection, 'heroSection', 0, e.currentTarget);
20928
21087
  } }),
20929
21088
  isHeroVideoPaused && (React.createElement(FormatImage$1, { className: 'clc-pb-video-pause', src: videoPlayIcon, alt: 'play' })))) : ((_f = data.heroSection.imgUrls) === null || _f === void 0 ? void 0 : _f[0]) ? (React.createElement("img", { src: data.heroSection.imgUrls[0], alt: 'Hero', style: baseStyles.heroImage })) : null),
20930
21089
  React.createElement("div", { style: baseStyles.heroOverlay }, renderCTA('heroButton', (_h = (_g = data.heroSection.bindProducts) === null || _g === void 0 ? void 0 : _g[0]) === null || _h === void 0 ? void 0 : _h.bindCta, (_j = data.heroSection.bindProducts) === null || _j === void 0 ? void 0 : _j[0], baseStyles.heroButton, 'heroSection', 0))))),
@@ -20932,22 +21091,99 @@ const StructurePage = (_a) => {
20932
21091
  React.createElement("div", { style: baseStyles.carouselImageContainer, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd },
20933
21092
  React.createElement("div", { style: Object.assign(Object.assign({}, baseStyles.carouselContainer), { transform: `translateX(-${carouselIndex * 100}%)` }) }, data.carouselSection.map((item, index) => {
20934
21093
  var _a;
20935
- return (React.createElement("div", { key: item.itemId, style: baseStyles.carouselSlide }, item.url ? (React.createElement("div", { style: { position: 'relative', width: '100%', height: '100%' }, onClick: () => handleCarouselVideoClick(index) },
21094
+ // 只有当前显示的 slide 才应用曝光检测
21095
+ const isCurrentSlide = index === carouselIndex;
21096
+ return (React.createElement("div", { key: item.itemId, style: baseStyles.carouselSlide }, item.url ? (
21097
+ // 视频:使用 PostView 包裹,但只在当前显示时生效
21098
+ isCurrentSlide ? (React.createElement(PostView, { videoRef: { current: carouselVideoRefs.current[index] }, isVideo: true, autoPlayTriggerRef: {
21099
+ get current() {
21100
+ return carouselAutoPlayTriggers.current[index] || false;
21101
+ },
21102
+ set current(value) {
21103
+ carouselAutoPlayTriggers.current[index] = value;
21104
+ }
21105
+ }, onVideoPlay: () => {
21106
+ // 视频开始播放,隐藏暂停图标
21107
+ console.log('[Carousel PostView] onVideoPlay 回调,索引:', index);
21108
+ setCarouselVideoPausedStates(prev => {
21109
+ const newStates = [...prev];
21110
+ newStates[index] = false;
21111
+ return newStates;
21112
+ });
21113
+ }, onVideoPause: () => {
21114
+ // 视频暂停,显示暂停图标
21115
+ console.log('[Carousel PostView] onVideoPause 回调,索引:', index);
21116
+ setCarouselVideoPausedStates(prev => {
21117
+ const newStates = [...prev];
21118
+ newStates[index] = true;
21119
+ return newStates;
21120
+ });
21121
+ } },
21122
+ React.createElement("div", { style: { position: 'relative', width: '100%', height: '100%' }, onClick: () => handleCarouselVideoClick(index) },
21123
+ React.createElement("video", { ref: (el) => {
21124
+ carouselVideoRefs.current[index] = el;
21125
+ // 添加 addEventListener 监听事件,确保网络慢时也能触发
21126
+ if (el && !el.dataset.playListenerAdded) {
21127
+ el.dataset.playListenerAdded = 'true';
21128
+ // play 事件:视频开始播放时触发(可能还在缓冲)
21129
+ el.addEventListener('play', (e) => {
21130
+ console.log('[Carousel Video] play event triggered, index:', index);
21131
+ handleVideoPlay(item, 'carouselSection', index, e.currentTarget);
21132
+ });
21133
+ // playing 事件:视频真正开始播放时触发(已缓冲足够数据)
21134
+ el.addEventListener('playing', (e) => {
21135
+ console.log('[Carousel Video] playing event triggered (backup), index:', index);
21136
+ handleVideoPlay(item, 'carouselSection', index, e.currentTarget);
21137
+ });
21138
+ el.addEventListener('pause', (e) => {
21139
+ console.log('[Carousel Video] pause event triggered, index:', index);
21140
+ handleVideoPlayOver(item, 'carouselSection', index, e.currentTarget);
21141
+ });
21142
+ el.addEventListener('ended', (e) => {
21143
+ console.log('[Carousel Video] ended event triggered, index:', index);
21144
+ handleVideoPlayOver(item, 'carouselSection', index, e.currentTarget);
21145
+ });
21146
+ }
21147
+ }, src: item.url, style: baseStyles.carouselVideo, muted: true, loop: true, playsInline: true, controls: false, draggable: false, onDragStart: (e) => e.preventDefault(), onLoadedMetadata: () => {
21148
+ const videoId = `${item.itemId}-carouselSection-${index}`;
21149
+ const loadTime = Date.now();
21150
+ const initTime = videoLoadTime.current[videoId] || loadTime;
21151
+ videoLoadTime.current[videoId] = loadTime - initTime;
21152
+ } }),
21153
+ carouselVideoPausedStates[index] && (React.createElement(FormatImage$1, { className: 'clc-pb-video-pause', src: videoPlayIcon, alt: 'play' }))))) : (
21154
+ // 非当前 slide,不使用 PostView
21155
+ React.createElement("div", { style: { position: 'relative', width: '100%', height: '100%' }, onClick: () => handleCarouselVideoClick(index) },
20936
21156
  React.createElement("video", { ref: (el) => {
20937
21157
  carouselVideoRefs.current[index] = el;
21158
+ // 添加 addEventListener 监听事件,确保网络慢时也能触发
21159
+ if (el && !el.dataset.playListenerAdded) {
21160
+ el.dataset.playListenerAdded = 'true';
21161
+ // play 事件:视频开始播放时触发(可能还在缓冲)
21162
+ el.addEventListener('play', (e) => {
21163
+ console.log('[Carousel Video (non-current)] play event triggered, index:', index);
21164
+ handleVideoPlay(item, 'carouselSection', index, e.currentTarget);
21165
+ });
21166
+ // playing 事件:视频真正开始播放时触发(已缓冲足够数据)
21167
+ el.addEventListener('playing', (e) => {
21168
+ console.log('[Carousel Video (non-current)] playing event triggered (backup), index:', index);
21169
+ handleVideoPlay(item, 'carouselSection', index, e.currentTarget);
21170
+ });
21171
+ el.addEventListener('pause', (e) => {
21172
+ console.log('[Carousel Video (non-current)] pause event triggered, index:', index);
21173
+ handleVideoPlayOver(item, 'carouselSection', index, e.currentTarget);
21174
+ });
21175
+ el.addEventListener('ended', (e) => {
21176
+ console.log('[Carousel Video (non-current)] ended event triggered, index:', index);
21177
+ handleVideoPlayOver(item, 'carouselSection', index, e.currentTarget);
21178
+ });
21179
+ }
20938
21180
  }, src: item.url, style: baseStyles.carouselVideo, muted: true, loop: true, playsInline: true, controls: false, draggable: false, onDragStart: (e) => e.preventDefault(), onLoadedMetadata: () => {
20939
21181
  const videoId = `${item.itemId}-carouselSection-${index}`;
20940
21182
  const loadTime = Date.now();
20941
21183
  const initTime = videoLoadTime.current[videoId] || loadTime;
20942
21184
  videoLoadTime.current[videoId] = loadTime - initTime;
20943
- }, onPlay: (e) => {
20944
- handleVideoPlay(item, 'carouselSection', index, e.currentTarget);
20945
- }, onPause: (e) => {
20946
- handleVideoPlayOver(item, 'carouselSection', index, e.currentTarget);
20947
- }, onEnded: (e) => {
20948
- handleVideoPlayOver(item, 'carouselSection', index, e.currentTarget);
20949
21185
  } }),
20950
- carouselVideoPausedStates[index] && (React.createElement(FormatImage$1, { className: 'clc-pb-video-pause', src: videoPlayIcon, alt: 'play' })))) : ((_a = item.imgUrls) === null || _a === void 0 ? void 0 : _a[0]) ? (React.createElement("img", { src: item.imgUrls[0], alt: item.text || 'Carousel', style: baseStyles.carouselImage, draggable: false, onDragStart: (e) => e.preventDefault(), onLoad: () => {
21186
+ carouselVideoPausedStates[index] && (React.createElement(FormatImage$1, { className: 'clc-pb-video-pause', src: videoPlayIcon, alt: 'play' }))))) : ((_a = item.imgUrls) === null || _a === void 0 ? void 0 : _a[0]) ? (React.createElement("img", { src: item.imgUrls[0], alt: item.text || 'Carousel', style: baseStyles.carouselImage, draggable: false, onDragStart: (e) => e.preventDefault(), onLoad: () => {
20951
21187
  // 记录图片加载完成时间
20952
21188
  if (!carouselImageLoadTime.current[index]) {
20953
21189
  carouselImageLoadTime.current[index] = Date.now();
@@ -21001,7 +21237,7 @@ const StructurePage = (_a) => {
21001
21237
  return textStyle;
21002
21238
  })()) }, (_m = data.carouselSection[carouselIndex]) === null || _m === void 0 ? void 0 : _m.text))),
21003
21239
  renderCTA('carouselButton', (_q = (_p = (_o = data.carouselSection[carouselIndex]) === null || _o === void 0 ? void 0 : _o.bindProducts) === null || _p === void 0 ? void 0 : _p[0]) === null || _q === void 0 ? void 0 : _q.bindCta, (_s = (_r = data.carouselSection[carouselIndex]) === null || _r === void 0 ? void 0 : _r.bindProducts) === null || _s === void 0 ? void 0 : _s[0], baseStyles.carouselButton, 'carouselSection', carouselIndex)))),
21004
- data.highlightRevealSection && (React.createElement(ProductView, { onExposure: () => handleProductViewExposure(data.highlightRevealSection, 'highlightRevealSection', 0) },
21240
+ data.highlightRevealSection && (React.createElement(ProductView, { onExposure: () => handleProductViewExposure(data.highlightRevealSection, 'highlightRevealSection', 0), onLeave: () => handleProductViewLeave(data.highlightRevealSection, 'highlightRevealSection', 0) },
21005
21241
  React.createElement("div", { style: mergeStyles(baseStyles.highlightSection, 'highlightSection', { excludeBorder: true }) },
21006
21242
  React.createElement("div", { style: baseStyles.highlightImageContainer },
21007
21243
  React.createElement("img", { src: data.highlightRevealSection.landingImageUrl || data.highlightRevealSection.cover, alt: data.highlightRevealSection.title, style: baseStyles.highlightImage })),
@@ -21087,7 +21323,7 @@ const StructurePage = (_a) => {
21087
21323
  // 使用产品在数据数组中的实际索引来确定 buttonKey
21088
21324
  const productDataIndex = productIndexMap[gridIndex];
21089
21325
  const buttonKey = `productButton${productDataIndex || gridIndex + 1}`;
21090
- return (React.createElement("div", { key: (product === null || product === void 0 ? void 0 : product.itemId) || `empty-${gridIndex}`, style: baseStyles.productItem }, product ? (React.createElement(ProductView, { onExposure: () => handleProductViewExposure(product, 'productGridSection', gridIndex) },
21326
+ return (React.createElement("div", { key: (product === null || product === void 0 ? void 0 : product.itemId) || `empty-${gridIndex}`, style: baseStyles.productItem }, product ? (React.createElement(ProductView, { onExposure: () => handleProductViewExposure(product, 'productGridSection', gridIndex), onLeave: () => handleProductViewLeave(product, 'productGridSection', gridIndex) },
21091
21327
  React.createElement(React.Fragment, null,
21092
21328
  React.createElement("div", { style: baseStyles.productImageContainer },
21093
21329
  React.createElement("img", { src: product.landingImageUrl || product.cover,