Files
ApiServer-Web-admin_dashboa…/src/components/admin/AvatarSelector.vue
T
lin b3ed406f84
Build and Deploy Vue3 / build (push) Successful in 1m31s
Build and Deploy Vue3 / deploy (push) Successful in 1m9s
fix: 提交修改
2026-04-15 16:02:36 +08:00

436 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<el-dialog
v-model="visible"
:title="title"
width="800px"
append-to-body
@close="handleClose"
>
<div class="avatar-selector">
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
<!-- 文件列表 -->
<el-tab-pane label="文件" name="userFiles">
<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="file-grid" v-loading="loading">
<div
v-for="file in fileList"
:key="file.cover_id"
class="file-item"
:class="{ 'selected': selectedId === file.cover_id }"
@click="selectFile(file)"
>
<div class="file-preview">
<img
v-if="isImageFile(file)"
:src="file.url"
:alt="file.realName"
@error="handleImageError"
/>
<el-icon v-else class="file-icon"><Document /></el-icon>
</div>
<div class="file-info">
<p class="file-name">{{ file.realName }}</p>
<p class="file-size">{{ formatFileSize(file.size) }}</p>
</div>
</div>
</div>
<el-empty v-if="fileList.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="[10, 20, 30, 50]"
: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/*"
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文件且不超过2MB
</div>
</template>
</el-upload>
</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 } from 'vue'
import { ElMessage } from 'element-plus'
import { Upload, UploadFilled, Document } from '@element-plus/icons-vue'
import { getFileList, getFileDetail, uploadFile } from '@/api/admin/file'
import { closeAllMessage } from '../../utils/message'
// Props
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
userId: {
type: [String, Number],
required: true
},
currentCoverId: {
type: [String, Number],
default: ''
},
title: {
type: String,
default: '选择文件'
}
})
// Emits
const emit = defineEmits(['update:modelValue', 'confirm'])
// 响应式数据
const visible = ref(false)
const activeTab = ref('userFiles')
const fileList = ref([])
const loading = ref(false)
const selectedId = ref('')
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
// 监听 modelValue 变化
watch(() => props.modelValue, (newVal) => {
visible.value = newVal
if (newVal) {
selectedId.value = props.currentCoverId
currentPage.value = 1
fetchFileList()
}
})
// 监听 visible 变化
watch(visible, (newVal) => {
emit('update:modelValue', newVal)
})
// 获取文件列表
const fetchFileList = async () => {
if (!props.userId) return
loading.value = true
fileList.value = [] // 清空列表
try {
const res = await getFileList({
page: currentPage.value,
count: pageSize.value
})
console.log("获取文件列表:", res)
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 {
console.log("获取文件详情:", list[i].id)
const res2 = await getFileDetail({ file_id: list[i].id })
if (res2.data.code === 200) {
fileList.value.push({
url: res2.data.data.url,
cover_id: res2.data.data.data.id,
size: res2.data.data.data.size,
realName: res2.data.data.data.realName
})
}
} catch (error) {
console.error('获取文件详情失败:', error)
}
}
console.log("文件列表1237", fileList.value)
}
} catch (error) {
console.error('获取文件列表失败:', error)
ElMessage.error('获取文件列表失败')
} finally {
loading.value = false
}
}
// 处理标签页切换
const handleTabClick = (tab) => {
if (tab.name === 'userFiles') {
currentPage.value = 1
fetchFileList()
}
}
// 分页处理
const handleSizeChange = (size) => {
pageSize.value = size
currentPage.value = 1
fetchFileList()
}
const handlePageChange = (page) => {
currentPage.value = page
fetchFileList()
}
// 切换到上传标签页
const switchToUpload = () => {
activeTab.value = 'upload'
}
// 判断是否为图片文件
const isImageFile = (file) => {
const imageTypes = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']
const extension = file.realName?.split('.').pop()?.toLowerCase()
return imageTypes.includes(extension)
}
// 格式化文件大小
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.cover_id
}
// 上传前验证
const beforeUpload = (file) => {
const isImage = file.type.startsWith('image/')
const isLt2M = file.size / 1024 / 1024 < 2
if (!isImage) {
ElMessage.error('只能上传图片文件!')
return false
}
if (!isLt2M) {
ElMessage.error('图片大小不能超过 2MB!')
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)
formData.append('update_type', 'cover')
formData.append('open_down', 'true')
try {
const res = await uploadFile(formData)
console.log("上传文件:", res)
if (res.data.code === 200) {
ElMessage.success("上传成功")
// 重置到第一页并刷新文件列表
currentPage.value = 1
await fetchFileList()
// 切换到文件列表标签页
activeTab.value = 'userFiles'
// 自动选择新上传的文件
if (res.data.data?.id) {
selectedId.value = res.data.data.id
}
} else {
ElMessage.error(res.data.msg || '上传失败')
}
} catch (error) {
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
}
// 确认选择
const handleConfirm = () => {
if (selectedId.value) {
const selectedFile = fileList.value.find(file => file.cover_id === selectedId.value)
emit('confirm', {
cover_id: selectedId.value,
url: selectedFile?.url || ''
})
handleClose()
}
}
</script>
<style scoped>
.avatar-selector {
min-height: 400px;
}
.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;
}
.file-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 16px;
max-height: 400px;
overflow-y: auto;
}
.file-item {
border: 2px solid #e4e7ed;
border-radius: 8px;
padding: 12px;
cursor: pointer;
transition: all 0.3s ease;
text-align: center;
}
.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: 80px;
height: 80px;
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-preview .file-icon {
font-size: 32px;
color: #909399;
}
.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;
}
.file-size {
font-size: 11px;
color: #909399;
margin: 0;
}
.upload-section {
padding: 40px 20px;
text-align: center;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: center;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>