| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- <template>
- <view class="img-wrap" :style="wrapStyle" @click="onClick">
- <image class="img" :src="displaySrc" :mode="mode" @load="onLoad" @error="onError" />
- <view v-if="loading && src" class="img-mask">
- <view class="spinner" />
- </view>
- </view>
- </template>
- <script setup>
- import { computed, ref, watch, onBeforeUnmount } from 'vue';
- import { withOssVersion } from '@/utils/ossCache'
- const props = defineProps({
- src: {
- type: String,
- default: ''
- },
- mode: {
- type: String,
- default: 'aspectFill'
- },
- radius: {
- type: String,
- default: '0'
- }
- });
- const emit = defineEmits(['click', 'load', 'error']);
- const loading = ref(true);
- let loadingTimer = null;
- const displaySrc = computed(() => withOssVersion(props.src))
- function clearLoadingTimer() {
- if (loadingTimer) {
- clearTimeout(loadingTimer);
- loadingTimer = null;
- }
- }
- function startLoadingFallback() {
- clearLoadingTimer();
- // H5 缓存命中场景偶发 load 事件未触发,超时后自动去除遮罩,避免图片“已加载但看不见”
- loadingTimer = setTimeout(() => {
- loading.value = false;
- loadingTimer = null;
- }, 1200);
- }
- watch(
- () => displaySrc.value,
- () => {
- loading.value = !!props.src;
- if (loading.value) {
- startLoadingFallback();
- } else {
- clearLoadingTimer();
- }
- },
- { immediate: true }
- );
- function onLoad(e) {
- clearLoadingTimer();
- loading.value = false;
- emit('load', e);
- }
- function onError(e) {
- clearLoadingTimer();
- loading.value = false;
- emit('error', e);
- }
- onBeforeUnmount(() => {
- clearLoadingTimer();
- });
- const wrapStyle = computed(() => ({
- borderRadius: props.radius
- }));
- function onClick(e) {
- emit('click', e);
- }
- </script>
- <style scoped lang="scss">
- .img-wrap {
- position: relative;
- width: 100%;
- height: 100%;
- overflow: hidden;
- }
- .img {
- display: block;
- width: 100%;
- height: 100%;
- }
- .img-mask {
- position: absolute;
- inset: 0;
- background: rgba(245, 248, 247, 0.82);
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .spinner {
- width: 36rpx;
- height: 36rpx;
- border-radius: 50%;
- border: 4rpx solid rgba(10, 159, 109, 0.22);
- border-top-color: #0a9f6d;
- animation: img-spin 0.9s linear infinite;
- }
- @keyframes img-spin {
- to {
- transform: rotate(360deg);
- }
- }
- </style>
|