feat:修改存在的问题
This commit is contained in:
846
src/views/conferencingRoom/components/fileUpload/browseFile.vue
Normal file
846
src/views/conferencingRoom/components/fileUpload/browseFile.vue
Normal file
@@ -0,0 +1,846 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog
|
||||
v-model="dialogFormVisible"
|
||||
:title="title"
|
||||
width="80%"
|
||||
:before-close="handleClose"
|
||||
class="file-preview-dialog"
|
||||
>
|
||||
<div class="preview-container">
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-container">
|
||||
<el-icon class="is-loading" size="48">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
<p>{{ loadingText }}</p>
|
||||
<!-- <p v-if="convertTaskId" class="task-id">任务ID: {{ convertTaskId }}</p> -->
|
||||
</div>
|
||||
|
||||
<!-- 转换状态 -->
|
||||
<div v-else-if="converting" class="loading-container">
|
||||
<el-icon class="is-loading" size="48">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
<p>{{ conversionMessage }}</p>
|
||||
<!-- <p class="task-id">任务ID: {{ convertTaskId }}</p> -->
|
||||
</div>
|
||||
|
||||
<!-- 下载状态 -->
|
||||
<div v-else-if="downloading" class="loading-container">
|
||||
<el-icon class="is-loading" size="48">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
<p>正在下载文件,请稍候...</p>
|
||||
<p class="download-progress" v-if="downloadProgress > 0">
|
||||
下载进度: {{ downloadProgress }}%
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<div v-else-if="error" class="error-container">
|
||||
<el-icon size="48" color="#F56C6C">
|
||||
<CircleClose />
|
||||
</el-icon>
|
||||
<p>文件预览失败</p>
|
||||
<p class="error-message">{{ errorMessage }}</p>
|
||||
<el-button type="primary" @click="retryPreview">重试</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 文件预览内容 -->
|
||||
<div v-else class="file-content">
|
||||
<!-- 图片预览 -->
|
||||
<div v-if="isImage" class="image-preview">
|
||||
<img :src="previewUrl" :alt="fileName" @load="handleImageLoad" />
|
||||
</div>
|
||||
|
||||
<!-- PDF预览 - 使用 vue-pdf-embed -->
|
||||
<div v-else-if="isPdf" class="pdf-preview">
|
||||
<div class="pdf-controls" v-if="pageCount > 0">
|
||||
<el-button-group>
|
||||
<el-button :disabled="currentPage <= 1" @click="previousPage">
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
上一页
|
||||
</el-button>
|
||||
<el-button>
|
||||
{{ currentPage }} / {{ pageCount }}
|
||||
</el-button>
|
||||
<el-button :disabled="currentPage >= pageCount" @click="nextPage">
|
||||
下一页
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
|
||||
<div class="pdf-viewer-container">
|
||||
<VuePdfEmbed
|
||||
:source="pdfSource"
|
||||
:page="currentPage"
|
||||
:scale="scale"
|
||||
@loaded="handlePdfLoaded"
|
||||
@rendered="handlePdfRendered"
|
||||
@error="handlePdfError"
|
||||
class="pdf-viewer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视频预览 -->
|
||||
<div v-else-if="isVideo" class="video-preview">
|
||||
<video controls :src="previewUrl" class="video-player">
|
||||
您的浏览器不支持视频播放
|
||||
</video>
|
||||
</div>
|
||||
|
||||
<!-- 音频预览 -->
|
||||
<div v-else-if="isAudio" class="audio-preview">
|
||||
<audio controls :src="previewUrl" class="audio-player">
|
||||
您的浏览器不支持音频播放
|
||||
</audio>
|
||||
</div>
|
||||
|
||||
<!-- 文本预览 -->
|
||||
<div v-else-if="isText" class="text-preview">
|
||||
<pre>{{ textContent }}</pre>
|
||||
</div>
|
||||
|
||||
<!-- Office文档预览(转换后) -->
|
||||
<div v-else-if="isConvertedOffice" class="office-preview">
|
||||
<!-- 转换后的Office文件也是PDF,使用相同的PDF预览器 -->
|
||||
<div class="pdf-controls" v-if="pageCount > 0">
|
||||
<el-button-group>
|
||||
<el-button :disabled="currentPage <= 1" @click="previousPage">
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
上一页
|
||||
</el-button>
|
||||
<el-button>
|
||||
{{ currentPage }} / {{ pageCount }}
|
||||
</el-button>
|
||||
<el-button :disabled="currentPage >= pageCount" @click="nextPage">
|
||||
下一页
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
|
||||
<div class="pdf-viewer-container">
|
||||
<VuePdfEmbed
|
||||
:source="pdfSource"
|
||||
:page="currentPage"
|
||||
:scale="scale"
|
||||
@loaded="handlePdfLoaded"
|
||||
@rendered="handlePdfRendered"
|
||||
@error="handlePdfError"
|
||||
class="pdf-viewer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 不支持预览的文件类型 -->
|
||||
<div v-else class="unsupported-preview">
|
||||
<el-icon size="64" color="#909399">
|
||||
<Document />
|
||||
</el-icon>
|
||||
<p>不支持在线预览此文件类型</p>
|
||||
<p class="file-name">{{ fileName }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="close">
|
||||
关闭
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, getCurrentInstance, onUnmounted, nextTick, onMounted } from 'vue'
|
||||
import { convertFileApi, getConvertStatusApi } from '@/api/conferencingRoom'
|
||||
import { ElMessage ,ElMessageBox} from 'element-plus'
|
||||
import {
|
||||
Loading,
|
||||
CircleClose,
|
||||
Document,
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
} from '@element-plus/icons-vue'
|
||||
import VuePdfEmbed from 'vue-pdf-embed'
|
||||
import { mqttClient } from "@/utils/mqtt.js";
|
||||
import { emitter } from "@/utils/bus.js";
|
||||
|
||||
// 定义props
|
||||
const props = defineProps({
|
||||
fileType: {
|
||||
type: Array,
|
||||
default: () => ["pdf", "png", "jpg", "jpeg", "gif", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "mp4", "mp3"],
|
||||
},
|
||||
roomId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
// 定义emits
|
||||
const emit = defineEmits(['fetch-data'])
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const enumType = ["doc", "docx", "xls", "xlsx", "ppt", "pptx"]
|
||||
|
||||
// 响应式数据
|
||||
const dialogFormVisible = ref(false)
|
||||
const title = ref('')
|
||||
const loading = ref(false)
|
||||
const converting = ref(false)
|
||||
const downloading = ref(false)
|
||||
const downloadProgress = ref(0)
|
||||
const loadingText = ref('正在加载...')
|
||||
const conversionMessage = ref('正在转换文件...')
|
||||
const error = ref(false)
|
||||
const errorMessage = ref('')
|
||||
const previewUrl = ref('')
|
||||
const fileName = ref('')
|
||||
const textContent = ref('')
|
||||
const currentFileData = ref(null)
|
||||
const convertTaskId = ref('')
|
||||
|
||||
// PDF相关状态
|
||||
const pdfSource = ref('')
|
||||
const currentPage = ref(1)
|
||||
const pageCount = ref(0)
|
||||
const scale = ref(1.0)
|
||||
const pdfDocument = ref(null)
|
||||
|
||||
// MQTT相关
|
||||
const isMqttConnected = ref(false)
|
||||
|
||||
|
||||
// 计算属性
|
||||
const isImage = computed(() => {
|
||||
const ext = fileName.value.split('.').pop().toLowerCase()
|
||||
return ['png', 'jpg', 'jpeg', 'gif'].includes(ext)
|
||||
})
|
||||
|
||||
const isPdf = computed(() => {
|
||||
const ext = fileName.value.split('.').pop().toLowerCase()
|
||||
return ext === 'pdf'
|
||||
})
|
||||
|
||||
const isVideo = computed(() => {
|
||||
const ext = fileName.value.split('.').pop().toLowerCase()
|
||||
return ['mp4'].includes(ext)
|
||||
})
|
||||
|
||||
const isAudio = computed(() => {
|
||||
const ext = fileName.value.split('.').pop().toLowerCase()
|
||||
return ['mp3'].includes(ext)
|
||||
})
|
||||
|
||||
const isText = computed(() => {
|
||||
const ext = fileName.value.split('.').pop().toLowerCase()
|
||||
return ext === 'txt'
|
||||
})
|
||||
|
||||
const isOffice = computed(() => {
|
||||
const ext = fileName.value.split('.').pop().toLowerCase()
|
||||
return enumType.includes(ext)
|
||||
})
|
||||
|
||||
const isConvertedOffice = computed(() => {
|
||||
return previewUrl.value && previewUrl.value.endsWith('.pdf') && isOffice.value
|
||||
})
|
||||
|
||||
// 组件挂载时初始化MQTT连接
|
||||
onMounted(async () => {
|
||||
})
|
||||
emitter.on('subscribeToFileConversionStatusTopic',subscribeToFileConversionStatusTopic)
|
||||
emitter.on('fileUploadStatus',fileUploadStatus)
|
||||
emitter.on('subscribeToFilePreviewTopic',subscribeToFilePreviewTopic)
|
||||
|
||||
function subscribeToFileConversionStatusTopic(data){
|
||||
try {
|
||||
const userId = JSON.parse(sessionStorage.getItem('userData'))?.uid
|
||||
if (!userId) {
|
||||
console.error('用户ID不存在')
|
||||
return
|
||||
}
|
||||
const topic = `xsynergy/room/${data.roomId}/file/${userId}/conversion_status`
|
||||
mqttClient.subscribe(topic, handlePdfMessage)
|
||||
|
||||
} catch (error) {
|
||||
console.error('订阅pdf转换事件失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
function subscribeToFilePreviewTopic(data){
|
||||
try {
|
||||
const topic = `xsynergy/room/${data.roomId}/file/preview`
|
||||
mqttClient.subscribe(topic, handleFileUploadMessage)
|
||||
} catch (error) {
|
||||
console.error('订阅文件上传事件失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function fileUploadStatus(data){
|
||||
if(!dialogFormVisible.value){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化MQTT连接 pdf转换成功
|
||||
async function initMqttConnection() {
|
||||
try {
|
||||
if (isMqttConnected.value) return
|
||||
const clientId = `PdfConversion_${Date.now()}`
|
||||
await mqttClient.connect(clientId)
|
||||
isMqttConnected.value = true
|
||||
// 订阅主题
|
||||
subscribeToPdfConversionTopic()
|
||||
} catch (error) {
|
||||
console.error('MQTT连接失败:', error)
|
||||
ElMessage.error('文件转换服务连接失败')
|
||||
}
|
||||
}
|
||||
|
||||
function subscribeToPdfConversionTopic() {
|
||||
try {
|
||||
const userId = JSON.parse(sessionStorage.getItem('userData'))?.uid
|
||||
if (!userId) {
|
||||
console.error('用户ID不存在')
|
||||
return
|
||||
}
|
||||
const topic = `xsynergy/room/${props.roomId}/file/${userId}/conversion_status`
|
||||
mqttClient.subscribe(topic, handlePdfMessage)
|
||||
|
||||
} catch (error) {
|
||||
console.error('订阅pdf转换事件失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
function handleFileUploadMessage(payload, topic){
|
||||
try {
|
||||
const messageStr = payload.toString()
|
||||
const data = JSON.parse(messageStr)
|
||||
emitter.emit('fileUploadStatus')
|
||||
if(dialogFormVisible.value){
|
||||
// 显示确认对话框
|
||||
ElMessageBox.confirm(
|
||||
`用户${data.user_uid}上传了${data.file_name}文件,是否立即预览?`,
|
||||
'文件更新提示',
|
||||
{
|
||||
confirmButtonText: '预览',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
closeOnClickModal: false,
|
||||
closeOnPressEscape: false,
|
||||
showClose: false
|
||||
}
|
||||
).then(() => {
|
||||
// 用户点击"预览"
|
||||
resetPreviewState()
|
||||
getPreviewFileUrl(data)
|
||||
ElMessage({
|
||||
message: '已切换到新文件预览',
|
||||
type: 'success'
|
||||
})
|
||||
}).catch(() => {
|
||||
// 用户点击"取消"
|
||||
ElMessage({
|
||||
message: '已取消预览新文件,继续查看当前文件',
|
||||
type: 'info'
|
||||
})
|
||||
})
|
||||
// resetPreviewState()
|
||||
// getPreviewFileUrl(data)
|
||||
} else {
|
||||
ElMessage({
|
||||
message: `用户${data.user_uid}上传了${data.file_name}文件`,
|
||||
type: 'info',
|
||||
})
|
||||
showEdit(data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('处理转换状态消息失败:', error)
|
||||
handleError('处理转换状态失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 新增重置预览状态的方法
|
||||
function resetPreviewState() {
|
||||
loading.value = true
|
||||
converting.value = false
|
||||
downloading.value = false
|
||||
downloadProgress.value = 0
|
||||
error.value = false
|
||||
previewUrl.value = ''
|
||||
textContent.value = ''
|
||||
resetPdfState()
|
||||
|
||||
// 强制重新渲染
|
||||
nextTick(() => {
|
||||
// 确保DOM更新
|
||||
})
|
||||
}
|
||||
|
||||
function handlePdfMessage(payload, topic){
|
||||
try {
|
||||
const messageStr = payload.toString()
|
||||
const data = JSON.parse(messageStr)
|
||||
switch (data.status) {
|
||||
case 'converting':
|
||||
break
|
||||
case 'completed':
|
||||
getConvertedFile(data.task_id)
|
||||
break
|
||||
case 'failed':
|
||||
break
|
||||
default:
|
||||
console.warn('未知的转换状态:', data.status)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('处理转换状态消息失败:', error)
|
||||
handleError('处理转换状态失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 获取转换后的文件
|
||||
const getConvertedFile = async (taskId) => {
|
||||
try {
|
||||
if (!taskId) {
|
||||
throw new Error('任务ID不存在')
|
||||
}
|
||||
const fileRes = await getConvertStatusApi(taskId,props.roomId)
|
||||
} catch (err) {
|
||||
console.error('获取转换文件失败:', err)
|
||||
handleError('获取转换文件失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 修改 getPreviewFileUrl 方法,确保状态正确更新
|
||||
async function getPreviewFileUrl(file){
|
||||
fileName.value = file.file_name || file.source_url.split('/').pop()
|
||||
const fileExt = fileName.value.split('.').pop().toLowerCase()
|
||||
try {
|
||||
// 根据文件类型处理转换后的文件
|
||||
if (fileExt === 'pdf' || enumType.includes(fileExt)) {
|
||||
// PDF和Office文档使用PDF预览器
|
||||
await loadPdfFile(file.preview_url || file.file_url)
|
||||
} else if (fileExt === 'txt') {
|
||||
// 文本文件
|
||||
await loadTextFile(file.preview_url || file.file_url)
|
||||
} else if (['png', 'jpg', 'jpeg', 'gif'].includes(fileExt)) {
|
||||
// 图片文件直接预览
|
||||
previewUrl.value = file.preview_url || file.file_url
|
||||
// 确保图片重新加载
|
||||
nextTick(() => {
|
||||
loading.value = false
|
||||
})
|
||||
} else if (fileExt === 'mp4') {
|
||||
// 视频文件
|
||||
previewUrl.value = file.preview_url || file.file_url
|
||||
nextTick(() => {
|
||||
loading.value = false
|
||||
})
|
||||
} else if (fileExt === 'mp3') {
|
||||
// 音频文件
|
||||
previewUrl.value = file.preview_url || file.file_url
|
||||
nextTick(() => {
|
||||
loading.value = false
|
||||
})
|
||||
} else {
|
||||
// 其他文件类型
|
||||
previewUrl.value = file.preview_url || file.file_url
|
||||
nextTick(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载预览文件失败:', error)
|
||||
handleError('加载预览文件失败', error)
|
||||
}
|
||||
}
|
||||
// 显示弹框
|
||||
const showEdit = async (data) => {
|
||||
// 重置状态
|
||||
resetPreviewState()
|
||||
title.value = '文件预览'
|
||||
dialogFormVisible.value = true
|
||||
currentFileData.value = data
|
||||
// fileName.value = data.file_name || data.source_url.split('/').pop()
|
||||
|
||||
await getPreviewFileUrl(data)
|
||||
}
|
||||
|
||||
// 加载PDF文件
|
||||
const loadPdfFile = async (fileUrl) => {
|
||||
try {
|
||||
loading.value = false
|
||||
downloading.value = true
|
||||
downloadProgress.value = 0
|
||||
|
||||
const response = await fetch(fileUrl)
|
||||
if (!response.ok) {
|
||||
throw new Error('文件下载失败')
|
||||
}
|
||||
|
||||
const contentLength = response.headers.get('content-length')
|
||||
const total = parseInt(contentLength, 10)
|
||||
let loaded = 0
|
||||
const reader = response.body.getReader()
|
||||
const chunks = []
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
chunks.push(value)
|
||||
loaded += value.length
|
||||
if (total) {
|
||||
downloadProgress.value = Math.round((loaded / total) * 100)
|
||||
}
|
||||
}
|
||||
|
||||
const blob = new Blob(chunks)
|
||||
const blobUrl = URL.createObjectURL(blob)
|
||||
|
||||
// 先清理之前的URL
|
||||
if (pdfSource.value) {
|
||||
URL.revokeObjectURL(pdfSource.value)
|
||||
}
|
||||
|
||||
previewUrl.value = fileUrl
|
||||
pdfSource.value = blobUrl
|
||||
|
||||
// 重置PDF状态
|
||||
currentPage.value = 1
|
||||
pageCount.value = 0
|
||||
|
||||
downloading.value = false
|
||||
downloadProgress.value = 0
|
||||
|
||||
} catch (err) {
|
||||
downloading.value = false
|
||||
downloadProgress.value = 0
|
||||
handleError('PDF文件下载失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载文本文件
|
||||
const loadTextFile = async (fileUrl) => {
|
||||
try {
|
||||
const response = await fetch(fileUrl)
|
||||
if (!response.ok) throw new Error('无法加载文本文件')
|
||||
const text = await response.text()
|
||||
textContent.value = text
|
||||
loading.value = false
|
||||
} catch (err) {
|
||||
handleError('文本文件加载失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 上传文件获取office获取pdf文件
|
||||
async function getFilePdf(fileUrl) {
|
||||
try {
|
||||
loadingText.value = '正在转换文件...'
|
||||
const res = await convertFileApi({ file_url: fileUrl },props.roomId)
|
||||
if (res.meta.code !== 200) {
|
||||
throw new Error(res.meta.msg || '文件转换失败')
|
||||
}
|
||||
convertTaskId.value = res.data.task_id
|
||||
// 等待MQTT消息,不进行轮询
|
||||
loading.value = false
|
||||
converting.value = true
|
||||
conversionMessage.value = '已提交转换任务,等待处理...'
|
||||
|
||||
} catch (err) {
|
||||
handleError('文件转换失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
// PDF相关方法
|
||||
const handlePdfLoaded = (data) => {
|
||||
pageCount.value = data.numPages
|
||||
pdfDocument.value = data
|
||||
loading.value = false
|
||||
converting.value = false
|
||||
}
|
||||
|
||||
const handlePdfRendered = () => {
|
||||
// console.log('PDF页面渲染完成')
|
||||
}
|
||||
|
||||
const handlePdfError = (error) => {
|
||||
console.error('PDF加载错误:', error)
|
||||
handleError('PDF文件加载失败', error)
|
||||
}
|
||||
|
||||
const previousPage = () => {
|
||||
if (currentPage.value > 1) {
|
||||
currentPage.value--
|
||||
}
|
||||
}
|
||||
|
||||
const nextPage = () => {
|
||||
if (currentPage.value < pageCount.value) {
|
||||
currentPage.value++
|
||||
}
|
||||
}
|
||||
|
||||
const resetPdfState = () => {
|
||||
pdfSource.value = ''
|
||||
currentPage.value = 1
|
||||
pageCount.value = 0
|
||||
scale.value = 1.0
|
||||
pdfDocument.value = null
|
||||
}
|
||||
|
||||
// 处理图片加载完成
|
||||
const handleImageLoad = () => {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
// 处理错误
|
||||
const handleError = (message, error) => {
|
||||
error.value = true
|
||||
errorMessage.value = message
|
||||
loading.value = false
|
||||
converting.value = false
|
||||
downloading.value = false
|
||||
downloadProgress.value = 0
|
||||
ElMessage.error(message)
|
||||
}
|
||||
|
||||
// 重试预览
|
||||
const retryPreview = () => {
|
||||
if (currentFileData.value) {
|
||||
showEdit(currentFileData.value)
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭按钮点击事件
|
||||
const close = () => {
|
||||
dialogFormVisible.value = false
|
||||
resetState()
|
||||
}
|
||||
|
||||
// 处理对话框关闭
|
||||
const handleClose = (done) => {
|
||||
close()
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
const resetState = () => {
|
||||
loading.value = false
|
||||
converting.value = false
|
||||
downloading.value = false
|
||||
downloadProgress.value = 0
|
||||
error.value = false
|
||||
previewUrl.value = ''
|
||||
textContent.value = ''
|
||||
currentFileData.value = null
|
||||
convertTaskId.value = ''
|
||||
resetPdfState()
|
||||
}
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
// 可以在这里取消特定主题的订阅,但不断开连接
|
||||
// const userId = JSON.parse(sessionStorage.getItem('userData'))?.uid
|
||||
// if (userId) {
|
||||
// const topic = `xsynergy/room/${props.roomId}/file/${userId}/conversion_status`
|
||||
// mqttClient.unsubscribe(topic, handlePdfMessage)
|
||||
// }
|
||||
})
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
showEdit,
|
||||
close,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.file-preview-dialog {
|
||||
.preview-container {
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.loading-container, .error-container {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
|
||||
p {
|
||||
margin-top: 16px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
font-size: 14px;
|
||||
color: #F56C6C;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.download-progress {
|
||||
font-size: 14px;
|
||||
color: #409EFF;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.file-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.image-preview {
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 70vh;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.pdf-preview, .office-preview {
|
||||
.pdf-controls {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding: 8px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pdf-viewer-container {
|
||||
height: 70vh;
|
||||
overflow: auto;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
background: #f9f9f9;
|
||||
|
||||
.pdf-viewer {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
:deep(.vue-pdf-embed) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(canvas) {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video-preview {
|
||||
text-align: center;
|
||||
|
||||
.video-player {
|
||||
max-width: 100%;
|
||||
max-height: 70vh;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-preview {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
|
||||
.audio-player {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
.text-preview {
|
||||
height: 70vh;
|
||||
overflow: auto;
|
||||
background: #f5f5f5;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
margin: 0;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.unsupported-preview {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
|
||||
p {
|
||||
margin-top: 16px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.file-preview-dialog {
|
||||
width: 95% !important;
|
||||
|
||||
.preview-container {
|
||||
min-height: 300px;
|
||||
|
||||
.file-content {
|
||||
.pdf-preview, .office-preview {
|
||||
.pdf-viewer-container {
|
||||
height: 50vh;
|
||||
}
|
||||
}
|
||||
|
||||
.text-preview {
|
||||
height: 50vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pdf-controls {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.el-button-group {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
.el-button {
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
padding: 8px 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
269
src/views/conferencingRoom/components/fileUpload/fileList.vue
Normal file
269
src/views/conferencingRoom/components/fileUpload/fileList.vue
Normal file
@@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-drawer v-model="drawerVisible" direction="rtl" title="文件列表" size="40%">
|
||||
<template #header>
|
||||
<h4>文件列表</h4>
|
||||
</template>
|
||||
|
||||
<div class="drawer-content">
|
||||
<!-- 上传按钮 -->
|
||||
<div class="upload-section">
|
||||
<el-button type="primary" size="small" @click="handleUpload">
|
||||
<el-icon><Upload /></el-icon>
|
||||
上传文件
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 文件列表 -->
|
||||
<div class="file-list" v-loading="loading">
|
||||
<!-- 文件列表内容 -->
|
||||
<div v-for="item in fileList" :key="item.id || item.fileKey" class="file-item">
|
||||
<div class="file-info">
|
||||
<div class="file-icon">
|
||||
<img :src="getFileIcon(item.file_name)" alt="文件图标" class="file-icon-img">
|
||||
</div>
|
||||
<div class="file-details">
|
||||
<div class="file-name" :title="item.file_name">{{ item.file_name }}</div>
|
||||
<!-- <div class="file-meta">
|
||||
<span class="file-size">{{ formatFileSize(item.fileSize) }}</span>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="file-actions">
|
||||
<el-button type="primary" size="small" :disabled="!item.preview_url" @click="handlePreview(item)">预览</el-button>
|
||||
<el-button type="success" size="small" @click="handleDownload(item)">下载</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="fileList.length === 0" class="empty-state">
|
||||
<el-empty description="暂无文件" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
<!-- 文件上传 -->
|
||||
<UpLoadFile
|
||||
ref="uploadRef"
|
||||
:fileType='["pdf", "png", "jpg", "jpeg","gif","doc","docx","xls","xlsx","ppt","pptx","txt","mp4","mp3"]'
|
||||
:roomId="roomId"
|
||||
@upload-success="handleUploadSuccess"
|
||||
/>
|
||||
<!-- 文件预览 -->
|
||||
<BrowseFile ref="browseFileRef" :roomId="roomId"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
getFileListApi,
|
||||
} from '@/api/conferencingRoom.js'
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { ref } from "vue";
|
||||
import { Upload } from '@element-plus/icons-vue'
|
||||
import UpLoadFile from './upLoadFile.vue'
|
||||
import BrowseFile from './browseFile.vue'
|
||||
import fileLogo from '@/assets/images/file-logo.png';
|
||||
import { emitter } from "@/utils/bus.js";
|
||||
// 定义 emit
|
||||
const emit = defineEmits([""]);
|
||||
|
||||
// 接收 props
|
||||
const props = defineProps({
|
||||
roomId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
emitter.on('fileUploadStatus',async ()=>{
|
||||
if(drawerVisible.value){
|
||||
console.log('更新啦')
|
||||
await getFileList()
|
||||
}
|
||||
})
|
||||
|
||||
const drawerVisible = ref(false);
|
||||
const fileList = ref([]);
|
||||
const loading = ref(false);
|
||||
const uploadRef = ref(null);
|
||||
|
||||
//文件预览
|
||||
const browseFileRef = ref(null);
|
||||
|
||||
// 根据文件扩展名获取文件图标
|
||||
function getFileIcon(fileName) {
|
||||
return fileLogo;
|
||||
}
|
||||
|
||||
// 格式化文件大小
|
||||
function formatFileSize(size) {
|
||||
if (!size) return '未知大小';
|
||||
|
||||
if (size < 1024) {
|
||||
return size + ' B';
|
||||
} else if (size < 1024 * 1024) {
|
||||
return (size / 1024).toFixed(2) + ' KB';
|
||||
} else {
|
||||
return (size / (1024 * 1024)).toFixed(2) + ' MB';
|
||||
}
|
||||
}
|
||||
|
||||
// 文件下载功能
|
||||
function handleDownload(file) {
|
||||
try {
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.style.display = 'none';
|
||||
iframe.src = file.source_url;
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(iframe);
|
||||
}, 5000); // 5秒后清理iframe
|
||||
|
||||
ElMessage.success('开始下载文件');
|
||||
} catch (error) {
|
||||
console.error('iframe下载失败:', error);
|
||||
ElMessage.error('下载失败,请检查浏览器设置');
|
||||
}
|
||||
}
|
||||
|
||||
// 文件预览功能
|
||||
function handlePreview(file) {
|
||||
if (!file.preview_url) {
|
||||
ElMessage.error('文件链接无效');
|
||||
return;
|
||||
}
|
||||
browseFileRef.value.showEdit(file)
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
function handleUpload() {
|
||||
uploadRef.value.showEdit()
|
||||
// 这里可以添加文件上传逻辑
|
||||
}
|
||||
|
||||
// 显示抽屉
|
||||
async function show() {
|
||||
drawerVisible.value = true;
|
||||
await getFileList()
|
||||
}
|
||||
|
||||
//获取文件列表
|
||||
async function getFileList(){
|
||||
loading.value = true;
|
||||
try {
|
||||
// xsy
|
||||
const res = await getFileListApi(props.roomId);
|
||||
if (res.meta.code !== 200) {
|
||||
ElMessage.error(res.meta.msg);
|
||||
return;
|
||||
}
|
||||
fileList.value = res.data.files || [];
|
||||
} catch (error) {
|
||||
console.error('获取文件列表失败:', error);
|
||||
ElMessage.error('获取文件列表失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
//文件上传成功
|
||||
async function handleUploadSuccess(){
|
||||
if(drawerVisible.value){
|
||||
await getFileList()
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
show,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.drawer-content {
|
||||
padding: 0 10px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.upload-section {
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.file-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.file-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0; /* 防止内容溢出 */
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0; /* 防止图标被压缩 */
|
||||
}
|
||||
|
||||
.file-icon-img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.file-details {
|
||||
flex: 1;
|
||||
min-width: 0; /* 防止文本溢出 */
|
||||
overflow: hidden; /* 隐藏溢出内容 */
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.file-meta {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.file-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-shrink: 0; /* 防止按钮被压缩 */
|
||||
margin-left: 10px; /* 添加左边距,与文件信息保持距离 */
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +0,0 @@
|
||||
<template>
|
||||
<div class="file-upload-container">
|
||||
<input type="file" ref="fileInput" @change="handleFileChange" />
|
||||
<button @click="uploadFile">上传文件</button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,726 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog
|
||||
v-model="dialogFormVisible"
|
||||
:title="title"
|
||||
width="80%"
|
||||
:before-close="handleClose"
|
||||
class="file-preview-dialog"
|
||||
>
|
||||
<div class="preview-container">
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-container">
|
||||
<el-icon class="is-loading" size="48">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
<p>{{ loadingText }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 下载状态 -->
|
||||
<div v-else-if="downloading" class="loading-container">
|
||||
<el-icon class="is-loading" size="48">
|
||||
<Loading />
|
||||
</el-icon>
|
||||
<p>正在下载文件,请稍候...</p>
|
||||
<p class="download-progress" v-if="downloadProgress > 0">
|
||||
下载进度: {{ downloadProgress }}%
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<div v-else-if="error" class="error-container">
|
||||
<el-icon size="48" color="#F56C6C">
|
||||
<CircleClose />
|
||||
</el-icon>
|
||||
<p>文件预览失败</p>
|
||||
<p class="error-message">{{ errorMessage }}</p>
|
||||
<el-button type="primary" @click="retryPreview">重试</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 文件预览内容 -->
|
||||
<div v-else class="file-content">
|
||||
<!-- 图片预览 -->
|
||||
<div v-if="isImage" class="image-preview">
|
||||
<img :src="previewUrl" :alt="fileName" @load="handleImageLoad" />
|
||||
</div>
|
||||
|
||||
<!-- PDF预览 - 使用 vue-pdf-embed -->
|
||||
<div v-else-if="isPdf" class="pdf-preview">
|
||||
<div class="pdf-controls" v-if="pageCount > 0">
|
||||
<el-button-group>
|
||||
<el-button :disabled="currentPage <= 1" @click="previousPage">
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
上一页
|
||||
</el-button>
|
||||
<el-button>
|
||||
{{ currentPage }} / {{ pageCount }}
|
||||
</el-button>
|
||||
<el-button :disabled="currentPage >= pageCount" @click="nextPage">
|
||||
下一页
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
|
||||
<div class="pdf-viewer-container">
|
||||
<VuePdfEmbed
|
||||
:source="pdfSource"
|
||||
:page="currentPage"
|
||||
:scale="scale"
|
||||
@loaded="handlePdfLoaded"
|
||||
@rendered="handlePdfRendered"
|
||||
@error="handlePdfError"
|
||||
class="pdf-viewer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视频预览 -->
|
||||
<div v-else-if="isVideo" class="video-preview">
|
||||
<video controls :src="previewUrl" class="video-player">
|
||||
您的浏览器不支持视频播放
|
||||
</video>
|
||||
</div>
|
||||
|
||||
<!-- 音频预览 -->
|
||||
<div v-else-if="isAudio" class="audio-preview">
|
||||
<audio controls :src="previewUrl" class="audio-player">
|
||||
您的浏览器不支持音频播放
|
||||
</audio>
|
||||
</div>
|
||||
|
||||
<!-- 文本预览 -->
|
||||
<div v-else-if="isText" class="text-preview">
|
||||
<pre>{{ textContent }}</pre>
|
||||
</div>
|
||||
|
||||
<!-- Office文档预览(转换后) -->
|
||||
<div v-else-if="isConvertedOffice" class="office-preview">
|
||||
<!-- 转换后的Office文件也是PDF,使用相同的PDF预览器 -->
|
||||
<div class="pdf-controls" v-if="pageCount > 0">
|
||||
<el-button-group>
|
||||
<el-button :disabled="currentPage <= 1" @click="previousPage">
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
上一页
|
||||
</el-button>
|
||||
<el-button>
|
||||
{{ currentPage }} / {{ pageCount }}
|
||||
</el-button>
|
||||
<el-button :disabled="currentPage >= pageCount" @click="nextPage">
|
||||
下一页
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
|
||||
<div class="pdf-viewer-container">
|
||||
<VuePdfEmbed
|
||||
:source="pdfSource"
|
||||
:page="currentPage"
|
||||
:scale="scale"
|
||||
@loaded="handlePdfLoaded"
|
||||
@rendered="handlePdfRendered"
|
||||
@error="handlePdfError"
|
||||
class="pdf-viewer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 不支持预览的文件类型 -->
|
||||
<div v-else class="unsupported-preview">
|
||||
<el-icon size="64" color="#909399">
|
||||
<Document />
|
||||
</el-icon>
|
||||
<p>不支持在线预览此文件类型</p>
|
||||
<p class="file-name">{{ fileName }}</p>
|
||||
<!-- <el-button type="primary" @click="downloadFile">下载文件</el-button> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<!-- <el-button @click="downloadFile" :disabled="loading">
|
||||
<el-icon><Download /></el-icon>
|
||||
下载
|
||||
</el-button> -->
|
||||
<el-button type="primary" @click="close">
|
||||
关闭
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, getCurrentInstance, onUnmounted, nextTick } from 'vue'
|
||||
import { convertFileApi, getConvertStatusApi } from '@/api/conferencingRoom'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
Loading,
|
||||
CircleClose,
|
||||
Document,
|
||||
Download,
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
Plus,
|
||||
Minus,
|
||||
Refresh
|
||||
} from '@element-plus/icons-vue'
|
||||
import VuePdfEmbed from 'vue-pdf-embed'
|
||||
|
||||
// 定义props
|
||||
const props = defineProps({
|
||||
fileType: {
|
||||
type: Array,
|
||||
default: () => ["pdf", "png", "jpg", "jpeg", "gif", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "mp4", "mp3"],
|
||||
},
|
||||
roomId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
// 定义emits
|
||||
const emit = defineEmits(['fetch-data'])
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const enumType = ["doc", "docx", "xls", "xlsx", "ppt", "pptx"]
|
||||
|
||||
// 响应式数据
|
||||
const dialogFormVisible = ref(false)
|
||||
const title = ref('')
|
||||
const loading = ref(false)
|
||||
const downloading = ref(false) // 新增:下载状态
|
||||
const downloadProgress = ref(0) // 新增:下载进度
|
||||
const loadingText = ref('正在加载...')
|
||||
const error = ref(false)
|
||||
const errorMessage = ref('')
|
||||
const previewUrl = ref('')
|
||||
const fileName = ref('')
|
||||
const textContent = ref('')
|
||||
const currentFileData = ref(null)
|
||||
const convertTaskId = ref('')
|
||||
const statusCheckInterval = ref(null)
|
||||
|
||||
// PDF相关状态
|
||||
const pdfSource = ref('')
|
||||
const currentPage = ref(1)
|
||||
const pageCount = ref(0)
|
||||
const scale = ref(1.0)
|
||||
const pdfDocument = ref(null)
|
||||
|
||||
// 计算属性
|
||||
const isImage = computed(() => {
|
||||
const ext = fileName.value.split('.').pop().toLowerCase()
|
||||
return ['png', 'jpg', 'jpeg', 'gif'].includes(ext)
|
||||
})
|
||||
|
||||
const isPdf = computed(() => {
|
||||
const ext = fileName.value.split('.').pop().toLowerCase()
|
||||
return ext === 'pdf'
|
||||
})
|
||||
|
||||
const isVideo = computed(() => {
|
||||
const ext = fileName.value.split('.').pop().toLowerCase()
|
||||
return ['mp4'].includes(ext)
|
||||
})
|
||||
|
||||
const isAudio = computed(() => {
|
||||
const ext = fileName.value.split('.').pop().toLowerCase()
|
||||
return ['mp3'].includes(ext)
|
||||
})
|
||||
|
||||
const isText = computed(() => {
|
||||
const ext = fileName.value.split('.').pop().toLowerCase()
|
||||
return ext === 'txt'
|
||||
})
|
||||
|
||||
const isOffice = computed(() => {
|
||||
const ext = fileName.value.split('.').pop().toLowerCase()
|
||||
return enumType.includes(ext)
|
||||
})
|
||||
|
||||
const isConvertedOffice = computed(() => {
|
||||
return previewUrl.value && previewUrl.value.endsWith('.pdf') && isOffice.value
|
||||
})
|
||||
|
||||
// 显示弹框
|
||||
const showEdit = async (data) => {
|
||||
// 重置状态
|
||||
loading.value = true
|
||||
downloading.value = false
|
||||
downloadProgress.value = 0
|
||||
title.value = '文件预览'
|
||||
dialogFormVisible.value = true
|
||||
currentFileData.value = data
|
||||
fileName.value = data.fileName || data.fileUrl.split('/').pop()
|
||||
|
||||
error.value = false
|
||||
previewUrl.value = ''
|
||||
textContent.value = ''
|
||||
resetPdfState()
|
||||
const fileExt = fileName.value.split('.').pop().toLowerCase()
|
||||
try {
|
||||
if (enumType.includes(fileExt)) {
|
||||
// Office文档需要转换
|
||||
await getFilePdf(data.fileUrl)
|
||||
} else if (fileExt === 'txt') {
|
||||
// 文本文件需要特殊处理
|
||||
await loadTextFile(data.fileUrl)
|
||||
} else if (fileExt === 'pdf') {
|
||||
// PDF文件使用vue-pdf-embed预览
|
||||
await loadPdfFile(data.fileUrl)
|
||||
} else {
|
||||
// 其他文件直接预览
|
||||
previewUrl.value = data.fileUrl
|
||||
loading.value = false
|
||||
}
|
||||
} catch (err) {
|
||||
handleError('文件加载失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载PDF文件
|
||||
const loadPdfFile = async (fileUrl) => {
|
||||
try {
|
||||
// 先显示下载状态
|
||||
loading.value = false
|
||||
downloading.value = true
|
||||
downloadProgress.value = 0
|
||||
|
||||
// 使用fetch下载文件并跟踪进度
|
||||
const response = await fetch(fileUrl)
|
||||
if (!response.ok) {
|
||||
throw new Error('文件下载失败')
|
||||
}
|
||||
|
||||
const contentLength = response.headers.get('content-length')
|
||||
const total = parseInt(contentLength, 10)
|
||||
let loaded = 0
|
||||
|
||||
// 创建读取器
|
||||
const reader = response.body.getReader()
|
||||
const chunks = []
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
|
||||
if (done) break
|
||||
|
||||
chunks.push(value)
|
||||
loaded += value.length
|
||||
|
||||
// 更新下载进度
|
||||
if (total) {
|
||||
downloadProgress.value = Math.round((loaded / total) * 100)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建Blob URL
|
||||
const blob = new Blob(chunks)
|
||||
const blobUrl = URL.createObjectURL(blob)
|
||||
|
||||
// 设置PDF源
|
||||
previewUrl.value = fileUrl
|
||||
pdfSource.value = blobUrl
|
||||
|
||||
// 重置状态
|
||||
downloading.value = false
|
||||
downloadProgress.value = 0
|
||||
|
||||
// 使用nextTick确保DOM更新
|
||||
nextTick(() => {
|
||||
// PDF组件会自动开始加载,loading状态会在handlePdfLoaded中设置为false
|
||||
})
|
||||
|
||||
} catch (err) {
|
||||
downloading.value = false
|
||||
downloadProgress.value = 0
|
||||
handleError('PDF文件下载失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载文本文件
|
||||
const loadTextFile = async (fileUrl) => {
|
||||
try {
|
||||
const response = await fetch(fileUrl)
|
||||
if (!response.ok) throw new Error('无法加载文本文件')
|
||||
const text = await response.text()
|
||||
textContent.value = text
|
||||
loading.value = false
|
||||
} catch (err) {
|
||||
handleError('文本文件加载失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 上传文件获取office获取pdf文件
|
||||
async function getFilePdf(fileUrl) {
|
||||
try {
|
||||
loadingText.value = '正在转换文件...'
|
||||
const res = await convertFileApi({ file_url: fileUrl })
|
||||
|
||||
if (res.meta.code !== 200) {
|
||||
throw new Error(res.meta.msg || '文件转换失败')
|
||||
}
|
||||
|
||||
convertTaskId.value = res.data.task_id
|
||||
loadingText.value = '正在等待转换完成...'
|
||||
|
||||
// 开始轮询转换状态
|
||||
startStatusPolling()
|
||||
} catch (err) {
|
||||
handleError('文件转换失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 开始轮询转换状态
|
||||
const startStatusPolling = () => {
|
||||
// 清除之前的轮询
|
||||
if (statusCheckInterval.value) {
|
||||
clearInterval(statusCheckInterval.value)
|
||||
}
|
||||
|
||||
// 设置新的轮询
|
||||
statusCheckInterval.value = setInterval(async () => {
|
||||
try {
|
||||
const fileRes = await getConvertStatusApi(convertTaskId.value)
|
||||
|
||||
if (fileRes.meta.code === 200) {
|
||||
if (fileRes.data.status === 'completed') {
|
||||
// 转换完成
|
||||
clearInterval(statusCheckInterval.value)
|
||||
previewUrl.value = fileRes.data.output_file
|
||||
pdfSource.value = fileRes.data.output_file
|
||||
loading.value = false
|
||||
} else if (fileRes.data.status === 'failed') {
|
||||
// 转换失败
|
||||
clearInterval(statusCheckInterval.value)
|
||||
throw new Error('文件转换失败')
|
||||
}
|
||||
// 其他状态继续等待
|
||||
} else {
|
||||
throw new Error(fileRes.meta.msg || '获取转换状态失败')
|
||||
}
|
||||
} catch (err) {
|
||||
clearInterval(statusCheckInterval.value)
|
||||
handleError('获取转换状态失败', err)
|
||||
}
|
||||
}, 2000) // 每2秒检查一次
|
||||
}
|
||||
|
||||
// PDF相关方法
|
||||
const handlePdfLoaded = (data) => {
|
||||
pageCount.value = data.numPages
|
||||
pdfDocument.value = data
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const handlePdfRendered = () => {
|
||||
console.log('PDF页面渲染完成')
|
||||
}
|
||||
|
||||
const handlePdfError = (error) => {
|
||||
console.error('PDF加载错误:', error)
|
||||
handleError('PDF文件加载失败', error)
|
||||
}
|
||||
|
||||
const previousPage = () => {
|
||||
if (currentPage.value > 1) {
|
||||
currentPage.value--
|
||||
}
|
||||
}
|
||||
|
||||
const nextPage = () => {
|
||||
if (currentPage.value < pageCount.value) {
|
||||
currentPage.value++
|
||||
}
|
||||
}
|
||||
|
||||
const zoomIn = () => {
|
||||
if (scale.value < 2) {
|
||||
scale.value = Math.round((scale.value + 0.1) * 10) / 10
|
||||
}
|
||||
}
|
||||
|
||||
const zoomOut = () => {
|
||||
if (scale.value > 0.5) {
|
||||
scale.value = Math.round((scale.value - 0.1) * 10) / 10
|
||||
}
|
||||
}
|
||||
|
||||
const resetZoom = () => {
|
||||
scale.value = 1.0
|
||||
}
|
||||
|
||||
const resetPdfState = () => {
|
||||
pdfSource.value = ''
|
||||
currentPage.value = 1
|
||||
pageCount.value = 0
|
||||
scale.value = 1.0
|
||||
pdfDocument.value = null
|
||||
}
|
||||
|
||||
// 处理图片加载完成
|
||||
const handleImageLoad = () => {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
// 处理错误
|
||||
const handleError = (message, error) => {
|
||||
console.error('File preview error:', error)
|
||||
error.value = true
|
||||
errorMessage.value = message
|
||||
loading.value = false
|
||||
downloading.value = false
|
||||
downloadProgress.value = 0
|
||||
ElMessage.error(message)
|
||||
}
|
||||
|
||||
// 重试预览
|
||||
const retryPreview = () => {
|
||||
if (currentFileData.value) {
|
||||
showEdit(currentFileData.value)
|
||||
}
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
const downloadFile = () => {
|
||||
if (currentFileData.value && currentFileData.value.fileUrl) {
|
||||
const link = document.createElement('a')
|
||||
link.href = currentFileData.value.fileUrl
|
||||
link.download = fileName.value
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
} else {
|
||||
ElMessage.warning('无法下载文件')
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭按钮点击事件
|
||||
const close = () => {
|
||||
dialogFormVisible.value = false
|
||||
resetState()
|
||||
}
|
||||
|
||||
// 处理对话框关闭
|
||||
const handleClose = (done) => {
|
||||
close()
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
const resetState = () => {
|
||||
// 清除轮询
|
||||
if (statusCheckInterval.value) {
|
||||
clearInterval(statusCheckInterval.value)
|
||||
statusCheckInterval.value = null
|
||||
}
|
||||
|
||||
// 重置其他状态
|
||||
loading.value = false
|
||||
downloading.value = false
|
||||
downloadProgress.value = 0
|
||||
error.value = false
|
||||
previewUrl.value = ''
|
||||
textContent.value = ''
|
||||
currentFileData.value = null
|
||||
convertTaskId.value = ''
|
||||
resetPdfState()
|
||||
}
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
if (statusCheckInterval.value) {
|
||||
clearInterval(statusCheckInterval.value)
|
||||
}
|
||||
})
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
showEdit,
|
||||
close,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.file-preview-dialog {
|
||||
.preview-container {
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.loading-container, .error-container {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
|
||||
p {
|
||||
margin-top: 16px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
font-size: 14px;
|
||||
color: #F56C6C;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.download-progress {
|
||||
font-size: 14px;
|
||||
color: #409EFF;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.file-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.image-preview {
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 70vh;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.pdf-preview, .office-preview {
|
||||
.pdf-controls {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
padding: 8px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pdf-viewer-container {
|
||||
height: 70vh;
|
||||
overflow: auto;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
background: #f9f9f9;
|
||||
|
||||
.pdf-viewer {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
:deep(.vue-pdf-embed) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(canvas) {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video-preview {
|
||||
text-align: center;
|
||||
|
||||
.video-player {
|
||||
max-width: 100%;
|
||||
max-height: 70vh;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-preview {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
|
||||
.audio-player {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
.text-preview {
|
||||
height: 70vh;
|
||||
overflow: auto;
|
||||
background: #f5f5f5;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
margin: 0;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.unsupported-preview {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
|
||||
p {
|
||||
margin-top: 16px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.file-preview-dialog {
|
||||
width: 95% !important;
|
||||
|
||||
.preview-container {
|
||||
min-height: 300px;
|
||||
|
||||
.file-content {
|
||||
.pdf-preview, .office-preview {
|
||||
.pdf-viewer-container {
|
||||
height: 50vh;
|
||||
}
|
||||
}
|
||||
|
||||
.text-preview {
|
||||
height: 50vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pdf-controls {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.el-button-group {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
.el-button {
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
padding: 8px 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
291
src/views/conferencingRoom/components/fileUpload/upLoadFile.vue
Normal file
291
src/views/conferencingRoom/components/fileUpload/upLoadFile.vue
Normal file
@@ -0,0 +1,291 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog
|
||||
v-model="dialogFormVisible"
|
||||
:title="title"
|
||||
width="403px"
|
||||
@close="close"
|
||||
>
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:accept="acceptString"
|
||||
:show-file-list="false"
|
||||
:limit="999"
|
||||
style="width: 100%; text-align: center"
|
||||
:before-upload="handleBeforeUpload"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-button
|
||||
type="primary"
|
||||
class="el-button-custom-css blue-css"
|
||||
:loading="uploadLoading"
|
||||
:disabled="uploadLoading"
|
||||
>
|
||||
{{ uploadLoading ? '上传中...' : '上传文件' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-upload>
|
||||
<div style="margin-top: 20px">
|
||||
请上传格式为:
|
||||
<b style="color: #f56c6c">
|
||||
{{ acceptString }}
|
||||
</b>
|
||||
的文件
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, getCurrentInstance } from 'vue'
|
||||
import axios from 'axios'
|
||||
import { getUploadTokenApi, uploadFileApi,convertFileApi } from '@/api/conferencingRoom'
|
||||
import { calculateFileSHA1 } from '@/views/conferencingRoom/business/index.js'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { mqttClient } from "@/utils/mqtt.js";
|
||||
import { emitter } from "@/utils/bus.js";
|
||||
// 定义props
|
||||
const props = defineProps({
|
||||
fileType: {
|
||||
type: Array,
|
||||
default: () => ["pdf", "png", "jpg", "jpeg", "gif", "doc", "docx", "xls", "xlsx", "ppt", "pptx"],
|
||||
},
|
||||
roomId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
// 定义emits
|
||||
const emit = defineEmits(['upload-success'])
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
// 响应式数据
|
||||
const dialogFormVisible = ref(false)
|
||||
const title = ref('')
|
||||
const fileList = ref([])
|
||||
const fileIds = ref([])
|
||||
const uploadRef = ref(null)
|
||||
const showList = ref(true)
|
||||
const saveLoading = ref(false)
|
||||
|
||||
const uploadToken = ref('')
|
||||
const fileUrl = ref('')
|
||||
const currentUploadFile = ref(null) // 存储当前要上传的文件
|
||||
const uploadLoading = ref(false) // 上传loading状态
|
||||
|
||||
const roomId = ref()
|
||||
const uploaderInfo = ref('')
|
||||
|
||||
// 计算属性:将文件类型数组转换为accept字符串
|
||||
const acceptString = computed(() => {
|
||||
return props.fileType.map(type => `.${type}`).join(', ')
|
||||
})
|
||||
|
||||
emitter.on('subscribeToFileUploadTopic',subscribeToFileUploadTopic)
|
||||
|
||||
|
||||
function subscribeToFileUploadTopic(data){
|
||||
try {
|
||||
// 订阅文件上传状态主题
|
||||
roomId.value = data.roomId
|
||||
const topic = `xsynergy/room/${data.roomId}/file/upload`
|
||||
mqttClient.subscribe(topic, handleFileUploadMessage)
|
||||
} catch (error) {
|
||||
console.error('订阅文件上传事件失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
//订阅文件上传状态主题
|
||||
function handleFileUploadMessage(payload, topic){
|
||||
try {
|
||||
const messageStr = payload.toString()
|
||||
const data = JSON.parse(messageStr)
|
||||
emitter.emit('fileUploadStatus',data)
|
||||
emit('upload-success')
|
||||
} catch (error) {
|
||||
console.error('文件长传状态消息失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 上传前校检格式和大小
|
||||
const handleBeforeUpload = async (file) => {
|
||||
// 如果正在上传中,阻止新文件上传
|
||||
if (uploadLoading.value) {
|
||||
ElMessage.warning('文件正在上传中,请稍候...')
|
||||
return false
|
||||
}
|
||||
|
||||
// 获取文件扩展名
|
||||
const fileExtension = file.name.toLowerCase().slice(((file.name.lastIndexOf(".") - 1) >>> 0) + 2)
|
||||
// 校验文件格式
|
||||
if (!props.fileType.includes(fileExtension)) {
|
||||
ElMessage.error(`文件格式不支持,请上传 ${acceptString.value} 格式的文件`)
|
||||
return false
|
||||
}
|
||||
|
||||
// 校检文件大小
|
||||
const isLt = file.size / 1024 / 1024 < 50
|
||||
if (!isLt) {
|
||||
ElMessage.error(`上传文件大小不能超过 50 MB!`)
|
||||
return false
|
||||
}
|
||||
|
||||
// 开始上传,设置loading状态
|
||||
uploadLoading.value = true
|
||||
|
||||
try {
|
||||
// 保存当前文件引用
|
||||
currentUploadFile.value = file
|
||||
|
||||
// 计算文件SHA1
|
||||
const sha1 = await calculateFileSHA1(file)
|
||||
|
||||
// 获取上传token
|
||||
const res = await getUploadTokenApi({
|
||||
service: props.roomId,
|
||||
hash: sha1,
|
||||
ext: fileExtension,
|
||||
})
|
||||
|
||||
if(res.meta.code != 200){
|
||||
ElMessage.error(res.meta.msg)
|
||||
uploadLoading.value = false // 出错时取消loading
|
||||
return false
|
||||
}
|
||||
|
||||
if(res.data.exists){
|
||||
// 文件已存在,直接获取文件URL
|
||||
fileUrl.value = res.data.fileUrl
|
||||
ElMessage.info('文件已存在,无需重复上传')
|
||||
// dialogFormVisible.value = false
|
||||
uploadLoading.value = false
|
||||
} else {
|
||||
// 文件不存在,获取token并执行上传
|
||||
uploadToken.value = res.data.token
|
||||
// 执行上传操作
|
||||
await handleHttpRequest(file)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('上传过程出错:', error)
|
||||
ElMessage.error('上传过程出错,请重试')
|
||||
uploadLoading.value = false // 出错时取消loading
|
||||
return false
|
||||
}
|
||||
|
||||
return false // 阻止默认上传行为,因为我们使用自定义上传
|
||||
}
|
||||
|
||||
// 自定义上传
|
||||
const handleHttpRequest = async (file) => {
|
||||
if (!uploadToken.value) {
|
||||
ElMessage.error('上传凭证不存在')
|
||||
uploadLoading.value = false // 取消loading
|
||||
return false
|
||||
}
|
||||
let params = new FormData()
|
||||
params.append('file', file)
|
||||
|
||||
try {
|
||||
const res = await uploadFileApi(uploadToken.value, params)
|
||||
if(res.meta.code != 200){
|
||||
ElMessage.error(res.meta.msg)
|
||||
uploadLoading.value = false // 出错时取消loading
|
||||
return false
|
||||
}
|
||||
fileUrl.value = res.data.fileUrl
|
||||
getFileTaskId(res.data.fileUrl)
|
||||
ElMessage.success('文件上传成功')
|
||||
// publishFileUploadData(file);
|
||||
|
||||
// 上传成功,取消loading
|
||||
uploadLoading.value = false
|
||||
dialogFormVisible.value = false
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('上传文件失败:', error)
|
||||
ElMessage.error('上传文件失败')
|
||||
uploadLoading.value = false // 出错时取消loading
|
||||
return false
|
||||
}
|
||||
}
|
||||
async function getFileTaskId(fileUrl) {
|
||||
try {
|
||||
const res = await convertFileApi({ file_url: fileUrl },props.roomId)
|
||||
if (res.meta.code !== 200) {
|
||||
throw new Error(res.meta.msg || '文件转换失败')
|
||||
}
|
||||
} catch (err) {
|
||||
ElMessage.error('文件转换失败')
|
||||
}
|
||||
}
|
||||
|
||||
function publishFileUploadData(fileData) {
|
||||
try {
|
||||
const message = {
|
||||
uploaderName: uploaderInfo.value?.name || '',
|
||||
uploaderUid: uploaderInfo.value?.uid || '',
|
||||
fileName: fileData.name,
|
||||
fileUrl: fileUrl.value,
|
||||
roomId: roomId.value,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
mqttClient.publish(`xSynergy/File/Upload/${roomId.value}`, message);
|
||||
} catch (error) {
|
||||
console.error('发布激光笔数据失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 上传前校检格式和大小
|
||||
const beforeUploadser = (file) => {
|
||||
return false
|
||||
}
|
||||
|
||||
// 显示弹框
|
||||
const showEdit = () => {
|
||||
title.value = '上传文件'
|
||||
dialogFormVisible.value = true
|
||||
uploaderInfo.value = JSON.parse(sessionStorage.getItem('userData'))
|
||||
}
|
||||
|
||||
// 关闭按钮点击事件
|
||||
const close = () => {
|
||||
fileList.value = []
|
||||
currentUploadFile.value = null
|
||||
uploadToken.value = ''
|
||||
fileUrl.value = ''
|
||||
uploadLoading.value = false // 关闭时重置loading状态
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
|
||||
// 保存按钮点击事件
|
||||
const save = () => {
|
||||
close()
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
showEdit,
|
||||
close,
|
||||
save
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.avatar-uploader {
|
||||
.el-upload {
|
||||
width: 345px;
|
||||
height: 180px;
|
||||
}
|
||||
.el-upload-list__item-thumbnail {
|
||||
height: 180px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 765px) {
|
||||
.el-dialog {
|
||||
width: 80% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user