perf: 优化订阅刷新逻辑
This commit is contained in:
@@ -146,6 +146,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import type { BookingWithUser, BookingStatusHistory } from '@mp-pilates/shared'
|
||||
import { BookingStatus } from '@mp-pilates/shared'
|
||||
import { useBookingStore } from '../../stores/booking'
|
||||
@@ -165,6 +166,7 @@ const bookingStore = useBookingStore()
|
||||
const navBarHeight = ref('64px')
|
||||
const refreshing = ref(false)
|
||||
const loading = ref(false)
|
||||
const hasLoadedOnce = ref(false)
|
||||
|
||||
// ─── Filter state ─────────────────────────────────────────────────────────
|
||||
type FilterValue = string | null
|
||||
@@ -215,9 +217,9 @@ function formatTimelineText(h: BookingStatusHistory): string {
|
||||
}
|
||||
|
||||
// ─── Data loading ─────────────────────────────────────────────────────────
|
||||
async function loadBookings(append = false) {
|
||||
async function loadBookings(append = false, opts: { silent?: boolean } = {}) {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
if (!opts.silent) loading.value = true
|
||||
|
||||
try {
|
||||
const page = append ? currentPage.value + 1 : 1
|
||||
@@ -234,8 +236,9 @@ async function loadBookings(append = false) {
|
||||
totalCount.value = result.total
|
||||
hasMore.value = bookings.value.length < result.total
|
||||
|
||||
// Fetch history for each booking
|
||||
if (!append) {
|
||||
// Fetch history for each booking. Skip in silent mode — history is only
|
||||
// shown as a small inline preview, and the detail page has the full one.
|
||||
if (!append && !opts.silent) {
|
||||
await Promise.all(
|
||||
bookings.value.map((b) => fetchHistory(b.id)),
|
||||
)
|
||||
@@ -247,9 +250,9 @@ async function loadBookings(append = false) {
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Load bookings failed:', err)
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
if (!opts.silent) uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
if (!opts.silent) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,6 +394,24 @@ onMounted(() => {
|
||||
navBarHeight.value = `${getSystemLayout().navBarHeight}px`
|
||||
loadBookings(false)
|
||||
loadAllForStats()
|
||||
hasLoadedOnce.value = true
|
||||
})
|
||||
|
||||
// After returning from booking detail (where status may have changed),
|
||||
// re-sync the list with the server. Local row actions still call onRefresh
|
||||
// directly — this onShow is the safety net for the navigate-back path.
|
||||
// Uses silent mode so the list stays visible (no skeleton flash).
|
||||
onShow(() => {
|
||||
if (!hasLoadedOnce.value) return
|
||||
// Skip while a refresh is already in flight to avoid overlap.
|
||||
if (refreshing.value || loading.value) return
|
||||
Promise.all([
|
||||
loadBookings(false, { silent: true }),
|
||||
loadAllForStats(),
|
||||
]).catch(() => {
|
||||
// Errors are non-fatal here — list keeps showing stale data until
|
||||
// the next explicit refresh.
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -163,6 +163,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import type { BookingWithDetails } from '@mp-pilates/shared'
|
||||
import { BookingStatus } from '@mp-pilates/shared'
|
||||
import { useBookingStore } from '../../stores/booking'
|
||||
@@ -295,6 +296,8 @@ function formatDateDisplay(dateStr: string): string {
|
||||
}
|
||||
|
||||
// ─── Actions ──────────────────────────────────────────────
|
||||
const hasLoadedOnce = ref(false)
|
||||
|
||||
function selectTab(key: TabKey) {
|
||||
activeTab.value = key
|
||||
}
|
||||
@@ -351,7 +354,19 @@ onMounted(() => {
|
||||
const windowInfo = uni.getWindowInfo()
|
||||
const statusBarH = windowInfo.statusBarHeight ?? 20
|
||||
navBarHeight.value = `${statusBarH + Math.round(88 * windowInfo.windowWidth / 750)}px`
|
||||
bookingStore.fetchMyBookings()
|
||||
bookingStore.fetchMyBookings().then(() => {
|
||||
hasLoadedOnce.value = true
|
||||
})
|
||||
})
|
||||
|
||||
// After returning from booking detail (where status may have changed),
|
||||
// silently re-sync without flipping loading state — keeps the list visible
|
||||
// and avoids the skeleton flash. Store action `replaceBooking` already
|
||||
// keeps `myBookings` in sync for the common case; this is the safety net
|
||||
// for any state we didn't locally patch (e.g. server-side cascading fields).
|
||||
onShow(() => {
|
||||
if (!hasLoadedOnce.value) return
|
||||
bookingStore.fetchMyBookings(undefined, { silent: true })
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -44,13 +44,27 @@ export const useBookingStore = defineStore('booking', () => {
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a booking in `myBookings` by id. Preserves immutability: always
|
||||
* returns a new array reference so Vue's computed/watchers pick up the change.
|
||||
* If the booking isn't in the list (e.g. paginated out), leaves state untouched.
|
||||
*/
|
||||
function replaceBooking(updated: BookingWithDetails) {
|
||||
const idx = myBookings.value.findIndex((b) => b.id === updated.id)
|
||||
if (idx === -1) return
|
||||
const next = myBookings.value.slice()
|
||||
next[idx] = updated
|
||||
myBookings.value = next
|
||||
}
|
||||
|
||||
async function cancelBooking(bookingId: string) {
|
||||
const result = await put<BookingWithDetails>(`/booking/${bookingId}/cancel`)
|
||||
replaceBooking(result)
|
||||
return result
|
||||
}
|
||||
|
||||
async function fetchMyBookings(status?: string) {
|
||||
loadingBookings.value = true
|
||||
async function fetchMyBookings(status?: string, opts: { silent?: boolean } = {}) {
|
||||
if (!opts.silent) loadingBookings.value = true
|
||||
try {
|
||||
const params: Record<string, unknown> = status ? { status } : {}
|
||||
const paginated = await get<ServerPaginatedResult<BookingWithDetails>>('/booking/my', params)
|
||||
@@ -59,7 +73,7 @@ export const useBookingStore = defineStore('booking', () => {
|
||||
console.error('Fetch bookings failed:', err)
|
||||
myBookings.value = []
|
||||
} finally {
|
||||
loadingBookings.value = false
|
||||
if (!opts.silent) loadingBookings.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +120,7 @@ export const useBookingStore = defineStore('booking', () => {
|
||||
const result = await put<BookingWithDetails>(`/booking/${bookingId}/confirm`, {
|
||||
remark,
|
||||
})
|
||||
replaceBooking(result)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -113,6 +128,7 @@ export const useBookingStore = defineStore('booking', () => {
|
||||
const result = await put<BookingWithDetails>(`/booking/${bookingId}/complete`, {
|
||||
remark,
|
||||
})
|
||||
replaceBooking(result)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -120,6 +136,7 @@ export const useBookingStore = defineStore('booking', () => {
|
||||
const result = await put<BookingWithDetails>(`/booking/${bookingId}/noshow`, {
|
||||
remark,
|
||||
})
|
||||
replaceBooking(result)
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -159,5 +176,6 @@ export const useBookingStore = defineStore('booking', () => {
|
||||
fetchBookingHistory,
|
||||
fetchSlotById,
|
||||
fetchBookingById,
|
||||
replaceBooking,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user