项目中心-支付模块
This commit is contained in:
247
src/components/FilePreviewDialog.vue
Normal file
247
src/components/FilePreviewDialog.vue
Normal file
@@ -0,0 +1,247 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { AppButton } from '@/components'
|
||||
import { useOssStore } from '@/stores/modules/oss'
|
||||
import { PREVIEW_TEXT_MAX_BYTES, formatBytes } from '@/utils/filePreview'
|
||||
|
||||
const ossStore = useOssStore()
|
||||
const { visible, file, kind, objectUrl, textContent, loading, error, truncated, totalBytes } = storeToRefs(ossStore)
|
||||
|
||||
function onClose(): void {
|
||||
ossStore.closePreview()
|
||||
}
|
||||
|
||||
function onDownload(): void {
|
||||
const target = file.value
|
||||
if (!target?.url) return
|
||||
const link = document.createElement('a')
|
||||
link.href = target.url
|
||||
link.download = target.name
|
||||
link.target = '_blank'
|
||||
link.rel = 'noopener'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="visible" class="preview-mask" @click.self="onClose">
|
||||
<div class="preview-dialog">
|
||||
<header class="preview-dialog__head">
|
||||
<span class="preview-dialog__name" :title="file?.name">{{ file?.name }}</span>
|
||||
<button class="preview-dialog__close" type="button" aria-label="关闭" @click="onClose">
|
||||
×
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div class="preview-dialog__body">
|
||||
<!-- 加载中 -->
|
||||
<div v-if="loading" class="preview-dialog__center">
|
||||
预览加载中…
|
||||
</div>
|
||||
|
||||
<!-- 加载失败 / 无 ossId 等错误 -->
|
||||
<div v-else-if="error" class="preview-dialog__center">
|
||||
<p class="preview-dialog__msg">{{ error }}</p>
|
||||
<AppButton v-if="file?.url" type="primary" @click="onDownload">
|
||||
下载文件
|
||||
</AppButton>
|
||||
</div>
|
||||
|
||||
<!-- 不支持内联预览(office 等) -->
|
||||
<div v-else-if="kind === 'unsupported'" class="preview-dialog__center">
|
||||
<p class="preview-dialog__msg">
|
||||
当前格式({{ (file?.name ?? '').split('.').pop() }})不支持在线预览,请下载后查看。
|
||||
</p>
|
||||
<AppButton v-if="file?.url" type="primary" @click="onDownload">
|
||||
下载文件
|
||||
</AppButton>
|
||||
</div>
|
||||
|
||||
<!-- 图片 -->
|
||||
<img
|
||||
v-else-if="kind === 'image' && objectUrl"
|
||||
:src="objectUrl"
|
||||
class="preview-dialog__image"
|
||||
alt="预览"
|
||||
>
|
||||
|
||||
<!-- PDF(blob 已带 application/pdf,iframe 内联渲染) -->
|
||||
<iframe
|
||||
v-else-if="kind === 'pdf' && objectUrl"
|
||||
:src="objectUrl"
|
||||
class="preview-dialog__frame"
|
||||
title="预览"
|
||||
/>
|
||||
|
||||
<!-- 视频 -->
|
||||
<video
|
||||
v-else-if="kind === 'video' && objectUrl"
|
||||
:src="objectUrl"
|
||||
class="preview-dialog__media"
|
||||
controls
|
||||
/>
|
||||
|
||||
<!-- 音频 -->
|
||||
<audio
|
||||
v-else-if="kind === 'audio' && objectUrl"
|
||||
:src="objectUrl"
|
||||
class="preview-dialog__media"
|
||||
controls
|
||||
/>
|
||||
|
||||
<!-- 文本(大文件截断预览,避免卡死浏览器) -->
|
||||
<div v-else-if="kind === 'text'" class="preview-dialog__text-wrap">
|
||||
<p v-if="truncated" class="preview-dialog__hint">
|
||||
<span>文件较大({{ formatBytes(totalBytes ?? 0) }}),仅预览前 {{ formatBytes(PREVIEW_TEXT_MAX_BYTES) }},完整内容请下载。</span>
|
||||
<AppButton v-if="file?.url" type="primary" @click="onDownload">下载文件</AppButton>
|
||||
</p>
|
||||
<pre class="preview-dialog__text">{{ textContent }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.preview-mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 2000;
|
||||
.flex(row, center);
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
padding: @spacing-lg;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.preview-dialog {
|
||||
width: min(960px, 100%);
|
||||
max-height: calc(100vh - @spacing-xxl);
|
||||
.flex(column, flex-start, stretch);
|
||||
border-radius: @radius-lg;
|
||||
background: @white;
|
||||
box-shadow: @shadow-lg;
|
||||
overflow: hidden;
|
||||
|
||||
&__head {
|
||||
.flex(row, space-between, center);
|
||||
flex-shrink: 0;
|
||||
gap: @spacing-sm;
|
||||
padding: @spacing-base @spacing-lg;
|
||||
border-bottom: 1px solid @border-color;
|
||||
}
|
||||
|
||||
&__name {
|
||||
min-width: 0;
|
||||
font-size: @font-size-base;
|
||||
font-weight: 600;
|
||||
color: @heading-color;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__close {
|
||||
flex-shrink: 0;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: none;
|
||||
border-radius: @radius-base;
|
||||
background: transparent;
|
||||
color: @text-color-secondary;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: @bg-color-light;
|
||||
color: @text-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
.flex(column, flex-start, stretch);
|
||||
padding: @spacing-base;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&__center {
|
||||
.flex(column, center, center);
|
||||
gap: @spacing-base;
|
||||
width: 100%;
|
||||
min-height: 240px;
|
||||
}
|
||||
|
||||
&__msg {
|
||||
font-size: @font-size-base;
|
||||
color: @text-color-secondary;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__image {
|
||||
max-width: 100%;
|
||||
max-height: 70vh;
|
||||
margin: auto;
|
||||
object-fit: contain;
|
||||
border-radius: @radius-base;
|
||||
}
|
||||
|
||||
&__frame {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
min-height: 60vh;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&__media {
|
||||
width: 100%;
|
||||
max-height: 70vh;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
&__text-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
&__hint {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: @spacing-sm;
|
||||
flex-shrink: 0;
|
||||
margin: 0 0 @spacing-sm;
|
||||
padding: @spacing-sm @spacing-base;
|
||||
border-radius: @radius-base;
|
||||
background: #fff7e6;
|
||||
border: 1px solid #ffd591;
|
||||
color: #ad6800;
|
||||
font-size: @font-size-sm;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
&__text {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: @spacing-base;
|
||||
border: 1px solid @border-color;
|
||||
border-radius: @radius-base;
|
||||
background: @bg-color-light;
|
||||
color: @text-color;
|
||||
font-size: @font-size-sm;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
max-height: 70vh;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user