v1.2.1
1、api增加发布/调试的api域名 2、浏览的图片改为使用jpg格式 3、对联图片加载增加loading提示 4、东直门、弹窗、隆福寺、王府井模块动态加载的图片需要进行预加载处理
|
|
@ -4,7 +4,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// API 基础配置
|
// API 基础配置
|
||||||
const BASE_URL = 'http://xcsq.wxinh5.host'
|
const BASE_URL = process.env.NODE_ENV === 'production'
|
||||||
|
? 'https://xcsq.wxinh5.com'
|
||||||
|
: 'http://xcsq.wxinh5.host'
|
||||||
|
|
||||||
// 请求拦截器
|
// 请求拦截器
|
||||||
const requestInterceptor = (config) => {
|
const requestInterceptor = (config) => {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,18 @@
|
||||||
<div class="couplet-display">
|
<div class="couplet-display">
|
||||||
<!-- 显示海报图片(后端直接返回) -->
|
<!-- 显示海报图片(后端直接返回) -->
|
||||||
<div class="poster-image" v-if="couplet.image_url">
|
<div class="poster-image" v-if="couplet.image_url">
|
||||||
<img :src="couplet.image_url" alt="春联海报" style="width: 100%; max-width: 300px; height: auto;" />
|
<!-- 加载提示 -->
|
||||||
|
<div class="image-loading" v-if="isLoading">
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
<div class="loading-text">海报加载中...</div>
|
||||||
|
</div>
|
||||||
|
<img
|
||||||
|
:src="couplet.image_url"
|
||||||
|
alt="春联海报"
|
||||||
|
style="width: 100%; max-width: 300px; height: auto;"
|
||||||
|
@load="isLoading = false"
|
||||||
|
@error="isLoading = false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -25,6 +36,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: {
|
visible: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|
@ -40,6 +53,23 @@ const props = defineProps({
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['close'])
|
const emit = defineEmits(['close'])
|
||||||
|
|
||||||
|
// 图片加载状态
|
||||||
|
const isLoading = ref(true)
|
||||||
|
|
||||||
|
// 监听couplet变化,重新开始加载
|
||||||
|
watch(() => props.couplet.image_url, (newImageUrl) => {
|
||||||
|
if (newImageUrl) {
|
||||||
|
isLoading.value = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听visible变化,当显示时重置加载状态
|
||||||
|
watch(() => props.visible, (newVisible) => {
|
||||||
|
if (newVisible && props.couplet.image_url) {
|
||||||
|
isLoading.value = true
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
@ -80,11 +110,53 @@ const emit = defineEmits(['close'])
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.poster-image {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
.poster-image img {
|
.poster-image img {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 图片加载提示 */
|
||||||
|
.image-loading {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 4px solid #f3f3f3;
|
||||||
|
border-top: 4px solid #3498db;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
.share-hint {
|
.share-hint {
|
||||||
margin-bottom: 10rpx;
|
margin-bottom: 10rpx;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted, computed, nextTick, getCurrentInstance as vueGetCurrentInstance } from 'vue'
|
import { ref, onMounted, onUnmounted, computed, getCurrentInstance as vueGetCurrentInstance, watch } from 'vue'
|
||||||
import FuClickArea from './FuClickArea.vue'
|
import FuClickArea from './FuClickArea.vue'
|
||||||
|
import ImagePreloader from '@/utils/preload'
|
||||||
|
|
||||||
// 获取 Vue 实例
|
// 获取 Vue 实例
|
||||||
const instance = vueGetCurrentInstance()
|
const instance = vueGetCurrentInstance()
|
||||||
|
|
@ -19,9 +20,6 @@ const props = defineProps({
|
||||||
|
|
||||||
const emit = defineEmits(['collect-seal'])
|
const emit = defineEmits(['collect-seal'])
|
||||||
|
|
||||||
// 是否收集福印
|
|
||||||
const sealCollected = ref(false)
|
|
||||||
|
|
||||||
// 福字点击区域状态
|
// 福字点击区域状态
|
||||||
const fuClickAreaVisible = ref(true)
|
const fuClickAreaVisible = ref(true)
|
||||||
const sq3ImageVisible = ref(false)
|
const sq3ImageVisible = ref(false)
|
||||||
|
|
@ -38,10 +36,15 @@ const handleFuClick = () => {
|
||||||
emit('collect-seal')
|
emit('collect-seal')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 动态加载东直门场景图片
|
||||||
|
const getDzmImageUrl = (name) => {
|
||||||
|
return new URL(`/static/dzm/${name}`, import.meta.url).href
|
||||||
|
}
|
||||||
|
|
||||||
// 拖拽状态
|
// 拖拽状态
|
||||||
const isDragging = ref(false)
|
const isDragging = ref(false)
|
||||||
const showDuck = ref(true) // 调试时默认显示鸭子
|
const showDuck = ref(true) // 调试时默认显示鸭子
|
||||||
const deskImage = ref('/static/dzm/img_desk1.png')
|
const deskImage = ref(getDzmImageUrl('img_desk1.png'))
|
||||||
const showGuideElements = ref(true)
|
const showGuideElements = ref(true)
|
||||||
|
|
||||||
// Canvas 触摸是否禁用
|
// Canvas 触摸是否禁用
|
||||||
|
|
@ -112,6 +115,10 @@ const targetArea = ref({
|
||||||
// 动画帧 ID
|
// 动画帧 ID
|
||||||
const animationId = ref(null)
|
const animationId = ref(null)
|
||||||
|
|
||||||
|
// 图片加载状态
|
||||||
|
const imagesLoaded = ref(false)
|
||||||
|
const isPreloading = ref(false)
|
||||||
|
|
||||||
// 初始化 Canvas
|
// 初始化 Canvas
|
||||||
const initCanvas = () => {
|
const initCanvas = () => {
|
||||||
console.log('正在初始化 Canvas...')
|
console.log('正在初始化 Canvas...')
|
||||||
|
|
@ -135,7 +142,7 @@ const initCanvas = () => {
|
||||||
// 加载鸭子图片
|
// 加载鸭子图片
|
||||||
const loadDuckImage = () => {
|
const loadDuckImage = () => {
|
||||||
uni.getImageInfo({
|
uni.getImageInfo({
|
||||||
src: '/static/dzm/img_duck.png',
|
src: getDzmImageUrl('img_duck.png'),
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
console.log('鸭子图片加载成功:', res)
|
console.log('鸭子图片加载成功:', res)
|
||||||
duckImagePath.value = res.path
|
duckImagePath.value = res.path
|
||||||
|
|
@ -434,7 +441,7 @@ const handleTouchEnd = () => {
|
||||||
if (checkDuckInTarget()) {
|
if (checkDuckInTarget()) {
|
||||||
console.log('成功放入目标区域!')
|
console.log('成功放入目标区域!')
|
||||||
// 更换餐桌图片
|
// 更换餐桌图片
|
||||||
deskImage.value = '/static/dzm/img_desk2.png'
|
deskImage.value = getDzmImageUrl('img_desk2.png')
|
||||||
// 隐藏引导元素
|
// 隐藏引导元素
|
||||||
showGuideElements.value = false
|
showGuideElements.value = false
|
||||||
// 隐藏鸭子
|
// 隐藏鸭子
|
||||||
|
|
@ -460,6 +467,33 @@ const resetDuckPosition = () => {
|
||||||
console.log('鸭子已回归原始位置:', duckX.value, duckY.value)
|
console.log('鸭子已回归原始位置:', duckX.value, duckY.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 预加载图片
|
||||||
|
const preloadImages = async () => {
|
||||||
|
if (imagesLoaded.value || isPreloading.value) return
|
||||||
|
|
||||||
|
isPreloading.value = true
|
||||||
|
try {
|
||||||
|
// 需要预加载的图片列表
|
||||||
|
const imageUrls = [
|
||||||
|
getDzmImageUrl('img_duck.png'),
|
||||||
|
getDzmImageUrl('img_desk1.png'),
|
||||||
|
getDzmImageUrl('img_desk2.png'),
|
||||||
|
getDzmImageUrl('img_stove1.png'),
|
||||||
|
getDzmImageUrl('img_line.png'),
|
||||||
|
'/static/images/icon_hand.png'
|
||||||
|
]
|
||||||
|
|
||||||
|
// 使用 ImagePreloader 批量预加载图片
|
||||||
|
await ImagePreloader.preloadAll(imageUrls)
|
||||||
|
imagesLoaded.value = true
|
||||||
|
console.log('东直门场景图片预加载完成')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('图片预加载失败:', error)
|
||||||
|
} finally {
|
||||||
|
isPreloading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 开始拖拽(从触发区域开始)
|
// 开始拖拽(从触发区域开始)
|
||||||
const startDrag = (e) => {
|
const startDrag = (e) => {
|
||||||
console.log('开始拖拽', e)
|
console.log('开始拖拽', e)
|
||||||
|
|
@ -479,6 +513,13 @@ const startDrag = (e) => {
|
||||||
handleTouchStart(e)
|
handleTouchStart(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 监听激活状态变化,当组件激活时预加载图片
|
||||||
|
watch(() => props.active, async (newActive) => {
|
||||||
|
if (newActive) {
|
||||||
|
await preloadImages()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 页面挂载时的初始化
|
// 页面挂载时的初始化
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 添加动画类,触发入场动画
|
// 添加动画类,触发入场动画
|
||||||
|
|
@ -487,6 +528,9 @@ onMounted(() => {
|
||||||
container.classList.add('animate-in')
|
container.classList.add('animate-in')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 预加载图片
|
||||||
|
preloadImages()
|
||||||
|
|
||||||
// 调试时自动初始化 Canvas
|
// 调试时自动初始化 Canvas
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
initCanvas()
|
initCanvas()
|
||||||
|
|
@ -531,8 +575,8 @@ onUnmounted(() => {
|
||||||
|
|
||||||
<!-- 装饰图片 -->
|
<!-- 装饰图片 -->
|
||||||
<image :src="deskImage" alt="餐桌" class="deco-img desk-img" mode="widthFix" />
|
<image :src="deskImage" alt="餐桌" class="deco-img desk-img" mode="widthFix" />
|
||||||
<image src="/static/dzm/img_stove1.png" alt="灶台" class="deco-img stove-img" mode="widthFix" />
|
<image :src="getDzmImageUrl('img_stove1.png')" alt="灶台" class="deco-img stove-img" mode="widthFix" />
|
||||||
<image v-if="showGuideElements" src="/static/dzm/img_line.png" alt="线条" class="deco-img line-img" mode="widthFix" />
|
<image v-if="showGuideElements" :src="getDzmImageUrl('img_line.png')" alt="线条" class="deco-img line-img" mode="widthFix" />
|
||||||
<image v-if="showGuideElements" src="/static/images/icon_hand.png" alt="手势" class="deco-img hand-img" mode="widthFix" />
|
<image v-if="showGuideElements" src="/static/images/icon_hand.png" alt="手势" class="deco-img hand-img" mode="widthFix" />
|
||||||
|
|
||||||
<!-- Canvas 拖拽区域 -->
|
<!-- Canvas 拖拽区域 -->
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
import ImagePreloader from '@/utils/preload'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
isActive: {
|
isActive: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|
@ -133,7 +136,7 @@ const props = defineProps({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['lottery', 'couplet', 'restart', 'showCouplet'])
|
const emit = defineEmits(['lottery', 'couplet', 'showCouplet'])
|
||||||
|
|
||||||
const handleLottery = () => {
|
const handleLottery = () => {
|
||||||
// 如果已经提交过,直接提示
|
// 如果已经提交过,直接提示
|
||||||
|
|
@ -157,9 +160,44 @@ const handleCouplet = () => {
|
||||||
emit('couplet')
|
emit('couplet')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRestart = () => {
|
|
||||||
emit('restart')
|
|
||||||
|
// 预加载图片
|
||||||
|
const preloadImages = async () => {
|
||||||
|
try {
|
||||||
|
const imageUrls = [
|
||||||
|
// EndPage 图片
|
||||||
|
'/static/bg/bg_finish.jpg',
|
||||||
|
'/static/images/finish_title2.png',
|
||||||
|
'/static/images/finish_title.png',
|
||||||
|
'/static/images/gift1.png',
|
||||||
|
'/static/images/gift2.png',
|
||||||
|
'/static/images/gift3.png',
|
||||||
|
'/static/images/gift4.png',
|
||||||
|
'/static/images/gift5.png',
|
||||||
|
'/static/images/btn_lottery.png',
|
||||||
|
'/static/images/btn_ai.png',
|
||||||
|
// AICoupletForm 图片
|
||||||
|
'/static/info/couplet_info_box.png',
|
||||||
|
'/static/images/btn_submit.png',
|
||||||
|
'/static/images/btn_close.png',
|
||||||
|
// LotteryFormModal 图片
|
||||||
|
'/static/info/info_bg.jpg',
|
||||||
|
'/static/images/btn_back.png',
|
||||||
|
'/static/info/info_title.png',
|
||||||
|
'/static/info/info_tips.png'
|
||||||
|
]
|
||||||
|
await ImagePreloader.preloadAll(imageUrls)
|
||||||
|
console.log('EndPage 图片预加载完成')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('EndPage 图片预加载失败:', error)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时预加载图片
|
||||||
|
onMounted(() => {
|
||||||
|
preloadImages()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
@ -170,6 +208,7 @@ const handleRestart = () => {
|
||||||
|
|
||||||
.scene-section.active {
|
.scene-section.active {
|
||||||
/* 当前活动场景样式 */
|
/* 当前活动场景样式 */
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 背景图片层 */
|
/* 背景图片层 */
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ const handleOverlayClick = () => {
|
||||||
height: auto;
|
height: auto;
|
||||||
max-height: 600rpx;
|
max-height: 600rpx;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
border-radius: 12rpx;
|
border-radius: 20rpx;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed, watch } from 'vue'
|
||||||
import FuClickArea from './FuClickArea.vue'
|
import FuClickArea from './FuClickArea.vue'
|
||||||
import ImageGalleryModal from './ImageGalleryModal.vue'
|
import ImageGalleryModal from './ImageGalleryModal.vue'
|
||||||
|
import ImagePreloader from '@/utils/preload';
|
||||||
|
|
||||||
// 组件属性
|
// 组件属性
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|
@ -20,19 +21,45 @@ const props = defineProps({
|
||||||
// 组件事件
|
// 组件事件
|
||||||
const emit = defineEmits(['collect-seal'])
|
const emit = defineEmits(['collect-seal'])
|
||||||
|
|
||||||
// 是否收集福印
|
|
||||||
const sealCollected = ref(false)
|
|
||||||
|
|
||||||
// 福字点击区域状态
|
// 福字点击区域状态
|
||||||
const fuClickAreaVisible = ref(true)
|
const fuClickAreaVisible = ref(true)
|
||||||
const sq3ImageVisible = ref(false)
|
const sq3ImageVisible = ref(false)
|
||||||
|
|
||||||
|
// 图片加载状态
|
||||||
|
const imagesLoaded = ref(false)
|
||||||
|
const isPreloading = ref(false)
|
||||||
|
|
||||||
// 计算视差效果的偏移量
|
// 计算视差效果的偏移量
|
||||||
const parallaxOffset = computed(() => {
|
const parallaxOffset = computed(() => {
|
||||||
// 滚动位置的1/10作为视差偏移
|
// 滚动位置的1/10作为视差偏移
|
||||||
return props.scrollPosition * 0.1
|
return props.scrollPosition * 0.1
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 预加载图片函数
|
||||||
|
const preloadImages = async () => {
|
||||||
|
if (imagesLoaded.value || isPreloading.value) return
|
||||||
|
|
||||||
|
isPreloading.value = true
|
||||||
|
try {
|
||||||
|
// 提取所有图片地址
|
||||||
|
const imageUrls = lfsImages.map(img => img.src)
|
||||||
|
// 使用ImagePreloader批量预加载图片
|
||||||
|
await ImagePreloader.preloadAll(imageUrls)
|
||||||
|
imagesLoaded.value = true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('图片预加载失败:', error)
|
||||||
|
} finally {
|
||||||
|
isPreloading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听激活状态变化,当组件激活时预加载图片
|
||||||
|
watch(() => props.active, async (newActive) => {
|
||||||
|
if (newActive) {
|
||||||
|
await preloadImages()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 点击福字区域
|
// 点击福字区域
|
||||||
const handleFuClick = () => {
|
const handleFuClick = () => {
|
||||||
fuClickAreaVisible.value = false
|
fuClickAreaVisible.value = false
|
||||||
|
|
@ -42,7 +69,7 @@ const handleFuClick = () => {
|
||||||
|
|
||||||
// 动态加载图片
|
// 动态加载图片
|
||||||
const getImageUrl = (name) => {
|
const getImageUrl = (name) => {
|
||||||
return new URL(`/static/lfs/${name}.png`, import.meta.url).href
|
return new URL(`/static/lfs/${name}.jpg`, import.meta.url).href
|
||||||
}
|
}
|
||||||
|
|
||||||
// 图片浏览数据
|
// 图片浏览数据
|
||||||
|
|
@ -74,6 +101,11 @@ onMounted(() => {
|
||||||
if (container) {
|
if (container) {
|
||||||
container.classList.add('animate-in')
|
container.classList.add('animate-in')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果组件已经激活,立即预加载图片
|
||||||
|
if (props.active) {
|
||||||
|
preloadImages()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed, watch } from 'vue'
|
||||||
import FuClickArea from './FuClickArea.vue'
|
import FuClickArea from './FuClickArea.vue'
|
||||||
|
import ImagePreloader from '@/utils/preload';
|
||||||
|
|
||||||
// 组件属性
|
// 组件属性
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|
@ -19,19 +20,45 @@ const props = defineProps({
|
||||||
// 组件事件
|
// 组件事件
|
||||||
const emit = defineEmits(['collect-seal'])
|
const emit = defineEmits(['collect-seal'])
|
||||||
|
|
||||||
// 是否收集福印
|
|
||||||
const sealCollected = ref(false)
|
|
||||||
|
|
||||||
// 福字点击区域状态
|
// 福字点击区域状态
|
||||||
const fuClickAreaVisible = ref(true)
|
const fuClickAreaVisible = ref(true)
|
||||||
const sq3ImageVisible = ref(false)
|
const sq3ImageVisible = ref(false)
|
||||||
|
|
||||||
|
// 图片加载状态
|
||||||
|
const imagesLoaded = ref(false)
|
||||||
|
const isPreloading = ref(false)
|
||||||
|
|
||||||
// 计算视差效果的偏移量
|
// 计算视差效果的偏移量
|
||||||
const parallaxOffset = computed(() => {
|
const parallaxOffset = computed(() => {
|
||||||
// 滚动位置的1/10作为视差偏移
|
// 滚动位置的1/10作为视差偏移
|
||||||
return props.scrollPosition * 0.1
|
return props.scrollPosition * 0.1
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 预加载图片函数
|
||||||
|
const preloadImages = async () => {
|
||||||
|
if (imagesLoaded.value || isPreloading.value) return
|
||||||
|
|
||||||
|
isPreloading.value = true
|
||||||
|
try {
|
||||||
|
// 提取所有图片地址
|
||||||
|
const imageUrls = images.map(img => img.src)
|
||||||
|
// 使用ImagePreloader批量预加载图片
|
||||||
|
await ImagePreloader.preloadAll(imageUrls)
|
||||||
|
imagesLoaded.value = true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('图片预加载失败:', error)
|
||||||
|
} finally {
|
||||||
|
isPreloading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听激活状态变化,当组件激活时预加载图片
|
||||||
|
watch(() => props.active, async (newActive) => {
|
||||||
|
if (newActive) {
|
||||||
|
await preloadImages()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 点击福字区域
|
// 点击福字区域
|
||||||
const handleFuClick = () => {
|
const handleFuClick = () => {
|
||||||
fuClickAreaVisible.value = false
|
fuClickAreaVisible.value = false
|
||||||
|
|
@ -39,12 +66,19 @@ const handleFuClick = () => {
|
||||||
emit('collect-seal')
|
emit('collect-seal')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成带时间戳的图片URL(避免缓存)
|
||||||
|
const generateImageUrl = (name) => {
|
||||||
|
const url = new URL(`/static/wfj/${name}.jpg`, import.meta.url)
|
||||||
|
// 添加时间戳参数避免浏览器缓存
|
||||||
|
return url.href
|
||||||
|
}
|
||||||
|
|
||||||
// 图片浏览数据
|
// 图片浏览数据
|
||||||
const images = [
|
const images = [
|
||||||
{ src: '/static/wfj/img1.png', title: '' },
|
{ src: generateImageUrl('img1'), title: '' },
|
||||||
{ src: '/static/wfj/img2.png', title: '' },
|
{ src: generateImageUrl('img2'), title: '' },
|
||||||
{ src: '/static/wfj/img3.png', title: '' },
|
{ src: generateImageUrl('img3'), title: '' },
|
||||||
{ src: '/static/wfj/img4.png', title: '' }
|
{ src: generateImageUrl('img4'), title: '' }
|
||||||
]
|
]
|
||||||
const currentImageIndex = ref(0)
|
const currentImageIndex = ref(0)
|
||||||
|
|
||||||
|
|
@ -64,6 +98,11 @@ onMounted(() => {
|
||||||
if (container) {
|
if (container) {
|
||||||
container.classList.add('animate-in')
|
container.classList.add('animate-in')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果组件已经激活,立即预加载图片
|
||||||
|
if (props.active) {
|
||||||
|
preloadImages()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -97,6 +136,9 @@ onMounted(() => {
|
||||||
<!-- 图片浏览组件 -->
|
<!-- 图片浏览组件 -->
|
||||||
<div class="image-gallery">
|
<div class="image-gallery">
|
||||||
<div class="gallery-image-wrapper">
|
<div class="gallery-image-wrapper">
|
||||||
|
<div class="loading-overlay" v-if="isPreloading">
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
</div>
|
||||||
<div class="nav-btn prev-btn" @click="prevImage">
|
<div class="nav-btn prev-btn" @click="prevImage">
|
||||||
<img src="/static/images/btn_prev.png" alt="上一张" class="nav-icon" />
|
<img src="/static/images/btn_prev.png" alt="上一张" class="nav-icon" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -384,7 +426,7 @@ onMounted(() => {
|
||||||
.gallery-image {
|
.gallery-image {
|
||||||
width: 613rpx;
|
width: 613rpx;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-radius: 12rpx;
|
border-radius: 20%;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -430,4 +472,34 @@ onMounted(() => {
|
||||||
height: 80px;
|
height: 80px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 加载遮罩层 */
|
||||||
|
.loading-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 5;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载动画 */
|
||||||
|
.loading-spinner {
|
||||||
|
width: 60rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
border: 4rpx solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-top: 4rpx solid #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 354 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 357 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 430 KiB |
BIN
static/logo.png
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 564 KiB |
|
After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 568 KiB |
|
After Width: | Height: | Size: 192 KiB |
|
Before Width: | Height: | Size: 604 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 494 KiB |
|
|
@ -0,0 +1,128 @@
|
||||||
|
// utils/preload.js
|
||||||
|
export class ImagePreloader {
|
||||||
|
constructor() {
|
||||||
|
this.cache = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预加载单张图片
|
||||||
|
* @param {string} src - 图片地址
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
async preload(src) {
|
||||||
|
if (!src) return false;
|
||||||
|
|
||||||
|
// 检查缓存
|
||||||
|
if (this.cache.has(src)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// data URL 直接返回
|
||||||
|
if (src.startsWith('data:')) {
|
||||||
|
this.cache.set(src, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 本地图片和网络图片都需要真正加载
|
||||||
|
if (src.startsWith('/')) {
|
||||||
|
// 对于本地图片,使用Image对象加载
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => resolve();
|
||||||
|
img.onerror = reject;
|
||||||
|
img.src = src;
|
||||||
|
});
|
||||||
|
this.cache.set(src, true);
|
||||||
|
console.log('本地图片预加载成功:', src);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// 对于网络图片,使用uni.getImageInfo
|
||||||
|
await uni.getImageInfo({ src });
|
||||||
|
this.cache.set(src, true);
|
||||||
|
console.log('网络图片预加载成功:', src);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('图片预加载失败:', src, error);
|
||||||
|
this.cache.set(src, false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量预加载图片
|
||||||
|
* @param {string[]} srcList - 图片地址数组
|
||||||
|
* @param {number} concurrency - 并发数
|
||||||
|
* @returns {Promise<Array<{src: string, success: boolean}>>}
|
||||||
|
*/
|
||||||
|
async preloadAll(srcList, concurrency = 3) {
|
||||||
|
const results = [];
|
||||||
|
const queue = [...srcList];
|
||||||
|
|
||||||
|
// 执行并发下载
|
||||||
|
const workers = [];
|
||||||
|
for (let i = 0; i < concurrency; i++) {
|
||||||
|
workers.push(this.worker(queue, results));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(workers);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
async worker(queue, results) {
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const src = queue.shift();
|
||||||
|
if (src) {
|
||||||
|
const success = await this.preload(src);
|
||||||
|
results.push({ src, success });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理缓存
|
||||||
|
*/
|
||||||
|
clearCache() {
|
||||||
|
this.cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预加载图片并设置到 img 标签
|
||||||
|
* @param {string} src - 图片地址
|
||||||
|
* @param {number} timeout - 超时时间
|
||||||
|
* @returns {Promise<string>} 返回图片临时路径
|
||||||
|
*/
|
||||||
|
async preloadToTemp(src, timeout = 10000) {
|
||||||
|
if (src.startsWith('/') || src.startsWith('data:')) {
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
reject(new Error('图片下载超时'));
|
||||||
|
}, timeout);
|
||||||
|
|
||||||
|
uni.downloadFile({
|
||||||
|
url: src,
|
||||||
|
success: (res) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
if (res.statusCode === 200) {
|
||||||
|
const tempPath = res.tempFilePath;
|
||||||
|
this.cache.set(src, tempPath);
|
||||||
|
resolve(tempPath);
|
||||||
|
} else {
|
||||||
|
reject(new Error(`下载失败: ${res.statusCode}`));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (error) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建单例
|
||||||
|
export default new ImagePreloader();
|
||||||