فهرست منبع

国开大首页弹窗

yuanmingze 1 ماه پیش
والد
کامیت
0937d3a8aa

+ 1 - 1
src/pages-mine/personal-card/components/ProfessionalSkillCard.vue

@@ -51,7 +51,7 @@
               >
                 {{ getPromotionDrugDisplayText(item) }}
               </text>
-              <wd-icon name="arrow-right" size="30rpx" color="#a9afb8" />
+              <wd-icon name="right" size="30rpx" color="#a9afb8" />
             </view>
           </view>
         </view>

+ 107 - 0
src/pages/index/components/GdComplianceBannerDialog.vue

@@ -0,0 +1,107 @@
+<template>
+  <wd-popup
+    v-model="visible"
+    position="center"
+    root-portal
+    round
+    closable
+    lock-scroll
+    :z-index="12000"
+    :close-on-click-modal="false"
+    custom-style="width: 580rpx; overflow: hidden; background: transparent; --wot-popup-close-color: #ffffff; --wot-popup-close-size: 34rpx; --wot-popup-close-margin: 20rpx;"
+  >
+    <image class="banner-image" :src="BANNER_IMAGE_URL" mode="widthFix" />
+  </wd-popup>
+</template>
+
+<script setup lang="ts">
+import { onBeforeUnmount, ref, watch } from 'vue'
+
+import { getGdBannerStatusApi } from '@/services/modules/userInfo'
+
+import { useUserStore } from '@/stores/modules/user'
+
+const BANNER_IMAGE_URL =
+  'https://yy-cloud-oss-public.oss-cn-beijing.aliyuncs.com/image/guokaidacc.png'
+
+const props = defineProps<{
+  canShow: boolean
+}>()
+
+const userStore = useUserStore()
+const visible = ref(false)
+let showTimer: ReturnType<typeof setTimeout> | undefined
+
+const clearShowTimer = () => {
+  if (!showTimer) return
+
+  clearTimeout(showTimer)
+  showTimer = undefined
+}
+
+const requestStatus = async () => {
+  if (userStore.gdBannerRequestStatus !== 'idle') return
+
+  userStore.beginGdBannerRequest()
+
+  try {
+    const res = await getGdBannerStatusApi()
+    userStore.completeGdBannerRequest(res.code === 0 && res.data === true)
+  } catch (error) {
+    userStore.completeGdBannerRequest(false)
+    console.error('获取国开大活动弹窗状态失败:', error)
+  }
+}
+
+const scheduleShow = () => {
+  clearShowTimer()
+  showTimer = setTimeout(() => {
+    showTimer = undefined
+
+    if (!props.canShow || userStore.hasHandledGdBanner) return
+
+    userStore.markGdBannerHandled()
+    visible.value = true
+  }, 300)
+}
+
+watch(
+  () =>
+    [
+      props.canShow,
+      userStore.gdBannerRequestStatus,
+      userStore.gdBannerEligible,
+      userStore.hasHandledGdBanner,
+    ] as const,
+  ([canShow, requestStatusValue, eligible, handled]) => {
+    clearShowTimer()
+
+    if (!canShow) {
+      visible.value = false
+      return
+    }
+
+    if (requestStatusValue === 'idle') {
+      void requestStatus()
+      return
+    }
+
+    if (requestStatusValue === 'resolved' && eligible && !handled) {
+      scheduleShow()
+    }
+  },
+  { immediate: true }
+)
+
+onBeforeUnmount(() => {
+  clearShowTimer()
+})
+</script>
+
+<style lang="scss" scoped>
+.banner-image {
+  display: block;
+  width: 580rpx;
+  height: auto;
+}
+</style>

+ 104 - 29
src/pages/index/index.vue

@@ -33,13 +33,16 @@
       :type="livenessReminderType"
       :expire-date-text="livenessExpireDateText"
     />
+
+    <!-- 国开大合规上岗活动弹窗 -->
+    <GdComplianceBannerDialog :can-show="canShowGdBanner" />
   </view>
 </template>
 
 <script setup lang="ts">
-import { computed, ref } from 'vue'
+import { computed, ref, watch } from 'vue'
 
