perf: 优化订阅刷新逻辑
This commit is contained in:
@@ -146,6 +146,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { onShow } from '@dcloudio/uni-app'
|
||||||
import type { BookingWithUser, BookingStatusHistory } from '@mp-pilates/shared'
|
import type { BookingWithUser, BookingStatusHistory } from '@mp-pilates/shared'
|
||||||
import { BookingStatus } from '@mp-pilates/shared'
|
import { BookingStatus } from '@mp-pilates/shared'
|
||||||
import { useBookingStore } from '../../stores/booking'
|
import { useBookingStore } from '../../stores/booking'
|
||||||
@@ -165,6 +166,7 @@ const bookingStore = useBookingStore()
|
|||||||
const navBarHeight = ref('64px')
|
const navBarHeight = ref('64px')
|
||||||
const refreshing = ref(false)
|
const refreshing = ref(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
const hasLoadedOnce = ref(false)
|
||||||
|
|
||||||
// ─── Filter state ─────────────────────────────────────────────────────────
|
// ─── Filter state ─────────────────────────────────────────────────────────
|
||||||
type FilterValue = string | null
|
type FilterValue = string | null
|
||||||
@@ -215,9 +217,9 @@ function formatTimelineText(h: BookingStatusHistory): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─── Data loading ─────────────────────────────────────────────────────────
|
// ─── Data loading ─────────────────────────────────────────────────────────
|
||||||
async function loadBookings(append = false) {
|
async function loadBookings(append = false, opts: { silent?: boolean } = {}) {
|
||||||
if (loading.value) return
|
if (loading.value) return
|
||||||
loading.value = true
|
if (!opts.silent) loading.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const page = append ? currentPage.value + 1 : 1
|
const page = append ? currentPage.value + 1 : 1
|
||||||
@@ -234,8 +236,9 @@ async function loadBookings(append = false) {
|
|||||||
totalCount.value = result.total
|
totalCount.value = result.total
|
||||||
hasMore.value = bookings.value.length < result.total
|
hasMore.value = bookings.value.length < result.total
|
||||||
|
|
||||||
// Fetch history for each booking
|
// Fetch history for each booking. Skip in silent mode — history is only
|
||||||
if (!append) {
|
// shown as a small inline preview, and the detail page has the full one.
|
||||||
|
if (!append && !opts.silent) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
bookings.value.map((b) => fetchHistory(b.id)),
|
bookings.value.map((b) => fetchHistory(b.id)),
|
||||||
)
|
)
|
||||||
@@ -247,9 +250,9 @@ async function loadBookings(append = false) {
|
|||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Load bookings failed:', err)
|
console.error('Load bookings failed:', err)
|
||||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
if (!opts.silent) uni.showToast({ title: '加载失败', icon: 'none' })
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
if (!opts.silent) loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,6 +394,24 @@ onMounted(() => {
|
|||||||
navBarHeight.value = `${getSystemLayout().navBarHeight}px`
|
navBarHeight.value = `${getSystemLayout().navBarHeight}px`
|
||||||
loadBookings(false)
|
loadBookings(false)
|
||||||
loadAllForStats()
|
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>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -163,6 +163,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { onShow } from '@dcloudio/uni-app'
|
||||||
import type { BookingWithDetails } from '@mp-pilates/shared'
|
import type { BookingWithDetails } from '@mp-pilates/shared'
|
||||||
import { BookingStatus } from '@mp-pilates/shared'
|
import { BookingStatus } from '@mp-pilates/shared'
|
||||||
import { useBookingStore } from '../../stores/booking'
|
import { useBookingStore } from '../../stores/booking'
|
||||||
@@ -295,6 +296,8 @@ function formatDateDisplay(dateStr: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─── Actions ──────────────────────────────────────────────
|
// ─── Actions ──────────────────────────────────────────────
|
||||||
|
const hasLoadedOnce = ref(false)
|
||||||
|
|
||||||
function selectTab(key: TabKey) {
|
function selectTab(key: TabKey) {
|
||||||
activeTab.value = key
|
activeTab.value = key
|
||||||
}
|
}
|
||||||
@@ -351,7 +354,19 @@ onMounted(() => {
|
|||||||
const windowInfo = uni.getWindowInfo()
|
const windowInfo = uni.getWindowInfo()
|
||||||
const statusBarH = windowInfo.statusBarHeight ?? 20
|
const statusBarH = windowInfo.statusBarHeight ?? 20
|
||||||
navBarHeight.value = `${statusBarH + Math.round(88 * windowInfo.windowWidth / 750)}px`
|
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>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -44,13 +44,27 @@ export const useBookingStore = defineStore('booking', () => {
|
|||||||
return result
|
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) {
|
async function cancelBooking(bookingId: string) {
|
||||||
const result = await put<BookingWithDetails>(`/booking/${bookingId}/cancel`)
|
const result = await put<BookingWithDetails>(`/booking/${bookingId}/cancel`)
|
||||||
|
replaceBooking(result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchMyBookings(status?: string) {
|
async function fetchMyBookings(status?: string, opts: { silent?: boolean } = {}) {
|
||||||
loadingBookings.value = true
|
if (!opts.silent) loadingBookings.value = true
|
||||||
try {
|
try {
|
||||||
const params: Record<string, unknown> = status ? { status } : {}
|
const params: Record<string, unknown> = status ? { status } : {}
|
||||||
const paginated = await get<ServerPaginatedResult<BookingWithDetails>>('/booking/my', params)
|
const paginated = await get<ServerPaginatedResult<BookingWithDetails>>('/booking/my', params)
|
||||||
@@ -59,7 +73,7 @@ export const useBookingStore = defineStore('booking', () => {
|
|||||||
console.error('Fetch bookings failed:', err)
|
console.error('Fetch bookings failed:', err)
|
||||||
myBookings.value = []
|
myBookings.value = []
|
||||||
} finally {
|
} 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`, {
|
const result = await put<BookingWithDetails>(`/booking/${bookingId}/confirm`, {
|
||||||
remark,
|
remark,
|
||||||
})
|
})
|
||||||
|
replaceBooking(result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +128,7 @@ export const useBookingStore = defineStore('booking', () => {
|
|||||||
const result = await put<BookingWithDetails>(`/booking/${bookingId}/complete`, {
|
const result = await put<BookingWithDetails>(`/booking/${bookingId}/complete`, {
|
||||||
remark,
|
remark,
|
||||||
})
|
})
|
||||||
|
replaceBooking(result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +136,7 @@ export const useBookingStore = defineStore('booking', () => {
|
|||||||
const result = await put<BookingWithDetails>(`/booking/${bookingId}/noshow`, {
|
const result = await put<BookingWithDetails>(`/booking/${bookingId}/noshow`, {
|
||||||
remark,
|
remark,
|
||||||
})
|
})
|
||||||
|
replaceBooking(result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,5 +176,6 @@ export const useBookingStore = defineStore('booking', () => {
|
|||||||
fetchBookingHistory,
|
fetchBookingHistory,
|
||||||
fetchSlotById,
|
fetchSlotById,
|
||||||
fetchBookingById,
|
fetchBookingById,
|
||||||
|
replaceBooking,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user