33 <div class =" flex-1 flex-col basis-1/2 m-4" >
44 <p >{{ useSiteConfig().name }} {{ routePath }}</p >
55 <h2 v-if =" firstPostPageForumName !== undefined" >{{ firstPostPageForumName }}吧</h2 >
6- <p v-if =" shouldShowImage " >右侧为查询结果中第一张图片(不一定来自第一条帖子)</p >
6+ <p v-if =" firstImageBase64 !== null " >右侧为查询结果中第一张图片(不一定来自第一条帖子)</p >
77 <p v-if =" currentQueryType !== 'postID'" class =" m-0" >下方为查询结果中第一条主题帖/回复帖/楼中楼</p >
88 <h1 v-if =" firstThreadTitle !== undefined" >{{ firstThreadTitle }}</h1 >
99 <NewlineToBr is="h3" :text =" firstPostContentTexts " wrapInSpan class="h-auto" />
1010 <template v-for =" author in [firstPostAuthor ]" >
1111 <div :key =" author.uid" v-if =" author !== undefined" class =" m-auto" >
12- <SkippableImage :src =" toUserPortraitImageUrl ( author . portrait ) " />
12+ <img v-if = " authorPortraitImageBase64 !== null " :src =" authorPortraitImageBase64 " />
1313 <span v-if =" author.name !== null" >{{ author.name }}</span >
1414 <span v-if =" author.displayName !== null" >{{ author.displayName }}</span >
1515 <span v-if =" author.name === null && author.displayName === null" >{{ author.uid }}</span >
1616 </div >
1717 </template >
1818 </div >
19- <div v-if =" firstImageUrl !== undefined && shouldShowImage " class =" flex-auto basis-1/4" >
20- <SkippableImage @ failToLoad = " shouldShowImage = false " :src =" firstImageUrl " class="h-screen object-contain" />
19+ <div v-if =" firstImageBase64 !== null " class =" flex-auto basis-1/4" >
20+ <img :src =" firstImageBase64 " class =" h-screen object-contain" />
2121 </div >
2222</div >
2323</template >
2424
2525<script setup lang="ts">
2626import type { UnwrapRef } from ' vue' ;
2727
28- const { firstImageUrl } = defineProps <{
28+ const { firstPostAuthor, firstImageUrl } = defineProps <{
2929 routePath: string ,
3030 currentQueryType: UnwrapRef <QueryFormDeps [' currentQueryType' ]>,
3131 firstPostPageForumName? : string ,
@@ -34,5 +34,32 @@ const { firstImageUrl } = defineProps<{
3434 firstPostAuthor? : User ,
3535 firstImageUrl? : string
3636}>();
37- const shouldShowImage = ref (firstImageUrl !== undefined );
37+ const fetchImageAsBase64 = async (url : string ): Promise <string | null > => {
38+ // eslint-disable-next-line @typescript-eslint/init-declarations
39+ let timeoutId: NodeJS .Timeout ;
40+ const abortController = new AbortController ();
41+
42+ return Promise .race ([
43+ new Promise <null >(resolve => { timeoutId = setTimeout (() => { resolve (null ) }, 5000 ) }),
44+ (async () => {
45+ try {
46+ // https://github.com/vercel/satori/issues/626#issuecomment-2401402201
47+ const response = await fetch (url , { signal: abortController .signal });
48+ const arrayBuffer = await response .arrayBuffer ();
49+ const base64 = Buffer .from (arrayBuffer ).toString (' base64' );
50+ const type = response .headers .get (' content-type' );
51+
52+ return ` data:${type ?? ' image/png' };base64,${base64 } ` ;
53+ } catch {
54+ return null ;
55+ }
56+ })()
57+ ]).finally (() => { clearTimeout (timeoutId ); abortController .abort () });
58+ };
59+ const authorPortraitImageBase64 = ref <string | null >(
60+ firstPostAuthor === undefined ? null : await fetchImageAsBase64 (toUserPortraitImageUrl (firstPostAuthor .portrait ))
61+ );
62+ const firstImageBase64 = ref <string | null >(
63+ firstImageUrl === undefined ? null : await fetchImageAsBase64 (firstImageUrl )
64+ );
3865 </script >
0 commit comments