web-学习管理、upload组件修改

This commit is contained in:
2025-10-21 16:21:10 +08:00
parent 3b4a639b95
commit f72a5cec61
13 changed files with 1288 additions and 127 deletions

View File

@@ -1,5 +1,57 @@
<template>
<div v-if="!asDialog" class="upload-container">
<!-- Cover 模式 - 封面图片上传 -->
<div v-if="!asDialog && listType === 'cover'" class="upload-cover-container">
<!-- 已上传的封面 -->
<div v-if="props.coverUrl" class="cover-image-wrapper">
<img
:src="FILE_DOWNLOAD_URL + props.coverUrl"
class="cover-image"
:class="coverImageClass"
@load="handleCoverImageLoad"
/>
<div class="cover-actions">
<el-button
type="danger"
size="small"
circle
:icon="Delete"
@click="handleRemoveCover"
class="delete-btn"
/>
</div>
</div>
<!-- 上传区域 -->
<div
v-else
class="upload-cover-area"
:class="{ 'is-dragover': isDragover, 'is-disabled': uploading }"
@click="handleClickUpload"
@drop.prevent="handleDrop"
@dragover.prevent="handleDragOver"
@dragleave.prevent="handleDragLeave"
>
<el-icon v-if="!uploading" class="upload-cover-icon"><Plus /></el-icon>
<div v-if="uploading" class="uploading-mask">
<el-icon class="is-loading"><Loading /></el-icon>
<div>上传中...</div>
</div>
<div v-if="!uploading && tip" class="upload-cover-tip">{{ tip }}</div>
</div>
<!-- 隐藏的文件输入框 -->
<input
ref="fileInputRef"
type="file"
:accept="accept"
:multiple="false"
@change="handleFileSelect"
style="display: none"
/>
</div>
<!-- 默认模式 -->
<div v-else-if="!asDialog" class="upload-container">
<div
class="upload-area"
:class="{ 'is-dragover': isDragover, 'is-disabled': uploading }"
@@ -218,8 +270,10 @@
<script setup lang="ts">
import { ref, computed } from 'vue';
import { ElDialog, ElButton, ElMessage } from 'element-plus';
import { ElDialog, ElButton, ElMessage, ElIcon } from 'element-plus';
import { Plus, Delete, Loading } from '@element-plus/icons-vue';
import { fileApi } from '@/apis/system/file';
import { FILE_DOWNLOAD_URL } from '@/config';
import type { SysFile } from '@/types';
interface Props {
@@ -232,6 +286,8 @@ interface Props {
businessId?: string;
tip?: string;
asDialog?: boolean; // 是否作为弹窗使用
listType?: 'default' | 'cover'; // 列表类型default-默认, cover-封面
coverUrl?: string; // 封面图片URL用于cover模式
}
const props = withDefaults(defineProps<Props>(), {
@@ -242,13 +298,17 @@ const props = withDefaults(defineProps<Props>(), {
multiple: false,
module: 'common',
tip: '',
asDialog: true
asDialog: true,
listType: 'default',
coverUrl: ''
});
const emit = defineEmits<{
'update:modelValue': [value: boolean];
'update:coverUrl': [url: string];
'success': [files: SysFile[]];
'error': [error: any];
'remove': [];
}>();
const fileInputRef = ref<HTMLInputElement>();
@@ -261,6 +321,24 @@ const visible = computed({
set: (val) => props.asDialog ? emit('update:modelValue', val) : undefined
});
// cover图片的宽高比类
const coverImageClass = ref('');
// 处理封面图片加载
function handleCoverImageLoad(event: Event) {
const img = event.target as HTMLImageElement;
const width = img.naturalWidth;
const height = img.naturalHeight;
// 如果宽度大于高度横向图片设置width: 100%
if (width > height) {
coverImageClass.value = 'cover-image-horizontal';
} else {
// 纵向图片设置height: 100%
coverImageClass.value = 'cover-image-vertical';
}
}
// 预览相关
const imagePreviewVisible = ref(false);
const filePreviewVisible = ref(false);
@@ -283,6 +361,11 @@ function handleFileSelect(event: Event) {
addFiles(Array.from(input.files));
// 清空 input允许重复选择同一文件
input.value = '';
// cover模式下选择文件后立即上传
if (props.listType === 'cover') {
handleUpload();
}
}
}
@@ -445,7 +528,11 @@ async function handleUpload() {
if (result.code === 200 && result.data) {
uploadedFilesList.push(result.data);
ElMessage.success(`${file.name} 上传成功`);
// cover模式下不显示成功提示
if (props.listType !== 'cover') {
ElMessage.success(`${file.name} 上传成功`);
}
} else {
ElMessage.error(`${file.name} 上传失败: ${result.message}`);
}
@@ -454,6 +541,11 @@ async function handleUpload() {
// 所有文件上传完成
if (uploadedFilesList.length > 0) {
emit('success', uploadedFilesList);
// cover模式下更新coverUrl只返回fileID不拼接URL
if (props.listType === 'cover' && uploadedFilesList[0]) {
emit('update:coverUrl', uploadedFilesList[0].fileID || '');
}
}
} catch (error: any) {
console.error('上传失败:', error);
@@ -461,13 +553,24 @@ async function handleUpload() {
emit('error', error);
} finally {
uploading.value = false;
// 上传完成后关闭对话框
if (uploadedFilesList.length > 0) {
// cover模式下清空选择的文件
if (props.listType === 'cover') {
selectedFiles.value = [];
} else if (uploadedFilesList.length > 0) {
// 其他模式上传完成后关闭对话框
handleClose();
}
}
}
// 删除封面
function handleRemoveCover() {
emit('update:coverUrl', '');
coverImageClass.value = '';
emit('remove');
}
// 关闭对话框
function handleClose() {
if (props.asDialog) {
@@ -710,4 +813,127 @@ defineExpose({
margin-top: 16px;
text-align: center;
}
/* Cover模式样式 */
.upload-cover-container {
display: inline-block;
}
.cover-image-wrapper {
position: relative;
width: 200px;
height: 150px;
border-radius: 8px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
background: #f5f7fa;
&:hover .cover-actions {
opacity: 1;
}
}
.cover-image {
display: block;
object-fit: contain;
max-width: 100%;
max-height: 100%;
&.cover-image-horizontal {
width: 100%;
height: auto;
}
&.cover-image-vertical {
height: 100%;
width: auto;
}
}
.cover-actions {
position: absolute;
top: 8px;
right: 8px;
opacity: 0;
transition: opacity 0.3s;
.delete-btn {
background: rgba(0, 0, 0, 0.6);
border: none;
&:hover {
background: rgba(0, 0, 0, 0.8);
}
}
}
.upload-cover-area {
width: 200px;
height: 150px;
border: 2px dashed #dcdfe6;
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
background: #fafafa;
transition: all 0.3s;
position: relative;
&:hover {
border-color: #409eff;
background: #f0f9ff;
}
&.is-dragover {
border-color: #409eff;
background: #e6f7ff;
}
&.is-disabled {
cursor: not-allowed;
opacity: 0.6;
}
}
.upload-cover-icon {
font-size: 32px;
color: #8c939d;
margin-bottom: 8px;
}
.upload-cover-tip {
font-size: 12px;
color: #909399;
text-align: center;
padding: 0 10px;
margin-top: 8px;
}
.uploading-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.9);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.el-icon {
font-size: 32px;
color: #409eff;
margin-bottom: 8px;
}
div {
font-size: 14px;
color: #606266;
}
}
</style>