-import { onShow } from '@dcloudio/uni-app'
+import { onHide, onShow, onUnload } from '@dcloudio/uni-app'
 import dayjs from 'dayjs'
 
 import {
@@ -53,6 +56,7 @@ import type { PopupNoticeItem, PopupNoticeType } from '@/services/modules/userIn
 
 import { useUserStore } from '@/stores/modules/user'
 
+import GdComplianceBannerDialog from './components/GdComplianceBannerDialog.vue'
 import HonestAgreementGate from './components/HonestAgreementGate.vue'
 import LivenessReminderDialog from './components/LivenessReminderDialog.vue'
 import WorkbenchCard from './components/WorkbenchCard.vue'
@@ -66,6 +70,11 @@ const isLoggedIn = computed(() => userStore.isLoggedIn)
 const waitApprove = ref('0')
 const onTheWay = ref('0')
 const isShowingPopupNotice = ref(false)
+const isPageActive = ref(false)
+const isHomePopupFlowReady = ref(false)
+const shouldResumeAfterLiveness = ref(false)
+let livenessResumeTimer: ReturnType<typeof setTimeout> | undefined
+let isCompletingHomePopupFlow = false
 
 const SUPPORTED_POPUP_NOTICE_TYPES: PopupNoticeType[] = [
   'PENDING_SUBMIT',
@@ -75,6 +84,10 @@ const SUPPORTED_POPUP_NOTICE_TYPES: PopupNoticeType[] = [
 ]
 
 onShow(async () => {
+  isPageActive.value = true
+  isHomePopupFlowReady.value = false
+  shouldResumeAfterLiveness.value = false
+
   // 每次进入页面时刷新用户状态
   if (!userStore.isLoggedIn) return
 
@@ -100,7 +113,7 @@ onShow(async () => {
 
   // 超龄提醒
   if (userInfo.ageReminder) {
-    notifyUpcomingAgeLimit()
+    await notifyUpcomingAgeLimit()
   }
 
   // 平台合规测试
@@ -110,20 +123,61 @@ onShow(async () => {
   // 活体验证提醒:本次登录只弹一次
   const hasOpenedLivenessReminder = checkLivenessReminder(userInfo)
   if (hasOpenedLivenessReminder) {
+    shouldResumeAfterLiveness.value = true
     uni.showTabBar()
     return
   }
 
-  // 密码修改提醒
-  const hasNavigatedToResetPassword = await checkLastChangePassword()
-  if (hasNavigatedToResetPassword) return
+  await completeHomePopupFlow()
+})
 
-  await requestPopupNoticesOnce()
+onHide(() => {
+  isPageActive.value = false
+  isHomePopupFlowReady.value = false
+  shouldResumeAfterLiveness.value = false
+  clearLivenessResumeTimer()
+})
 
-  // 兜底处理
-  uni.showTabBar()
+onUnload(() => {
+  clearLivenessResumeTimer()
 })
 
+const completeHomePopupFlow = async () => {
+  if (!isPageActive.value || !userStore.isLoggedIn || isCompletingHomePopupFlow) return
+
+  isCompletingHomePopupFlow = true
+
+  try {
+    // 密码修改提醒
+    const hasNavigatedToResetPassword = await checkLastChangePassword()
+    if (hasNavigatedToResetPassword || !isPageActive.value) return
+
+    await requestPopupNoticesOnce()
+    if (!isPageActive.value) return
+
+    isHomePopupFlowReady.value = true
+
+    // 兜底处理
+    uni.showTabBar()
+  } finally {
+    isCompletingHomePopupFlow = false
+  }
+}
+
+const scheduleLivenessResume = () => {
+  clearLivenessResumeTimer()
+  livenessResumeTimer = setTimeout(() => {
+    livenessResumeTimer = undefined
+    void completeHomePopupFlow()
+  }, 300)
+}
+
+const clearLivenessResumeTimer = () => {
+  if (!livenessResumeTimer) return
+  clearTimeout(livenessResumeTimer)
+  livenessResumeTimer = undefined
+}
+
 const requestPopupNoticesOnce = async () => {
   if (!userStore.hasRequestedPopupNotices) {
     // 发起请求前即标记,避免首页 onShow 并发触发重复请求。
@@ -246,9 +300,7 @@ interface PopupNoticeModalOptions {
   confirmText: string
 }
 
-const openPopupNoticeModal = (
-  options: PopupNoticeModalOptions
-): Promise<boolean | null> => {
+const openPopupNoticeModal = (options: PopupNoticeModalOptions): Promise<boolean | null> => {
   return new Promise((resolve) => {
     let result: boolean | null = null
 
@@ -353,23 +405,28 @@ const isAgeOverLimit = () => {
   })
 }
 // 提前提醒年龄即将超限
-const notifyUpcomingAgeLimit = () => {
-  uni.showModal({
-    title: '提示',
-    cancelText: '不再提醒',
-    confirmText: '确定',
-    content: userStore?.currentUserInfo?.ageReminderInfo || '',
-    async success(res) {
-      if (res.cancel) {
-        const response = await stopSixtyReminderApi()
-        if (!response.data) return
-
-        uni.showToast({
-          title: '不再弹出年龄提醒',
-          icon: 'none',
-        })
-      }
-    },
+const notifyUpcomingAgeLimit = (): Promise<void> => {
+  return new Promise((resolve) => {
+    uni.showModal({
+      title: '提示',
+      cancelText: '不再提醒',
+      confirmText: '确定',
+      content: userStore?.currentUserInfo?.ageReminderInfo || '',
+      async success(res) {
+        if (res.cancel) {
+          const response = await stopSixtyReminderApi()
+          if (!response.data) return
+
+          uni.showToast({
+            title: '不再弹出年龄提醒',
+            icon: 'none',
+          })
+        }
+      },
+      complete() {
+        resolve()
+      },
+    })
   })
 }
 const getPltQuizResult = async (): Promise<boolean> => {
@@ -460,6 +517,24 @@ const isLivenessReminderVisible = ref(false)
 const livenessReminderType = ref<LivenessReminderType>('')
 const livenessExpireDateText = ref('')
 
+const canShowGdBanner = computed(
+  () =>
+    isHomePopupFlowReady.value &&
+    isPageActive.value &&
+    userStore.isLoggedIn &&
+    !isHonestAgreementVisible.value &&
+    !isLivenessReminderVisible.value &&
+    !isShowingPopupNotice.value &&
+    !userStore.currentPopupNotice
+)
+
+watch(isLivenessReminderVisible, (visible, wasVisible) => {
+  if (visible || !wasVisible || !shouldResumeAfterLiveness.value) return
+
+  shouldResumeAfterLiveness.value = false
+  scheduleLivenessResume()
+})
+
 const checkLivenessReminder = (userInfo: Record<string, any>): boolean => {
   if (userStore.hasShownLivenessReminder) return false
 

+ 4 - 0
src/services/modules/userInfo/index.ts

@@ -20,6 +20,10 @@ export const getPopupNoticesApi = () => {
   })
 }
 
+export const getGdBannerStatusApi = () => {
+  return http.get<boolean>('/admin/notification/status/GD-BANNER')
+}
+
 export const readPopupNoticeApi = (id: number) => {
   return http.put<unknown>(`/admin/user-filing/popup-notices/${id}/read`, undefined, {
     loading: true,

+ 32 - 0
src/stores/modules/user.ts

@@ -39,6 +39,13 @@ interface UserState {
    */
   hasRequestedPopupNotices: boolean
   popupNotices: PopupNoticeItem[]
+
+  /**
+   * 国开大活动弹窗:每次登录、每个企业只判断和展示一次
+   */
+  gdBannerRequestStatus: 'idle' | 'loading' | 'resolved'
+  gdBannerEligible: boolean
+  hasHandledGdBanner: boolean
 }
 
 export const useUserStore = defineStore('user', {
@@ -50,6 +57,9 @@ export const useUserStore = defineStore('user', {
     hasShownLivenessReminder: false,
     hasRequestedPopupNotices: false,
     popupNotices: [],
+    gdBannerRequestStatus: 'idle',
+    gdBannerEligible: false,
+    hasHandledGdBanner: false,
   }),
 
   getters: {
@@ -89,6 +99,7 @@ export const useUserStore = defineStore('user', {
       // 登录成功后立即重置,保证本次登录重新具备弹窗资格
       this.resetLivenessReminderShown()
       this.resetPopupNotices()
+      this.resetGdBanner()
 
       await this.getUserInfoByCode()
 
@@ -119,6 +130,7 @@ export const useUserStore = defineStore('user', {
       // 新企业使用独立的业务状态,切换后重新获取
       this.resetLivenessReminderShown()
       this.resetPopupNotices()
+      this.resetGdBanner()
 
       await this.getUserInfoByCode(payload.code)
 
@@ -177,6 +189,7 @@ export const useUserStore = defineStore('user', {
       // 切换身份后,活体状态需要重新判断
       this.resetLivenessReminderShown()
       this.resetPopupNotices()
+      this.resetGdBanner()
     },
 
     markLivenessReminderShown() {
@@ -204,6 +217,25 @@ export const useUserStore = defineStore('user', {
       this.popupNotices = []
     },
 
+    beginGdBannerRequest() {
+      this.gdBannerRequestStatus = 'loading'
+    },
+
+    completeGdBannerRequest(eligible: boolean) {
+      this.gdBannerEligible = eligible
+      this.gdBannerRequestStatus = 'resolved'
+    },
+
+    markGdBannerHandled() {
+      this.hasHandledGdBanner = true
+    },
+
+    resetGdBanner() {
+      this.gdBannerRequestStatus = 'idle'
+      this.gdBannerEligible = false
+      this.hasHandledGdBanner = false
+    },
+
     logout() {
       this.$reset()
     },