pb-sxp-ui 1.20.44 → 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
@@ -19611,15 +19611,19 @@ 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));
@@ -20037,6 +20041,7 @@ const StructurePage = (_a) => {
20037
20041
  const isFirstPlay = React.useRef({});
20038
20042
  const videoLoadTime = React.useRef({});
20039
20043
  const videoAutoPlayReported = React.useRef(new Set()); // 追踪哪些视频已通过自动播放上报
20044
+ const videoPlayReportTime = React.useRef({}); // 追踪视频播放上报的时间戳,防止短时间内重复上报
20040
20045
  // 自动播放触发标记(用于区分自动播放和用户手动播放)
20041
20046
  const heroAutoPlayTrigger = React.useRef(false);
20042
20047
  const carouselAutoPlayTriggers = React.useRef({});
@@ -20285,6 +20290,16 @@ const StructurePage = (_a) => {
20285
20290
  return;
20286
20291
  }
20287
20292
  const videoId = `${videoData === null || videoData === void 0 ? void 0 : videoData.itemId}-${viewPosition}-${sectionIndex}`;
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
+ }
20288
20303
  // 检查是否是自动播放触发的
20289
20304
  let isAutoPlay = false;
20290
20305
  if (viewPosition === 'heroSection' && heroAutoPlayTrigger.current) {
@@ -20314,6 +20329,8 @@ const StructurePage = (_a) => {
20314
20329
  }
20315
20330
  // 记录视频开始播放时间
20316
20331
  videoStartTime.current[videoId] = videoRef.currentTime || 0;
20332
+ // 记录本次上报时间
20333
+ videoPlayReportTime.current[videoId] = now;
20317
20334
  // 判断是否首次播放
20318
20335
  const playType = isFirstPlay.current[videoId] === false ? '1' : '0';
20319
20336
  isFirstPlay.current[videoId] = false;
@@ -20385,20 +20402,40 @@ const StructurePage = (_a) => {
20385
20402
  }
20386
20403
  });
20387
20404
  }, [bffEventReport, data]);
20388
- // 产品浏览事件处理
20405
+ // 产品进入可视区域时记录开始浏览时间
20389
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) => {
20390
20422
  var _a;
20391
- // 生成唯一标识,避免重复上报
20423
+ // 生成唯一标识
20392
20424
  const productId = `${productData === null || productData === void 0 ? void 0 : productData.itemId}-${viewPosition}-${sectionIndex}`;
20393
- // 如果已经上报过,不再上报
20425
+ // 如果已经上报过,不再重复上报
20394
20426
  if (exposedProductRefs.current.has(productId)) {
20395
20427
  return;
20396
20428
  }
20397
- // 标记为已上报并记录开始浏览时间
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
+ // 标记为已上报
20398
20438
  exposedProductRefs.current.add(productId);
20399
- productViewStartTime.current[productId] = Date.now();
20400
- // 计算在页面停留时间(从产品出现到现在)
20401
- const timeOnSite = 0; // 首次曝光时为0
20402
20439
  // 根据 viewPosition 确定 fromKName
20403
20440
  let fromKName = 'productPage';
20404
20441
  if (viewPosition === 'heroSection') {
@@ -20413,6 +20450,11 @@ const StructurePage = (_a) => {
20413
20450
  else if (viewPosition === 'footerSection') {
20414
20451
  fromKName = 'pdpPage';
20415
20452
  }
20453
+ console.log('[handleProductViewLeave] 上报 productView 事件:', {
20454
+ productId,
20455
+ timeOnSite,
20456
+ fromKName
20457
+ });
20416
20458
  bffEventReport === null || bffEventReport === void 0 ? void 0 : bffEventReport({
20417
20459
  eventInfo: {
20418
20460
  eventSubject: 'productView',
@@ -21008,19 +21050,40 @@ const StructurePage = (_a) => {
21008
21050
  })()) }, data.heroSection.text))),
