fix(海报生成): 修复二维码加载问题并优化加载状态处理
替换 axios 为 fetch 加载二维码以避免拦截器导致的 CORS 问题 添加加载状态处理防止未准备好时渲染组件 同时监听二维码 URL 变化触发重新生成
Showing
2 changed files
with
41 additions
and
7 deletions
| ... | @@ -21,6 +21,7 @@ | ... | @@ -21,6 +21,7 @@ |
| 21 | <script setup> | 21 | <script setup> |
| 22 | import { ref, watch, nextTick, onMounted } from 'vue' | 22 | import { ref, watch, nextTick, onMounted } from 'vue' |
| 23 | import { showToast } from 'vant' | 23 | import { showToast } from 'vant' |
| 24 | +// import request from '@/utils/axios' // 移除 axios 依赖,改用 fetch | ||
| 24 | 25 | ||
| 25 | const props = defineProps({ | 26 | const props = defineProps({ |
| 26 | bgUrl: { | 27 | bgUrl: { |
| ... | @@ -150,13 +151,37 @@ const generatePoster = async () => { | ... | @@ -150,13 +151,37 @@ const generatePoster = async () => { |
| 150 | ctx.fillStyle = '#ffffff' | 151 | ctx.fillStyle = '#ffffff' |
| 151 | ctx.fillRect(0, 0, width, height) | 152 | ctx.fillRect(0, 0, width, height) |
| 152 | 153 | ||
| 154 | + // 处理 API 形式的二维码 URL (如 /admin/?m=srv...) | ||
| 155 | + // 如果是相对路径或特定API,使用 fetch 获取 blob 以通过鉴权并避免跨域/拦截器问题 | ||
| 156 | + let qrUrlToLoad = props.qrUrl | ||
| 157 | + let qrBlobUrl = null | ||
| 158 | + // 兼容完整 URL 和相对路径,只要包含特定特征或是相对路径 | ||
| 159 | + if (qrUrlToLoad && (qrUrlToLoad.startsWith('/') || qrUrlToLoad.includes('m=srv') || qrUrlToLoad.includes('http'))) { | ||
| 160 | + try { | ||
| 161 | + // 使用 fetch 替代 axios,避免拦截器自动添加 header 导致 CORS 预检失败 | ||
| 162 | + const res = await fetch(qrUrlToLoad) | ||
| 163 | + if (res.ok) { | ||
| 164 | + const blob = await res.blob() | ||
| 165 | + qrBlobUrl = URL.createObjectURL(blob) | ||
| 166 | + qrUrlToLoad = qrBlobUrl | ||
| 167 | + } | ||
| 168 | + } catch (err) { | ||
| 169 | + console.warn('QR Code API fetch failed, falling back to direct load', err) | ||
| 170 | + } | ||
| 171 | + } | ||
| 172 | + | ||
| 153 | // 2. 并行加载所有图片资源 | 173 | // 2. 并行加载所有图片资源 |
| 154 | const [bgImg, logoImg, qrImg] = await Promise.all([ | 174 | const [bgImg, logoImg, qrImg] = await Promise.all([ |
| 155 | loadImage(props.bgUrl), | 175 | loadImage(props.bgUrl), |
| 156 | loadImage(props.logoUrl), | 176 | loadImage(props.logoUrl), |
| 157 | - loadImage(props.qrUrl) | 177 | + loadImage(qrUrlToLoad) |
| 158 | ]) | 178 | ]) |
| 159 | 179 | ||
| 180 | + // 清理 Blob URL | ||
| 181 | + if (qrBlobUrl) { | ||
| 182 | + URL.revokeObjectURL(qrBlobUrl) | ||
| 183 | + } | ||
| 184 | + | ||
| 160 | // 3. 绘制背景图 (Object-Cover 效果) | 185 | // 3. 绘制背景图 (Object-Cover 效果) |
| 161 | if (bgImg) { | 186 | if (bgImg) { |
| 162 | // 计算裁剪 | 187 | // 计算裁剪 |
| ... | @@ -321,7 +346,7 @@ const generatePoster = async () => { | ... | @@ -321,7 +346,7 @@ const generatePoster = async () => { |
| 321 | } | 346 | } |
| 322 | } | 347 | } |
| 323 | 348 | ||
| 324 | -watch(() => props.bgUrl, () => { | 349 | +watch(() => [props.bgUrl, props.qrUrl], () => { |
| 325 | generatePoster() | 350 | generatePoster() |
| 326 | }) | 351 | }) |
| 327 | 352 | ... | ... |
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2025-12-23 15:50:59 | 2 | * @Date: 2025-12-23 15:50:59 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2025-12-24 20:35:33 | 4 | + * @LastEditTime: 2025-12-25 13:07:01 |
| 5 | * @FilePath: /mlaj/src/views/recall/PosterPage.vue | 5 | * @FilePath: /mlaj/src/views/recall/PosterPage.vue |
| 6 | * @Description: 分享海报页面 | 6 | * @Description: 分享海报页面 |
| 7 | --> | 7 | --> |
| ... | @@ -11,11 +11,15 @@ | ... | @@ -11,11 +11,15 @@ |
| 11 | <!-- Poster Container (Scrollable Area) --> | 11 | <!-- Poster Container (Scrollable Area) --> |
| 12 | <div class="flex-1 overflow-y-auto px-6 pt-6 pb-32 flex flex-col items-center"> | 12 | <div class="flex-1 overflow-y-auto px-6 pt-6 pb-32 flex flex-col items-center"> |
| 13 | <RecallPoster | 13 | <RecallPoster |
| 14 | + v-if="isReady" | ||
| 14 | :bg-url="posterBg" | 15 | :bg-url="posterBg" |
| 15 | :title="title" | 16 | :title="title" |
| 16 | :logo-url="logoUrl" | 17 | :logo-url="logoUrl" |
| 17 | :qr-url="qrCodeUrl" | 18 | :qr-url="qrCodeUrl" |
| 18 | /> | 19 | /> |
| 20 | + <div v-else class="w-full h-[62vh] min-h-[400px] flex items-center justify-center"> | ||
| 21 | + <van-loading color="#ffffff" vertical>加载中...</van-loading> | ||
| 22 | + </div> | ||
| 19 | </div> | 23 | </div> |
| 20 | 24 | ||
| 21 | <!-- Buttons (Fixed at Bottom) --> | 25 | <!-- Buttons (Fixed at Bottom) --> |
| ... | @@ -52,10 +56,11 @@ useTitle('分享海报'); | ... | @@ -52,10 +56,11 @@ useTitle('分享海报'); |
| 52 | // Assets | 56 | // Assets |
| 53 | const defaultBg = 'https://cdn.ipadbiz.cn/mlaj/images/test-bgg03.jpg?imageMogr2/thumbnail/800x/strip/quality/80' | 57 | const defaultBg = 'https://cdn.ipadbiz.cn/mlaj/images/test-bgg03.jpg?imageMogr2/thumbnail/800x/strip/quality/80' |
| 54 | const logoUrl = 'https://cdn.ipadbiz.cn/mlaj/recall/poster/kai@2x.png' | 58 | const logoUrl = 'https://cdn.ipadbiz.cn/mlaj/recall/poster/kai@2x.png' |
| 55 | -const qrCodeUrl = 'https://cdn.ipadbiz.cn/mlaj/recall/poster/%E4%BA%8C%E7%BB%B4%E7%A0%81@2x.png' | ||
| 56 | 59 | ||
| 57 | // State | 60 | // State |
| 58 | const posterBg = ref(defaultBg) | 61 | const posterBg = ref(defaultBg) |
| 62 | +const qrCodeUrl = ref('') | ||
| 63 | +const isReady = ref(false) | ||
| 59 | 64 | ||
| 60 | /** | 65 | /** |
| 61 | * 获取文件哈希(与七牛云ETag一致) | 66 | * 获取文件哈希(与七牛云ETag一致) |
| ... | @@ -163,15 +168,19 @@ const title = ref($route.query.title || '活动海报') | ... | @@ -163,15 +168,19 @@ const title = ref($route.query.title || '活动海报') |
| 163 | 168 | ||
| 164 | onMounted(async () => { | 169 | onMounted(async () => { |
| 165 | if (stu_uid.value && campaign_id.value) { | 170 | if (stu_uid.value && campaign_id.value) { |
| 166 | - const { data } = await getPosterAPI({ | 171 | + const { code, data } = await getPosterAPI({ |
| 167 | stu_uid: stu_uid.value, | 172 | stu_uid: stu_uid.value, |
| 168 | campaign_id: campaign_id.value | 173 | campaign_id: campaign_id.value |
| 169 | }) | 174 | }) |
| 170 | 175 | ||
| 171 | - if (data) { | 176 | + if (code) { |
| 172 | posterBg.value = data.background_image + '?imageMogr2/thumbnail/800x/strip/quality/80' || defaultBg | 177 | posterBg.value = data.background_image + '?imageMogr2/thumbnail/800x/strip/quality/80' || defaultBg |
| 173 | - qrCodeUrl.value = data.qrcode | 178 | + qrCodeUrl.value = data?.qrcode || '' |
| 179 | + isReady.value = true | ||
| 174 | } | 180 | } |
| 181 | + } else { | ||
| 182 | + // 如果没有参数,也设置为 ready,显示默认背景 | ||
| 183 | + isReady.value = true | ||
| 175 | } | 184 | } |
| 176 | }) | 185 | }) |
| 177 | </script> | 186 | </script> | ... | ... |
-
Please register or login to post a comment