web-学习管理、upload组件修改
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user