21009
21051
  React.createElement("div", { style: baseStyles.heroImageContainer },
21010
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 },
21011
- React.createElement("video", { ref: heroVideoRef, src: data.heroSection.url, style: baseStyles.heroVideo, muted: true, loop: true, playsInline: true, controls: false, onLoadedMetadata: () => {
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: () => {
21012
21082
  var _a;
21013
21083
  const videoId = `${(_a = data.heroSection) === null || _a === void 0 ? void 0 : _a.itemId}-heroSection-0`;
21014
21084
  const loadTime = Date.now();
21015
21085
  const initTime = videoLoadTime.current[videoId] || loadTime;
21016
21086
  videoLoadTime.current[videoId] = loadTime - initTime;
21017
- }, onPlay: (e) => {
21018
- // 视频真正开始播放时才上报(考虑网速问题)
21019
- handleVideoPlay(data.heroSection, 'heroSection', 0, e.currentTarget);
21020
- }, onPause: (e) => {
21021
- handleVideoPlayOver(data.heroSection, 'heroSection', 0, e.currentTarget);
21022
- }, onEnded: (e) => {
21023
- handleVideoPlayOver(data.heroSection, 'heroSection', 0, e.currentTarget);
21024
21087
  } }),
21025
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),
21026
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))))),
@@ -21059,34 +21122,66 @@ const StructurePage = (_a) => {
21059
21122
  React.createElement("div", { style: { position: 'relative', width: '100%', height: '100%' }, onClick: () => handleCarouselVideoClick(index) },
21060
21123
  React.createElement("video", { ref: (el) => {
21061
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
+ }
21062
21147
  }, src: item.url, style: baseStyles.carouselVideo, muted: true, loop: true, playsInline: true, controls: false, draggable: false, onDragStart: (e) => e.preventDefault(), onLoadedMetadata: () => {
21063
21148
  const videoId = `${item.itemId}-carouselSection-${index}`;
21064
21149
  const loadTime = Date.now();
21065
21150
  const initTime = videoLoadTime.current[videoId] || loadTime;
21066
21151
  videoLoadTime.current[videoId] = loadTime - initTime;
21067
- }, onPlay: (e) => {
21068
- handleVideoPlay(item, 'carouselSection', index, e.currentTarget);
21069
- }, onPause: (e) => {
21070
- handleVideoPlayOver(item, 'carouselSection', index, e.currentTarget);
21071
- }, onEnded: (e) => {
21072
- handleVideoPlayOver(item, 'carouselSection', index, e.currentTarget);
21073
21152
  } }),
21074
21153
  carouselVideoPausedStates[index] && (React.createElement(FormatImage$1, { className: 'clc-pb-video-pause', src: videoPlayIcon, alt: 'play' }))))) : (
21075
21154
  // 非当前 slide,不使用 PostView
21076
21155
  React.createElement("div", { style: { position: 'relative', width: '100%', height: '100%' }, onClick: () => handleCarouselVideoClick(index) },
21077
21156
  React.createElement("video", { ref: (el) => {
21078
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
+ }
21079
21180
  }, src: item.url, style: baseStyles.carouselVideo, muted: true, loop: true, playsInline: true, controls: false, draggable: false, onDragStart: (e) => e.preventDefault(), onLoadedMetadata: () => {
21080
21181
  const videoId = `${item.itemId}-carouselSection-${index}`;
21081
21182
  const loadTime = Date.now();
21082
21183
  const initTime = videoLoadTime.current[videoId] || loadTime;
21083
21184
  videoLoadTime.current[videoId] = loadTime - initTime;
21084
- }, onPlay: (e) => {
21085
- handleVideoPlay(item, 'carouselSection', index, e.currentTarget);
21086
- }, onPause: (e) => {
21087
- handleVideoPlayOver(item, 'carouselSection', index, e.currentTarget);
21088
- }, onEnded: (e) => {
21089
- handleVideoPlayOver(item, 'carouselSection', index, e.currentTarget);
21090
21185
  } }),
21091
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: () => {
21092
21187
  // 记录图片加载完成时间
@@ -21142,7 +21237,7 @@ const StructurePage = (_a) => {
21142
21237
  return textStyle;
21143
21238
  })()) }, (_m = data.carouselSection[carouselIndex]) === null || _m === void 0 ? void 0 : _m.text))),
21144
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)))),
21145
- 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) },
21146
21241
  React.createElement("div", { style: mergeStyles(baseStyles.highlightSection, 'highlightSection', { excludeBorder: true }) },
21147
21242
  React.createElement("div", { style: baseStyles.highlightImageContainer },
21148
21243
  React.createElement("img", { src: data.highlightRevealSection.landingImageUrl || data.highlightRevealSection.cover, alt: data.highlightRevealSection.title, style: baseStyles.highlightImage })),
@@ -21228,7 +21323,7 @@ const StructurePage = (_a) => {
21228
21323
  // 使用产品在数据数组中的实际索引来确定 buttonKey
21229
21324
  const productDataIndex = productIndexMap[gridIndex];
21230
21325
  const buttonKey = `productButton${productDataIndex || gridIndex + 1}`;
21231
- 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) },
21232
21327
  React.createElement(React.Fragment, null,
21233
21328
  React.createElement("div", { style: baseStyles.productImageContainer },
21234
21329
  React.createElement("img", { src: product.landingImageUrl || product.cover,