@@ -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 >