feat:增加主要刷新

This commit is contained in:
zheng020 2026-06-16 15:19:11 +08:00
parent d9473fda7a
commit a2d36cb7a0
9 changed files with 158 additions and 27 deletions

View File

@ -1,5 +1,9 @@
<script>
import { getGlobalSocket } from '@/utils/socket'
import { emitAppReturnFromBackground } from '@/utils/backgroundRefreshBus.js'
// storage key
const HIDE_TIME_KEY = 'app_last_hide_time'
export default {
onLaunch: function() {
@ -8,11 +12,14 @@ export default {
},
onShow: function() {
console.log('App Show')
this.handleBackgroundReturn()
},
onHide: function() {
console.log('App Hide')
// WebSocket
this.closeWebSocket()
// onShow ""
uni.setStorageSync(HIDE_TIME_KEY, Date.now())
},
methods: {
initWebSocket() {
@ -27,6 +34,20 @@ export default {
console.log('关闭全局 WebSocket 连接')
const globalSocket = getGlobalSocket()
globalSocket.closeAll()
},
/**
* 处理"从后台切回前台"事件
* 通过 storage 中的上次隐藏时间判断本次 onShow 是否由后台返回触发
* 若是则通知所有订阅了 useBackgroundRefresh 的页面执行刷新
*/
handleBackgroundReturn() {
const lastHide = uni.getStorageSync(HIDE_TIME_KEY) || 0
if (!lastHide) return
// onShow
uni.removeStorageSync(HIDE_TIME_KEY)
//
if (Date.now() - lastHide < 0) return
emitAppReturnFromBackground()
}
}
}

View File

@ -0,0 +1,32 @@
import { onMounted, onBeforeUnmount } from 'vue'
import { onAppReturnFromBackground } from '@/utils/backgroundRefreshBus.js'
/**
* 页面启用从后台切回时自动刷新能力
*
* 仅当 App 从后台切回前台时触发不会因页面间普通跳转而触发
* App.vue 统一调度页面只需关心自己的刷新逻辑
*
* @param {Function} refreshFn 刷新逻辑通常就是重新拉数据的函数
*
* @example
* // 在 <script setup> 中:
* useBackgroundRefresh(() => {
* fetchList()
* })
*/
export function useBackgroundRefresh(refreshFn) {
if (typeof refreshFn !== 'function') {
throw new Error('useBackgroundRefresh: refreshFn must be a function')
}
let unregister = null
onMounted(() => {
unregister = onAppReturnFromBackground(refreshFn)
})
onBeforeUnmount(() => {
if (typeof unregister === 'function') unregister()
})
}

View File

@ -143,6 +143,7 @@ import Avatar from "./Avatar.vue";
import DailyTasks from "@/pages/tasks/daily-tasks.vue";
import GuideModal from "@/pages/tasks/GuideModal.vue";
import { useBanner } from "@/pages/square/composables/useBanner.js";
import { useBackgroundRefresh } from "@/composables/useBackgroundRefresh.js";
import { reportEvent } from "@/utils/task-api.js";
import { getEarningsSummaryApi } from "@/utils/api.js";
@ -333,6 +334,11 @@ onShow(() => {
loadEarningsSummary();
});
// App banner onShow
useBackgroundRefresh(() => {
loadBannerActivities();
});
//
onUnmounted(() => {
uni.$off("avatarUpdated", handleAvatarUpdate);

View File

@ -59,6 +59,7 @@ import ScatteredRanks from "./ScatteredRanks.vue";
import { generateRingPositions } from "./config.js";
import { getHotRankingApi } from "@/utils/api.js";
import { getAssetCoverRealUrl } from "@/utils/assetImageHelper.js";
import { useBackgroundRefresh } from "@/composables/useBackgroundRefresh.js";
const emit = defineEmits(["cardClick"]);
@ -108,6 +109,11 @@ onMounted(() => {
loadData();
});
// App
useBackgroundRefresh(() => {
loadData();
});
onUnmounted(() => {
// timer
});

View File

@ -11,32 +11,40 @@ const FALLBACK_BANNER = [
}
]
export function useBanner() {
// 旧的活动 banner(square 顶部)
const bannerActivities = ref([])
/**
* 模块级共享状态单例
*
* 背景square.vue pages/components/Header.vue 都会调 useBanner()
* 旧实现每次调用都 new ref导致两个组件各持一份独立的 bannerActivities
* 状态可能不一致
*
* 改单例后所有调用 useBanner() 的地方拿到的是同一对 ref
* 任一处调 loadBannerActivities() / loadBanners() 都会同步刷新所有订阅者
*
* 不会把数据搬到 Vuex原因是 banner 数据目前只有这两个使用方没必要全局 store
*/
const bannerActivities = ref([])
const banners = ref([])
// 新的运营 banner(铸造活动)
const banners = ref([])
const loadBannerActivities = async () => {
try {
const starId = uni.getStorageSync('star_id') || null
const res = await getActivityListApi(starId, 1, 10)
const loadBannerActivities = async () => {
try {
const starId = uni.getStorageSync('star_id') || null
const res = await getActivityListApi(starId, 1, 10)
if (res.code === 0 && res.data?.activities) {
const activities = res.data.activities
// 过滤掉已过期的活动
bannerActivities.value = activities.filter(item => item.status !== 'expired')
}
} catch (e) {
console.error('[useBanner] 加载 banner 活动失败', e?.message ?? e)
if (res.code === 0 && res.data?.activities) {
const activities = res.data.activities
// 过滤掉已过期的活动
bannerActivities.value = activities.filter(item => item.status !== 'expired')
}
} catch (e) {
console.error('[useBanner] 加载 banner 活动失败', e?.message ?? e)
}
}
// 加载运营 banner(铸造活动)
const loadBanners = async () => {
try {
const res = await getMintingActivitiesApi(null, 1, 10)
// 加载运营 banner(铸造活动)
const loadBanners = async () => {
try {
const res = await getMintingActivitiesApi(null, 1, 10)
if (res.code === 0 && res.data?.activities) {
// 将后端数据映射为 BannerCarousel 约定的字段
@ -53,16 +61,18 @@ export function useBanner() {
}))
}
// 无数据兜底
if (banners.value.length === 0) {
banners.value = [...FALLBACK_BANNER]
}
} catch (e) {
console.error('[useBanner] 加载运营 banner 失败', e?.message ?? e)
// 无数据兜底
if (banners.value.length === 0) {
banners.value = [...FALLBACK_BANNER]
}
} catch (e) {
console.error('[useBanner] 加载运营 banner 失败', e?.message ?? e)
banners.value = [...FALLBACK_BANNER]
}
}
export function useBanner() {
// 返回模块级单例的引用API 不变,调用方零改动)
return {
bannerActivities,
banners,

View File

@ -107,6 +107,7 @@ import CreationGrid from "./components/CreationGrid.vue";
import StarGalaxy from "./components/StarGalaxy/index.vue";
// import { clearSubStepProgress, shouldShowGuideStartModal } from '@/utils/guideConfig.js'
import { useBanner } from "./composables/useBanner.js";
import { useBackgroundRefresh } from "@/composables/useBackgroundRefresh.js";
import { doubleTapLike } from "@/utils/likeHelper.js";
// ========== Store & User Info ==========
@ -268,6 +269,16 @@ onShow(() => {
activeContentTab.value = "xinghe";
});
// ========== ==========
// App banner
// Tab
// - StarGalaxy Tab v-if StarGalaxy useBackgroundRefresh
// - HotCategoryBlock / CreationGridonShow xinghe v-if
useBackgroundRefresh(() => {
loadBannerActivities();
loadBanners();
});
// onLoad((options) => {
// if (options && 'guide_debug' in options) {
// const debugValue = options.guide_debug

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -0,0 +1,45 @@
/**
* 后台返回事件总线
*
* 触发方App.vue onShow 中检测到"从后台切回前台"时调用
* emitAppReturnFromBackground()
* 订阅方页面通过 composables/useBackgroundRefresh.js 注册回调
*
* 为什么不用 uni.$emit / uni.$on
* 1. 事件名是字符串缺乏约束容易和其他业务事件撞名
* 2. 集中在一处便于后续加日志节流灰度等横切逻辑
*/
const listeners = new Set()
/**
* 注册后台返回监听
* @param {Function} fn 回调函数
* @returns {Function} 取消监听的函数
*/
export function onAppReturnFromBackground(fn) {
if (typeof fn !== 'function') return () => {}
listeners.add(fn)
return () => listeners.delete(fn)
}
/**
* 触发后台返回事件通知所有订阅者
*/
export function emitAppReturnFromBackground() {
listeners.forEach(fn => {
try {
fn()
} catch (e) {
// 单个监听者异常不影响其他监听者
console.error('[backgroundRefreshBus] listener error:', e)
}
})
}
/**
* 清空所有监听仅用于测试或强制重置
*/
export function clearAppReturnFromBackgroundListeners() {
listeners.clear()
}