PdfPreview.vue 3.84 KB
<!--
 * @Date: 2024-01-17
 * @Description: PDF预览组件
-->
<template>
    <view v-if="show" class="pdf-preview">
        <view class="mask" @tap="close"></view>
        <view class="panel">
            <view class="header">
                <text class="title">{{ title || 'PDF 预览' }}</text>
                <view class="close" @tap="close">
                    <IconFont name="del" size="16" color="#666" />
                </view>
            </view>

            <view class="body">
                <!-- #ifdef H5 -->
                <iframe
                    v-if="url"
                    class="pdf-iframe"
                    :src="url"
                    frameborder="0"
                    @load="on_loaded"
                />
                <view v-else class="empty">
                    <text class="empty-text">PDF 地址不能为空</text>
                </view>
                <!-- #endif -->

                <!-- #ifdef WEAPP -->
                <view class="empty">
                    <text class="empty-text">小程序不支持内嵌 PDF 预览</text>
                </view>
                <!-- #endif -->
            </view>

            <view v-if="loading" class="loading">
                <IconFont name="loading" size="24" class="animate-spin text-blue-600" />
                <text class="loading-text">加载中...</text>
            </view>
        </view>
    </view>
</template>

<script setup>
import { ref, watch } from 'vue'
import IconFont from '@/components/IconFont.vue'

const props = defineProps({
    show: {
        type: Boolean,
        default: false
    },
    url: {
        type: String,
        default: ''
    },
    title: {
        type: String,
        default: ''
    }
})

const emit = defineEmits(['update:show', 'onLoad'])

const loading = ref(false)

const close = () => {
    emit('update:show', false)
}

const on_loaded = () => {
    loading.value = false
    emit('onLoad', false)
}

watch(() => props.show, (new_show) => {
    if (new_show) {
        loading.value = true
    } else {
        loading.value = false
    }
}, { immediate: true })
</script>

<style lang="less" scoped>
.pdf-preview {
    position: fixed;
    inset: 0;
    z-index: 999;

    .mask {
        position: absolute;
        inset: 0;
        background: rgba(0, 0, 0, 0.5);
    }

    .panel {
        position: absolute;
        top: 0;
        right: 0;
        width: 100%;
        height: 100%;
        background: #fff;
        display: flex;
        flex-direction: column;
    }

    .header {
        height: 96rpx;
        padding: 0 24rpx;
        display: flex;
        align-items: center;
        justify-content: space-between;
        border-bottom: 2rpx solid #f1f5f9;

        .title {
            font-size: 30rpx;
            font-weight: 600;
            color: #111827;
        }

        .close {
            width: 64rpx;
            height: 64rpx;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 9999rpx;
            background: #f3f4f6;
        }
    }

    .body {
        flex: 1;
        position: relative;
    }

    .pdf-iframe {
        width: 100%;
        height: 100%;
        border: none;
    }

    .empty {
        width: 100%;
        height: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
        background: #f9fafb;

        .empty-text {
            font-size: 28rpx;
            color: #6b7280;
        }
    }

    .loading {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background: rgba(255, 255, 255, 0.9);
        border-radius: 16rpx;
        padding: 24rpx 32rpx;
        display: flex;
        align-items: center;
        gap: 16rpx;

        .loading-text {
            font-size: 28rpx;
            color: #333;
        }
    }
}
</style>