perf: 优化进度条
This commit is contained in:
@@ -97,11 +97,16 @@ const instance = getCurrentInstance()
|
||||
let sequence = 0, openedAt = 0, timer: ReturnType<typeof setTimeout> | undefined
|
||||
async function load() {
|
||||
if (props.admin && !props.userId) return
|
||||
const seq = ++sequence; loading.value = true; error.value = ''
|
||||
if (!editor.value) { photoUrls.value = {}; comparison.value = []; selectedPhotos.value = [] }
|
||||
const seq = ++sequence
|
||||
// 仅在首次加载(archive 尚未就绪)时才切换 loading 态、清空本地缓存;
|
||||
// 后台刷新保留 photoUrls / comparison / selectedPhotos,避免图片被打回占位造成视觉抖动
|
||||
const isInitial = !archive.value
|
||||
if (isInitial) loading.value = true
|
||||
error.value = ''
|
||||
if (!editor.value && isInitial) { photoUrls.value = {}; comparison.value = []; selectedPhotos.value = [] }
|
||||
try { const result = await get<ProgressArchive>(base.value); if (seq === sequence) archive.value = result }
|
||||
catch (e) { if (seq === sequence) error.value = message(e) }
|
||||
finally { if (seq === sequence) loading.value = false }
|
||||
finally { if (seq === sequence && isInitial) loading.value = false }
|
||||
}
|
||||
function previewComparison(url: string) { uni.previewImage({ urls: comparison.value.map(p => p.url), current: url }) }
|
||||
function changeShared(event: Event) { shared.value = (event as unknown as { detail: { value: boolean } }).detail.value }
|
||||
@@ -157,6 +162,17 @@ function clearPhoto(id: string) { const next = { ...photoUrls.value }; delete ne
|
||||
async function preview(photo: ProgressPhotoRecord) {
|
||||
try { const { url } = await get<{ url: string }>(base.value + `/photos/${photo.id}/url`); photoUrls.value = { ...photoUrls.value, [photo.id]: url }; uni.previewImage({ urls: [url] }); if (timer) clearTimeout(timer); timer = setTimeout(() => { photoUrls.value = {}; comparison.value = [] }, 55000) } catch(e) { uni.showToast({ title: message(e), icon: 'none' }) }
|
||||
}
|
||||
// 后台拉取单张照片签名 URL:学员侧全部自动加载,馆主侧仅加载已授权照片。
|
||||
// 与 preview() 走同一条接口,便于复用 60s 签名失效与手动重试的占位逻辑。
|
||||
async function ensurePhotoUrl(photo: ProgressPhotoRecord) {
|
||||
if (photoUrls.value[photo.id]) return
|
||||
if (props.admin && !photo.consentedAt) return
|
||||
try {
|
||||
const { url } = await get<{ url: string }>(base.value + `/photos/${photo.id}/url`)
|
||||
photoUrls.value = { ...photoUrls.value, [photo.id]: url }
|
||||
if (timer) clearTimeout(timer); timer = setTimeout(() => { photoUrls.value = {}; comparison.value = [] }, 55000)
|
||||
} catch (e) { /* 占位保留,学员/馆主可点击重试 */ }
|
||||
}
|
||||
function selectPhoto(photo: ProgressPhotoRecord) { if (selectedPhotos.value.includes(photo.id)) selectedPhotos.value = selectedPhotos.value.filter(id => id !== photo.id); else if (selectedPhotos.value.length < 2) selectedPhotos.value = [...selectedPhotos.value, photo.id]; else uni.showToast({ title: '最多选择两张照片', icon: 'none' }) }
|
||||
async function compare() {
|
||||
try { comparison.value = await Promise.all(selectedPhotos.value.map(async id => { const { url } = await get<{ url: string }>(base.value + `/photos/${id}/url`); return { id, url, date: formatChinaDate(archive.value!.photos.find(p => p.id === id)!.recordedAt) } })); if (timer) clearTimeout(timer); timer = setTimeout(() => { comparison.value = []; photoUrls.value = {} }, 55000) } catch (e) { uni.showToast({ title: message(e), icon: 'none' }) }
|
||||
@@ -177,9 +193,15 @@ async function saveMilestone(n: number) {
|
||||
}
|
||||
watch(() => [props.userId, props.refreshKey], () => {
|
||||
if (editor.value || busy.value) { void load(); return }
|
||||
archive.value = null
|
||||
void load()
|
||||
}, { immediate: true })
|
||||
// 学员切到「照片」Tab 或档案刷新后回到照片 Tab 时,自动后台拉取签名 URL。
|
||||
// 这样学员无需点击「轻触查看照片」即可直接看到图片;馆主侧只加载已授权照片。
|
||||
watch([() => tab.value, () => archive.value?.photos], ([currentTab, photos]) => {
|
||||
if (currentTab === 'photos' && photos && photos.length) {
|
||||
void Promise.all(photos.map(ensurePhotoUrl))
|
||||
}
|
||||
}, { immediate: true })
|
||||
watch(() => props.bookingId, id => { if (id) { tab.value = 'notes'; openEditor('notes') } }, { immediate: true })
|
||||
onBeforeUnmount(() => { sequence++; if (timer) clearTimeout(timer) })
|
||||
</script>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<template><view class="page" :style="{ paddingTop: navBarHeight + 'px' }"><CustomNavBar title="成长档案" show-back /><MemberProgress admin :user-id="userId" :booking-id="bookingId" :refresh-key="refreshKey" /></view></template>
|
||||
<template><view class="page" :style="{ paddingTop: navBarHeight + 'px' }"><CustomNavBar title="成长档案" show-back /><MemberProgress admin :user-id="userId" :booking-id="bookingId" /></view></template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import CustomNavBar from '../../components/CustomNavBar.vue'
|
||||
import MemberProgress from '../../components/MemberProgress.vue'
|
||||
import { getSystemLayout } from '../../utils/system'
|
||||
const navBarHeight = getSystemLayout().navBarHeight
|
||||
const userId = ref(''), bookingId = ref(''), refreshKey = ref(0)
|
||||
const userId = ref(''), bookingId = ref('')
|
||||
onLoad(query => { userId.value = String(query?.userId || ''); bookingId.value = String(query?.bookingId || '') })
|
||||
onShow(() => { refreshKey.value++ })
|
||||
</script>
|
||||
<style scoped>.page{min-height:100vh;background:#fbf9f6;box-sizing:border-box;}</style>
|
||||
<style scoped>.page{min-height:100vh;background:#fbf9f6;box-sizing:border-box;}</style>
|
||||
@@ -1,12 +1,8 @@
|
||||
<template><view class="page" :style="{ paddingTop: navBarHeight + 'px' }"><CustomNavBar title="成长档案" show-back /><MemberProgress :refresh-key="refreshKey" /></view></template>
|
||||
<template><view class="page" :style="{ paddingTop: navBarHeight + 'px' }"><CustomNavBar title="成长档案" show-back /><MemberProgress /></view></template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import CustomNavBar from '../../components/CustomNavBar.vue'
|
||||
import MemberProgress from '../../components/MemberProgress.vue'
|
||||
import { getSystemLayout } from '../../utils/system'
|
||||
const navBarHeight = getSystemLayout().navBarHeight
|
||||
const refreshKey = ref(0)
|
||||
onShow(() => { refreshKey.value++ })
|
||||
</script>
|
||||
<style scoped>.page{min-height:100vh;background:#fbf9f6;box-sizing:border-box;}</style>
|
||||
<style scoped>.page{min-height:100vh;background:#fbf9f6;box-sizing:border-box;}</style>
|
||||
Reference in New Issue
Block a user