fix: 设置动态编辑配置图片
This commit is contained in:
@@ -0,0 +1,505 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="visible"
|
||||||
|
title="选择图片"
|
||||||
|
width="900px"
|
||||||
|
append-to-body
|
||||||
|
@close="handleClose"
|
||||||
|
>
|
||||||
|
<div class="image-selector">
|
||||||
|
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||||
|
<!-- 文件库 -->
|
||||||
|
<el-tab-pane label="文件库" name="fileLibrary">
|
||||||
|
<div class="file-list-container">
|
||||||
|
<div class="file-list-header">
|
||||||
|
<h4>图片文件库</h4>
|
||||||
|
<el-button type="primary" @click="switchToUpload" :icon="Upload">
|
||||||
|
上传新图片
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索过滤 -->
|
||||||
|
<div class="filter-section">
|
||||||
|
<el-input
|
||||||
|
v-model="searchKeyword"
|
||||||
|
placeholder="搜索文件名"
|
||||||
|
:prefix-icon="Search"
|
||||||
|
clearable
|
||||||
|
@input="handleSearch"
|
||||||
|
style="width: 300px;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="file-grid" v-loading="loading">
|
||||||
|
<div
|
||||||
|
v-for="file in filteredFileList"
|
||||||
|
:key="file.id"
|
||||||
|
class="file-item"
|
||||||
|
:class="{ 'selected': selectedId === file.id }"
|
||||||
|
@click="selectFile(file)"
|
||||||
|
>
|
||||||
|
<div class="file-preview">
|
||||||
|
<img
|
||||||
|
:src="processImageUrl(file.url)"
|
||||||
|
:alt="file.realName"
|
||||||
|
@error="handleImageError"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="file-info">
|
||||||
|
<p class="file-name" :title="file.realName">{{ file.realName }}</p>
|
||||||
|
<p class="file-size">{{ formatFileSize(file.size) }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-empty v-if="filteredFileList.length === 0 && !loading" description="暂无图片文件" />
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div class="pagination-container" v-if="total > 0">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="currentPage"
|
||||||
|
v-model:page-size="pageSize"
|
||||||
|
:page-sizes="[12, 24, 36, 48]"
|
||||||
|
:total="total"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
background
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<!-- 上传图片 -->
|
||||||
|
<el-tab-pane label="上传图片" name="upload">
|
||||||
|
<div class="upload-section">
|
||||||
|
<el-upload
|
||||||
|
:http-request="handleUpload"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
:show-file-list="false"
|
||||||
|
accept="image/*"
|
||||||
|
multiple
|
||||||
|
drag
|
||||||
|
>
|
||||||
|
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
|
||||||
|
<div class="el-upload__text">
|
||||||
|
将文件拖到此处,或<em>点击上传</em>
|
||||||
|
</div>
|
||||||
|
<template #tip>
|
||||||
|
<div class="el-upload__tip">
|
||||||
|
支持jpg、png、gif、webp等图片格式,单个文件不超过5MB
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-upload>
|
||||||
|
|
||||||
|
<!-- 上传进度 -->
|
||||||
|
<div v-if="uploadProgress.length > 0" class="upload-progress">
|
||||||
|
<h4>上传进度</h4>
|
||||||
|
<div v-for="progress in uploadProgress" :key="progress.id" class="progress-item">
|
||||||
|
<span>{{ progress.name }}</span>
|
||||||
|
<el-progress :percentage="progress.percentage" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="handleClose">取消</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
@click="handleConfirm"
|
||||||
|
:disabled="!selectedId"
|
||||||
|
>
|
||||||
|
确定选择
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch, computed } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { Upload, UploadFilled, Search } from '@element-plus/icons-vue'
|
||||||
|
import { getFileList, getFileDetail, uploadFile } from '@/api/admin/file'
|
||||||
|
|
||||||
|
// Props
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
currentFileId: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Emits
|
||||||
|
const emit = defineEmits(['update:modelValue', 'confirm'])
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const visible = ref(false)
|
||||||
|
const activeTab = ref('fileLibrary')
|
||||||
|
const fileList = ref([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const selectedId = ref('')
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const pageSize = ref(12)
|
||||||
|
const total = ref(0)
|
||||||
|
const searchKeyword = ref('')
|
||||||
|
const uploadProgress = ref([])
|
||||||
|
|
||||||
|
// 监听 modelValue 变化
|
||||||
|
watch(() => props.modelValue, (newVal) => {
|
||||||
|
visible.value = newVal
|
||||||
|
if (newVal) {
|
||||||
|
selectedId.value = props.currentFileId
|
||||||
|
currentPage.value = 1
|
||||||
|
searchKeyword.value = ''
|
||||||
|
fetchFileList()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听 visible 变化
|
||||||
|
watch(visible, (newVal) => {
|
||||||
|
emit('update:modelValue', newVal)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 过滤后的文件列表
|
||||||
|
const filteredFileList = computed(() => {
|
||||||
|
if (!searchKeyword.value) {
|
||||||
|
return fileList.value
|
||||||
|
}
|
||||||
|
return fileList.value.filter(file =>
|
||||||
|
file.realName?.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 处理图片URL,确保正确显示
|
||||||
|
const processImageUrl = (url) => {
|
||||||
|
if (!url) return ''
|
||||||
|
// 先处理转义字符:将 \u0026 替换为 &
|
||||||
|
let processedUrl = url.replace(/\\u0026/g, '&')
|
||||||
|
// 再进行URL解码
|
||||||
|
return decodeURIComponent(processedUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件列表
|
||||||
|
const fetchFileList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
fileList.value = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getFileList({
|
||||||
|
page: currentPage.value,
|
||||||
|
count: pageSize.value
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
const list = res.data.data.list || []
|
||||||
|
total.value = res.data.data.all_count || 0
|
||||||
|
|
||||||
|
// 获取每个文件的详情
|
||||||
|
for (let i = 0; i < list.length; i++) {
|
||||||
|
try {
|
||||||
|
const res2 = await getFileDetail({ file_id: list[i].id })
|
||||||
|
if (res2.data.code === 200) {
|
||||||
|
fileList.value.push({
|
||||||
|
id: res2.data.data.data.id,
|
||||||
|
url: res2.data.data.url,
|
||||||
|
size: res2.data.data.data.size,
|
||||||
|
realName: res2.data.data.data.realName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取文件详情失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取文件列表失败:', error)
|
||||||
|
ElMessage.error('获取文件列表失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理标签页切换
|
||||||
|
const handleTabClick = (tab) => {
|
||||||
|
if (tab.name === 'fileLibrary') {
|
||||||
|
currentPage.value = 1
|
||||||
|
fetchFileList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理搜索
|
||||||
|
const handleSearch = () => {
|
||||||
|
// 搜索时重置到第一页
|
||||||
|
currentPage.value = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页处理
|
||||||
|
const handleSizeChange = (size) => {
|
||||||
|
pageSize.value = size
|
||||||
|
currentPage.value = 1
|
||||||
|
fetchFileList()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePageChange = (page) => {
|
||||||
|
currentPage.value = page
|
||||||
|
fetchFileList()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换到上传标签页
|
||||||
|
const switchToUpload = () => {
|
||||||
|
activeTab.value = 'upload'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化文件大小
|
||||||
|
const formatFileSize = (size) => {
|
||||||
|
if (!size) return '0 B'
|
||||||
|
const units = ['B', 'KB', 'MB', 'GB']
|
||||||
|
let unitIndex = 0
|
||||||
|
let fileSize = size
|
||||||
|
|
||||||
|
while (fileSize >= 1024 && unitIndex < units.length - 1) {
|
||||||
|
fileSize /= 1024
|
||||||
|
unitIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${fileSize.toFixed(1)} ${units[unitIndex]}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择文件
|
||||||
|
const selectFile = (file) => {
|
||||||
|
selectedId.value = file.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传前验证
|
||||||
|
const beforeUpload = (file) => {
|
||||||
|
const isImage = file.type.startsWith('image/')
|
||||||
|
const isLt5M = file.size / 1024 / 1024 < 5
|
||||||
|
|
||||||
|
if (!isImage) {
|
||||||
|
ElMessage.error('只能上传图片文件!')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!isLt5M) {
|
||||||
|
ElMessage.error('图片大小不能超过 5MB!')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自定义上传
|
||||||
|
const handleUpload = async (options) => {
|
||||||
|
const { file } = options
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('files', file)
|
||||||
|
formData.append('file_names', file.name)
|
||||||
|
|
||||||
|
// 添加上传进度跟踪
|
||||||
|
const progressId = Date.now() + Math.random()
|
||||||
|
uploadProgress.value.push({
|
||||||
|
id: progressId,
|
||||||
|
name: file.name,
|
||||||
|
percentage: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await uploadFile(formData)
|
||||||
|
|
||||||
|
// 移除进度跟踪
|
||||||
|
uploadProgress.value = uploadProgress.value.filter(p => p.id !== progressId)
|
||||||
|
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
ElMessage.success("上传成功")
|
||||||
|
// 重置到第一页并刷新文件列表
|
||||||
|
currentPage.value = 1
|
||||||
|
await fetchFileList()
|
||||||
|
// 切换到文件库标签页
|
||||||
|
activeTab.value = 'fileLibrary'
|
||||||
|
// 自动选择新上传的文件
|
||||||
|
if (res.data.data?.id) {
|
||||||
|
selectedId.value = res.data.data.id
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.data.msg || '上传失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 移除进度跟踪
|
||||||
|
uploadProgress.value = uploadProgress.value.filter(p => p.id !== progressId)
|
||||||
|
console.error('上传失败:', error)
|
||||||
|
ElMessage.error('上传失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片加载错误处理
|
||||||
|
const handleImageError = (event) => {
|
||||||
|
event.target.style.display = 'none'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭对话框
|
||||||
|
const handleClose = () => {
|
||||||
|
visible.value = false
|
||||||
|
selectedId.value = ''
|
||||||
|
fileList.value = []
|
||||||
|
currentPage.value = 1
|
||||||
|
total.value = 0
|
||||||
|
searchKeyword.value = ''
|
||||||
|
uploadProgress.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认选择
|
||||||
|
const handleConfirm = () => {
|
||||||
|
if (selectedId.value) {
|
||||||
|
const selectedFile = fileList.value.find(file => file.id === selectedId.value)
|
||||||
|
emit('confirm', {
|
||||||
|
id: selectedId.value,
|
||||||
|
url: selectedFile?.url || '',
|
||||||
|
realName: selectedFile?.realName || ''
|
||||||
|
})
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.image-selector {
|
||||||
|
min-height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list-container {
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list-header h4 {
|
||||||
|
margin: 0;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
max-height: 450px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
border: 2px solid #e4e7ed;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
text-align: center;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item:hover {
|
||||||
|
border-color: #409EFF;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item.selected {
|
||||||
|
border-color: #409EFF;
|
||||||
|
background-color: #f0f9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-preview {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin: 0 auto 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-preview img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #303133;
|
||||||
|
margin: 0 0 4px 0;
|
||||||
|
word-break: break-all;
|
||||||
|
line-height: 1.3;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-size {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #909399;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-section {
|
||||||
|
padding: 40px 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress {
|
||||||
|
margin-top: 30px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress h4 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-item {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-item span {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+1158
-145
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user