zlwu 3 månader sedan
förälder
incheckning
d13f6a0ab2
2 ändrade filer med 121 tillägg och 55 borttagningar
  1. 120 54
      frontend/src/pages/ChatBotPage.tsx
  2. 1 1
      frontend/src/utils/api.ts

+ 120 - 54
frontend/src/pages/ChatBotPage.tsx

@@ -223,6 +223,11 @@ function getLocalizedInboxContent(
   return item.content_en || item.content || item.content_zh || '';
 }
 
+/** 纯通知类站内信(无能量石领取):普通消息通知 + A/B 实验通知 */
+function isDisplayOnlyInboxNotice(type: string): boolean {
+  return type === 'news_notice' || type === 'ab_notice';
+}
+
 export const ChatBotPage = () => {
   const { t: tCommon, i18n } = useTranslation('common');
   const { t: tProfile } = useTranslation('profile');
@@ -550,15 +555,20 @@ export const ChatBotPage = () => {
     setPendingInboxGiftDetail(null);
     setShowInboxGiftModal(true);
     setLoadingInboxGiftDetail(true);
+    // 纯通知无领取流程:展示即标记已读,避免下次进页重复弹窗
+    const markRead = isDisplayOnlyInboxNotice(item.type);
     try {
-      const detailRes = await getUserInboxMessageDetail(userId, item.id, false);
+      const detailRes = await getUserInboxMessageDetail(userId, item.id, markRead);
       if (detailRes?.success && detailRes.data) {
         setPendingInboxGiftDetail(detailRes.data);
       }
+      if (markRead) {
+        await loadInboxUnreadBadge(userId);
+      }
     } finally {
       setLoadingInboxGiftDetail(false);
     }
-  }, []);
+  }, [loadInboxUnreadBadge]);
 
   const advanceInboxGiftQueueOrContinue = useCallback(
     (userId?: string | null) => {
@@ -583,12 +593,24 @@ export const ChatBotPage = () => {
 
   const closeInboxGiftModalAndContinue = useCallback(() => {
     const userId = getUserId();
+    const currentMessage = pendingInboxGiftMessage;
     setShowInboxGiftModal(false);
     setPendingInboxGiftDetail(null);
     setLoadingInboxGiftDetail(false);
     setClaimingInboxGift(false);
+
+    // 关闭时兜底标记已读(详情请求失败/未完成时避免下次重复弹)
+    if (userId && currentMessage && isDisplayOnlyInboxNotice(currentMessage.type)) {
+      void getUserInboxMessageDetail(userId, currentMessage.id, true)
+        .then(() => loadInboxUnreadBadge(userId))
+        .finally(() => {
+          advanceInboxGiftQueueOrContinue(userId);
+        });
+      return;
+    }
+
     advanceInboxGiftQueueOrContinue(userId);
-  }, [advanceInboxGiftQueueOrContinue]);
+  }, [advanceInboxGiftQueueOrContinue, loadInboxUnreadBadge, pendingInboxGiftMessage]);
 
   const closeInboxRewardModalAndContinue = useCallback(() => {
     const userId = getUserId();
@@ -2336,28 +2358,44 @@ export const ChatBotPage = () => {
       // 对话能量石数量(顶部状态)
       fetchConversationStoneCount(userId);
 
-      getUserInboxMessages(userId, {
-        page: 1,
-        page_size: 20,
-        message_type: 'energy_gift',
-      })
-        .then((res) => {
+      // 能量石未领取 + 消息通知/A/B 通知未读:进页依次弹窗(样式一致;通知仅展示无领取)
+      Promise.all([
+        getUserInboxMessages(userId, {
+          page: 1,
+          page_size: 20,
+          message_type: 'energy_gift',
+        }),
+        getUserInboxMessages(userId, {
+          page: 1,
+          page_size: 20,
+          message_type: 'news_notice',
+        }),
+        getUserInboxMessages(userId, {
+          page: 1,
+          page_size: 20,
+          message_type: 'ab_notice',
+        }),
+      ])
+        .then(([giftRes, noticeRes, abNoticeRes]) => {
           const pendingGiftItems =
-            res.data?.items
-              .filter((item) => Number(item.claim_status) !== 1)
-              .sort((a, b) => {
-                const timeA = a.created_at ? new Date(a.created_at).getTime() : 0;
-                const timeB = b.created_at ? new Date(b.created_at).getTime() : 0;
-                return timeB - timeA;
-              }) ?? [];
-          if (pendingGiftItems.length === 0) {
+            giftRes.data?.items.filter((item) => Number(item.claim_status) !== 1) ?? [];
+          const pendingNoticeItems = [
+            ...(noticeRes.data?.items ?? []),
+            ...(abNoticeRes.data?.items ?? []),
+          ].filter((item) => Number(item.read_status) === 0);
+          const pendingInboxItems = [...pendingGiftItems, ...pendingNoticeItems].sort((a, b) => {
+            const timeA = a.created_at ? new Date(a.created_at).getTime() : 0;
+            const timeB = b.created_at ? new Date(b.created_at).getTime() : 0;
+            return timeB - timeA;
+          });
+          if (pendingInboxItems.length === 0) {
             startPostInboxFlow(userId);
             return;
           }
 
-          const [firstGift, ...restGifts] = pendingGiftItems;
-          setPendingInboxGiftQueue(restGifts);
-          void showInboxGiftMessage(userId, firstGift);
+          const [firstMessage, ...restMessages] = pendingInboxItems;
+          setPendingInboxGiftQueue(restMessages);
+          void showInboxGiftMessage(userId, firstMessage);
         })
         .catch(() => {
           startPostInboxFlow(userId);
@@ -5061,47 +5099,57 @@ export const ChatBotPage = () => {
                 : `${isZh ? '亲爱的:' : 'Dear:'} ${getStorage(STORAGE_KEYS.USER_NAME) || tCommon('userDefault')}\n${pendingInboxGiftDetail ? getLocalizedInboxContent(pendingInboxGiftDetail, i18n.language) : ''}`}
             </div>
 
-            <div
-              style={{
-                position: 'relative',
-                width: px2vmin(44, 'v2'),
-                height: px2vmin(44, 'v2'),
-                margin: `${px2vmin(16, 'v2')} auto 0`,
-              }}
-            >
-              <img
-                src={talkEnergyStoneImg}
-                alt=""
-                draggable={false}
-                style={{
-                  width: '100%',
-                  height: '100%',
-                  objectFit: 'contain',
-                  display: 'block',
-                }}
-              />
-              <span
+            {isDisplayOnlyInboxNotice(pendingInboxGiftMessage.type) ? null : (
+              <div
                 style={{
-                  position: 'absolute',
-                  right: px2vmin(2, 'v2'),
-                  bottom: px2vmin(2, 'v2'),
-                  color: '#F5F3F0',
-                  fontSize: px2vmin(11, 'v2'),
-                  lineHeight: px2vmin(11, 'v2'),
-                  fontWeight: 500,
+                  position: 'relative',
+                  width: px2vmin(44, 'v2'),
+                  height: px2vmin(44, 'v2'),
+                  margin: `${px2vmin(16, 'v2')} auto 0`,
                 }}
               >
-                {`X${pendingInboxGiftMessage.energy_stone_amount}`}
-              </span>
-            </div>
+                <img
+                  src={talkEnergyStoneImg}
+                  alt=""
+                  draggable={false}
+                  style={{
+                    width: '100%',
+                    height: '100%',
+                    objectFit: 'contain',
+                    display: 'block',
+                  }}
+                />
+                <span
+                  style={{
+                    position: 'absolute',
+                    right: px2vmin(2, 'v2'),
+                    bottom: px2vmin(2, 'v2'),
+                    color: '#F5F3F0',
+                    fontSize: px2vmin(11, 'v2'),
+                    lineHeight: px2vmin(11, 'v2'),
+                    fontWeight: 500,
+                  }}
+                >
+                  {`X${pendingInboxGiftMessage.energy_stone_amount}`}
+                </span>
+              </div>
+            )}
 
             <button
               type="button"
               data-sfx-ignore
               onClick={() => {
+                if (isDisplayOnlyInboxNotice(pendingInboxGiftMessage.type)) {
+                  closeInboxGiftModalAndContinue();
+                  return;
+                }
                 void handleClaimInboxGift();
               }}
-              disabled={claimingInboxGift || loadingInboxGiftDetail}
+              disabled={
+                isDisplayOnlyInboxNotice(pendingInboxGiftMessage.type)
+                  ? loadingInboxGiftDetail
+                  : claimingInboxGift || loadingInboxGiftDetail
+              }
               style={{
                 margin: `${px2vmin(16, 'v2')} auto 0`,
                 width: px2vmin(210),
@@ -5111,11 +5159,29 @@ export const ChatBotPage = () => {
                 background: `url(${secondaryDefaultButton}) center/cover no-repeat`,
                 color: '#F5F3F0',
                 fontSize: px2vmin(16, 'v2'),
-                cursor: claimingInboxGift || loadingInboxGiftDetail ? 'default' : 'pointer',
-                opacity: claimingInboxGift || loadingInboxGiftDetail ? 0.6 : 1,
+                cursor: isDisplayOnlyInboxNotice(pendingInboxGiftMessage.type)
+                  ? loadingInboxGiftDetail
+                    ? 'default'
+                    : 'pointer'
+                  : claimingInboxGift || loadingInboxGiftDetail
+                    ? 'default'
+                    : 'pointer',
+                opacity: isDisplayOnlyInboxNotice(pendingInboxGiftMessage.type)
+                  ? loadingInboxGiftDetail
+                    ? 0.6
+                    : 1
+                  : claimingInboxGift || loadingInboxGiftDetail
+                    ? 0.6
+                    : 1,
               }}
             >
-              {isZh ? '领取奖励' : 'Claim reward'}
+              {isDisplayOnlyInboxNotice(pendingInboxGiftMessage.type)
+                ? isZh
+                  ? '知道了'
+                  : 'Got it'
+                : isZh
+                  ? '领取奖励'
+                  : 'Claim reward'}
             </button>
           </div>
         </>

+ 1 - 1
frontend/src/utils/api.ts

@@ -505,7 +505,7 @@ export const getUserInboxMessages = async (
   params?: {
     page?: number;
     page_size?: number;
-    message_type?: 'energy_gift' | 'news_notice';
+    message_type?: 'energy_gift' | 'news_notice' | 'ab_notice';
   },
 ): Promise<UserInboxMessagesResponse> => {
   const apiBaseUrl = getApiBaseUrl();