代码由Doubao-seed-code生成,以下是我的prompt:
- 实现一个二次封装的轮播图组件代码开发,并给出使用示例
- 组件图片获取存在时效限制,先从CDN返回的图片直接展示,失败返回或者超过2000ms还没有返回的图片,将用户无感知得重试2-3次;后续再次失败,则从自己的远程服务器;再获取不到不重试走默认兜底逻辑
- 图片先加载直接进行展示,不需要Promise.all等待所有图片结果加载完成之后,再全部展示;如果有图片暂时没加载出来,显示占位符或者骨架屏
- 图片的格式优先webp等高性能格式;如果浏览器不支持webp格式,则选择png/jpg等自输入样式兜底;
- 轮播图的组件基于element plus 开发
- 使用Ts进行编写
不过体感同一个prompt在同一个模型下生成的代码不一定一致,细节上需要开发人员关注并调整。
特地在prompt里面突出Promise.all,主要还是因为trae生成的代码总是使用Promise.all返回图片,但我希望图片先获得先直接展示。但是如果不在prompt里面添加提示词“不需要Promise.all等待所有图片结果加载完成之后,再全部展示”,模型无法正确理解“图片先加载直接进行展示”的意思。
<template>
<el-carousel :interval="5000" type="card" height="400px">
<el-carousel-item v-for="(image, index) in processedImages" :key="index">
<img
:src="image.src"
:alt="`Slide ${index + 1}`"
class="carousel-image"
/>
</el-carousel-item>
</el-carousel>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
interface ImageItem {
cdn: string
remote: string
fallback: string
suffix: string
}
interface ProcessedImageItem extends ImageItem {
src: string
}
const props = defineProps({
images: {
type: Array as () => ImageItem[],
required: true
}
})
const processedImages = ref<ProcessedImageItem[]>([])
// 检测浏览器是否支持 webp 格式
const checkWebpSupport = (): Promise<boolean> => {
return new Promise((resolve) => {
const img = new Image()
img.onload = () => resolve(true)
img.onerror = () => resolve(false)
img.src = 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA='
})
}
// 尝试加载图片,带超时和重试机制
const loadImageWithTimeout = (url: string, timeout: number = 2000): Promise<string> => {
return new Promise<string>((resolve, reject) => {
const img = new Image()
const timer = setTimeout(() => reject(new Error('Image load timeout')), timeout)
img.onload = () => {
clearTimeout(timer)
resolve(url)
}
img.onerror = () => {
clearTimeout(timer)
reject(new Error('Image load failed'))
}
img.src = url
})
}
// 尝试加载图片,按优先级顺序
const loadImage = async (image: ImageItem): Promise<string> => {
const supportsWebp = await checkWebpSupport()
// 生成不同格式的图片 URL
const getUrlByFormat = (url: string): string => {
if (supportsWebp) {
return url
} else {
// 替换 webp 为 png 或 jpg
return url.replace('.webp', image.suffix)
}
}
// 按优先级尝试加载图片
const cdnUrl = getUrlByFormat(image.cdn)
const remoteUrl = getUrlByFormat(image.remote)
const fallbackUrl = image.fallback
// 尝试 CDN 图片,最多重试 3 次
const maxRetries = 3
for (let i = 0; i < maxRetries; i++) {
try {
await loadImageWithTimeout(cdnUrl)
return cdnUrl
} catch (error) {
console.log(`Failed to load CDN image (attempt ${i + 1}/${maxRetries}): ${error.message}`)
// 无感知重试,不抛出错误
}
}
// CDN 失败,尝试远程服务器
try {
await loadImageWithTimeout(remoteUrl)
return remoteUrl
} catch (error) {
console.log(`Failed to load remote image: ${error.message}`)
}
// 远程服务器失败,尝试兜底图片
try {
await loadImageWithTimeout(fallbackUrl)
return fallbackUrl
} catch (error) {
console.log(`Failed to load fallback image: ${error.message}`)
}
// 所有尝试都失败,返回默认兜底图片
return 'https://via.placeholder.com/800x400?text=Image+Not+Available'
}
// 处理所有图片,实现渐进式加载
const processImages = (): void => {
// 初始化 processedImages,使用占位符
processedImages.value = props.images.map(image => ({
...image,
src: 'https://via.placeholder.com/800x400?text=Loading...'
}))
// 并行加载所有图片
props.images.forEach((image, index) => {
loadImage(image).then(src => {
// 图片加载完成后更新对应项
processedImages.value[index] = {
...image,
src
}
})
})
}
onMounted(() => {
processImages()
})
</script>
页面使用案例:
<template>
<div class="app-container">
<h1>轮播图组件测试</h1>
<carousel-component :images="images" />
</div>
</template>
<script setup lang="ts">
import CarouselComponent from './components/CarouselComponent.vue'
interface ImageItem {
cdn: string
remote: string
fallback: string
suffix: string
}
const images: ImageItem[] = [
{
cdn: 'https://cdn.example.com/image1.webp',
remote: 'https://example.com/image1.webp',
fallback: 'https://example.com/default1.jpg',
suffix: '.png'
},
{
cdn: 'https://cdn.example.com/image2.webp',
remote: 'https://example.com/image2.webp',
fallback: 'https://example.com/default2.jpg',
suffix: '.png'
},
{
cdn: 'https://cdn.example.com/image3.webp',
remote: 'https://example.com/image3.webp',
fallback: 'https://example.com/default3.jpg',
suffix: '.png'
}
]
</script>
知识点解析:
Q1:如何识别到浏览器支持webp格式
A1: 主流的方法有:
- 加载一张最基础的base64的webp图片,如果加载成功则说明浏览器支持webp格式,如果加载失败就说明不支持,最基础也是最常用的方法
const checkWebpSupport = (): Promise<boolean> => {
return new Promise((resolve) => {
const img = new Image()
img.onload = () => resolve(true)
img.onerror = () => resolve(false)
img.src = 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA='
})
}
//正式使用时获取promise的结果
const supportsWebp = await checkWebpSupport()
// 生成不同格式的图片 URL
const getUrlByFormat = (url: string): string => {
if (supportsWebp) {
return url
} else {
// 替换 webp 为 png 或 jpg
return url.replace('.webp', image.suffix)
}
}
- 建立一张canvas,导成webp的格式,如果能正常导出,说明支持webp;如果失败到处说明不支持,需要建立dom节点
function isSupportWebPSync() {
const canvas = document.createElement('canvas');
// 尝试把 canvas 导出成 webP 格式
const dataURL = canvas.toDataURL('image/webp');
// 如果返回的字符串包含 webp 说明支持
return dataURL.indexOf('data:image/webp') === 0;
}
如果浏览器支持 WebP,会返回:
data:image/webp;base64,...
不支持,浏览器会自动降级成 png,返回:
data:image/png;base64,...
3. navigator.mediaCapabilities提供查询功能,但是navigator这个API本身只在部分浏览器有效
Q: 如何实现加载CDN失败则重试2-3次;再次失败则走到本地服务器;依然失败则走到兜底方案?
A:使用Promise抛出错误,trycatch捕获错误的方式
// 尝试加载图片,带超时和重试机制
const loadImageWithTimeout = (url: string, timeout: number = 2000): Promise<string> => {
return new Promise<string>((resolve, reject) => {
const img = new Image()
//添加setTimeout进行时间限制
const timer = setTimeout(() => reject(new Error('Image load timeout')), timeout)
img.onload = () => {
//及时加载则清除定时器,并且返回url
clearTimeout(timer)
resolve(url)
}
img.onerror = () => {
//加载失败则清除定时器,并且抛出错误,用于跟后续的trycatch进行联动
clearTimeout(timer)
reject(new Error('Image load failed'))
}
img.src = url
})
}
// 按优先级尝试加载图片
const cdnUrl = getUrlByFormat(image.cdn)
const remoteUrl = getUrlByFormat(image.remote)
const fallbackUrl = image.fallback
// 尝试 CDN 图片,最多重试 3 次
const maxRetries = 3
for (let i = 0; i < maxRetries; i++) {
try {
await loadImageWithTimeout(cdnUrl)
//如果cdnUrl获取数据,则直接抛出结果,不会再执行到后面远程服务器以及兜底的逻辑
return cdnUrl
} catch (error) {
//弹出错误,并且继续循环,直到都失败,继续执行后续远程服务器的逻辑
console.log(`Failed to load CDN image (attempt ${i + 1}/${maxRetries}): ${error.message}`)
// 无感知重试,不抛出错误
}
}
// CDN 失败,尝试远程服务器
try {
await loadImageWithTimeout(remoteUrl)
return remoteUrl
} catch (error) {
console.log(`Failed to load remote image: ${error.message}`)
}
// 远程服务器失败,尝试兜底图片
try {
await loadImageWithTimeout(fallbackUrl)
return fallbackUrl
} catch (error) {
console.log(`Failed to load fallback image: ${error.message}`)
}
// 所有尝试都失败,返回默认兜底图片
return 'https://via.placeholder.com/800x400?text=Image+Not+Available'
- CDN加载机制
远程CDN文件有两种链接地址:
(1)写死的通用库地址,比如vue3.6的地址
(2)需要根据文件名动态生成的地址,比如图片包含content-hash名的文件