feat:增加主要刷新
This commit is contained in:
parent
d9473fda7a
commit
a2d36cb7a0
@ -1,5 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import { getGlobalSocket } from '@/utils/socket'
|
import { getGlobalSocket } from '@/utils/socket'
|
||||||
|
import { emitAppReturnFromBackground } from '@/utils/backgroundRefreshBus.js'
|
||||||
|
|
||||||
|
// 记录上次隐藏时间的 storage key
|
||||||
|
const HIDE_TIME_KEY = 'app_last_hide_time'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
onLaunch: function() {
|
onLaunch: function() {
|
||||||
@ -8,11 +12,14 @@ export default {
|
|||||||
},
|
},
|
||||||
onShow: function() {
|
onShow: function() {
|
||||||
console.log('App Show')
|
console.log('App Show')
|
||||||
|
this.handleBackgroundReturn()
|
||||||
},
|
},
|
||||||
onHide: function() {
|
onHide: function() {
|
||||||
console.log('App Hide')
|
console.log('App Hide')
|
||||||
// 关闭所有 WebSocket 连接
|
// 关闭所有 WebSocket 连接
|
||||||
this.closeWebSocket()
|
this.closeWebSocket()
|
||||||
|
// 记录切到后台的时间,用于 onShow 时判断是否"从后台返回"
|
||||||
|
uni.setStorageSync(HIDE_TIME_KEY, Date.now())
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initWebSocket() {
|
initWebSocket() {
|
||||||
@ -27,6 +34,20 @@ export default {
|
|||||||
console.log('关闭全局 WebSocket 连接')
|
console.log('关闭全局 WebSocket 连接')
|
||||||
const globalSocket = getGlobalSocket()
|
const globalSocket = getGlobalSocket()
|
||||||
globalSocket.closeAll()
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
32
frontend/composables/useBackgroundRefresh.js
Normal file
32
frontend/composables/useBackgroundRefresh.js
Normal 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()
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -143,6 +143,7 @@ import Avatar from "./Avatar.vue";
|
|||||||
import DailyTasks from "@/pages/tasks/daily-tasks.vue";
|
import DailyTasks from "@/pages/tasks/daily-tasks.vue";
|
||||||
import GuideModal from "@/pages/tasks/GuideModal.vue";
|
import GuideModal from "@/pages/tasks/GuideModal.vue";
|
||||||
import { useBanner } from "@/pages/square/composables/useBanner.js";
|
import { useBanner } from "@/pages/square/composables/useBanner.js";
|
||||||
|
import { useBackgroundRefresh } from "@/composables/useBackgroundRefresh.js";
|
||||||
import { reportEvent } from "@/utils/task-api.js";
|
import { reportEvent } from "@/utils/task-api.js";
|
||||||
import { getEarningsSummaryApi } from "@/utils/api.js";
|
import { getEarningsSummaryApi } from "@/utils/api.js";
|
||||||
|
|
||||||
@ -333,6 +334,11 @@ onShow(() => {
|
|||||||
loadEarningsSummary();
|
loadEarningsSummary();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// App 从后台切回前台时,刷新 banner 活动(onShow 已经在做收益刷新,这里不重复)
|
||||||
|
useBackgroundRefresh(() => {
|
||||||
|
loadBannerActivities();
|
||||||
|
});
|
||||||
|
|
||||||
// 组件卸载时移除事件监听
|
// 组件卸载时移除事件监听
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
uni.$off("avatarUpdated", handleAvatarUpdate);
|
uni.$off("avatarUpdated", handleAvatarUpdate);
|
||||||
|
|||||||
@ -59,6 +59,7 @@ import ScatteredRanks from "./ScatteredRanks.vue";
|
|||||||
import { generateRingPositions } from "./config.js";
|
import { generateRingPositions } from "./config.js";
|
||||||
import { getHotRankingApi } from "@/utils/api.js";
|
import { getHotRankingApi } from "@/utils/api.js";
|
||||||
import { getAssetCoverRealUrl } from "@/utils/assetImageHelper.js";
|
import { getAssetCoverRealUrl } from "@/utils/assetImageHelper.js";
|
||||||
|
import { useBackgroundRefresh } from "@/composables/useBackgroundRefresh.js";
|
||||||
|
|
||||||
const emit = defineEmits(["cardClick"]);
|
const emit = defineEmits(["cardClick"]);
|
||||||
|
|
||||||
@ -108,6 +109,11 @@ onMounted(() => {
|
|||||||
loadData();
|
loadData();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// App 从后台切回前台时刷新星河榜单
|
||||||
|
useBackgroundRefresh(() => {
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
// 清理(如有 timer)
|
// 清理(如有 timer)
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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 loadBannerActivities = async () => {
|
||||||
const banners = ref([])
|
try {
|
||||||
|
const starId = uni.getStorageSync('star_id') || null
|
||||||
|
const res = await getActivityListApi(starId, 1, 10)
|
||||||
|
|
||||||
const loadBannerActivities = async () => {
|
if (res.code === 0 && res.data?.activities) {
|
||||||
try {
|
const activities = res.data.activities
|
||||||
const starId = uni.getStorageSync('star_id') || null
|
// 过滤掉已过期的活动
|
||||||
const res = await getActivityListApi(starId, 1, 10)
|
bannerActivities.value = activities.filter(item => item.status !== 'expired')
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[useBanner] 加载 banner 活动失败', e?.message ?? e)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 加载运营 banner(铸造活动)
|
// 加载运营 banner(铸造活动)
|
||||||
const loadBanners = async () => {
|
const loadBanners = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getMintingActivitiesApi(null, 1, 10)
|
const res = await getMintingActivitiesApi(null, 1, 10)
|
||||||
|
|
||||||
if (res.code === 0 && res.data?.activities) {
|
if (res.code === 0 && res.data?.activities) {
|
||||||
// 将后端数据映射为 BannerCarousel 约定的字段
|
// 将后端数据映射为 BannerCarousel 约定的字段
|
||||||
@ -53,16 +61,18 @@ export function useBanner() {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 无数据兜底
|
// 无数据兜底
|
||||||
if (banners.value.length === 0) {
|
if (banners.value.length === 0) {
|
||||||
banners.value = [...FALLBACK_BANNER]
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[useBanner] 加载运营 banner 失败', e?.message ?? e)
|
|
||||||
banners.value = [...FALLBACK_BANNER]
|
banners.value = [...FALLBACK_BANNER]
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[useBanner] 加载运营 banner 失败', e?.message ?? e)
|
||||||
|
banners.value = [...FALLBACK_BANNER]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useBanner() {
|
||||||
|
// 返回模块级单例的引用(API 不变,调用方零改动)
|
||||||
return {
|
return {
|
||||||
bannerActivities,
|
bannerActivities,
|
||||||
banners,
|
banners,
|
||||||
|
|||||||
@ -107,6 +107,7 @@ import CreationGrid from "./components/CreationGrid.vue";
|
|||||||
import StarGalaxy from "./components/StarGalaxy/index.vue";
|
import StarGalaxy from "./components/StarGalaxy/index.vue";
|
||||||
// import { clearSubStepProgress, shouldShowGuideStartModal } from '@/utils/guideConfig.js'
|
// import { clearSubStepProgress, shouldShowGuideStartModal } from '@/utils/guideConfig.js'
|
||||||
import { useBanner } from "./composables/useBanner.js";
|
import { useBanner } from "./composables/useBanner.js";
|
||||||
|
import { useBackgroundRefresh } from "@/composables/useBackgroundRefresh.js";
|
||||||
import { doubleTapLike } from "@/utils/likeHelper.js";
|
import { doubleTapLike } from "@/utils/likeHelper.js";
|
||||||
|
|
||||||
// ========== Store & User Info ==========
|
// ========== Store & User Info ==========
|
||||||
@ -268,6 +269,16 @@ onShow(() => {
|
|||||||
activeContentTab.value = "xinghe";
|
activeContentTab.value = "xinghe";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ========== 后台切回自动刷新 ==========
|
||||||
|
// App 从后台切回前台时,刷新 banner 数据。
|
||||||
|
// 三个 Tab 内容区中:
|
||||||
|
// - StarGalaxy:默认 Tab 且 v-if 命中后会保持挂载,需要在 StarGalaxy 内部独立接入 useBackgroundRefresh
|
||||||
|
// - HotCategoryBlock / CreationGrid:onShow 会切回 xinghe,自身被 v-if 卸载后再切回时重新挂载即重新拉数据
|
||||||
|
useBackgroundRefresh(() => {
|
||||||
|
loadBannerActivities();
|
||||||
|
loadBanners();
|
||||||
|
});
|
||||||
|
|
||||||
// onLoad((options) => {
|
// onLoad((options) => {
|
||||||
// if (options && 'guide_debug' in options) {
|
// if (options && 'guide_debug' in options) {
|
||||||
// const debugValue = options.guide_debug
|
// const debugValue = options.guide_debug
|
||||||
|
|||||||
BIN
frontend/static/rank/jindut-2.png
Normal file
BIN
frontend/static/rank/jindut-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
frontend/static/rank/jindut.png
Normal file
BIN
frontend/static/rank/jindut.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
45
frontend/utils/backgroundRefreshBus.js
Normal file
45
frontend/utils/backgroundRefreshBus.js
Normal 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()
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user