Enhance project introduction module with advanced image upload and input features

- Add support for image upload and URL input with flexible selection
- Implement robust image upload validation and error handling
- Update form submission logic to handle different image input types
- Add computed properties for image URL processing and upload headers
- Improve user experience with better image upload feedback and validation
This commit is contained in:
wzclm 2025-02-23 16:54:34 +08:00
parent 777532f396
commit c041ef9671
2 changed files with 185 additions and 45 deletions

View File

@ -1,10 +1,11 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, onMounted, computed, nextTick } from 'vue'
import { Search, Plus, Edit, Delete } from '@element-plus/icons-vue' import { Search, Plus, Edit, Delete } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { getProjectList, deleteProject, updateProjectSort, createProject, updateProject } from '@/api/projects' import { getProjectList, deleteProject, updateProjectSort, createProject, updateProject } from '@/api/projects'
import { formatDateTime } from '@/utils/format' import { formatDateTime } from '@/utils/format'
import { sortArrayByField } from '@/utils/sort' import { sortArrayByField } from '@/utils/sort'
import request from '@/utils/request'
// //
const tableData = ref([]) const tableData = ref([])
@ -55,7 +56,8 @@ const formData = ref({
cover_image: '', cover_image: '',
type: '', type: '',
sort_order: 0, sort_order: 0,
status: 1 status: 1,
imageInputType: 'upload' //
}) })
// //
@ -73,9 +75,30 @@ const rules = {
sort_order: [ sort_order: [
{ required: true, message: '请输入排序', trigger: 'blur' }, { required: true, message: '请输入排序', trigger: 'blur' },
{ type: 'number', message: '排序必须为数字', trigger: 'blur' } { type: 'number', message: '排序必须为数字', trigger: 'blur' }
],
cover_image: [
{ required: true, message: '请上传封面图片或输入图片地址', trigger: ['change', 'blur'] }
] ]
} }
// URL
const imageUrl = computed(() => {
const url = formData.value.cover_image
if (!url) return ''
if (url.startsWith('data:')) return url
if (url.startsWith('http')) return url
// 使URL
return `http://localhost:3000${url}`
})
//
const uploadHeaders = computed(() => {
const token = localStorage.getItem('token')
return {
Authorization: token ? `Bearer ${token}` : ''
}
})
// //
const getList = async () => { const getList = async () => {
try { try {
@ -149,7 +172,8 @@ const handleAdd = () => {
cover_image: '', cover_image: '',
type: '', type: '',
sort_order: 0, sort_order: 0,
status: 1 status: 1,
imageInputType: 'upload' //
} }
dialogVisible.value = true dialogVisible.value = true
} }
@ -157,7 +181,14 @@ const handleAdd = () => {
// //
const handleEdit = (row) => { const handleEdit = (row) => {
dialogTitle.value = '编辑项目简介' dialogTitle.value = '编辑项目简介'
formData.value = { ...row } formData.value = {
...row,
imageInputType: row.cover_image?.startsWith('http') ? 'url' : 'upload' //
}
//
nextTick(() => {
formRef.value?.validateField('cover_image')
})
dialogVisible.value = true dialogVisible.value = true
} }
@ -193,15 +224,53 @@ const handleSortChange = async (row) => {
} }
} }
// //
const handleUploadSuccess = (res) => { const handleImageTypeChange = () => {
formData.value.cover_image = res.url formData.value.cover_image = ''
ElMessage.success('上传成功')
} }
// //
const handleUploadError = () => { const handleUploadSuccess = (response) => {
ElMessage.error('上传失败') console.log('上传响应:', response)
try {
if (response.success) {
const imageUrl = response.data?.url
if (!imageUrl) {
ElMessage.error('上传成功但未获取到图片地址')
return
}
// 使URL
formData.value.cover_image = imageUrl
ElMessage.success('图片上传成功')
} else {
ElMessage.error(response.message || '图片上传失败')
}
} catch (error) {
console.error('处理上传响应错误:', error)
ElMessage.error('处理上传响应失败')
}
}
//
const handleUploadError = (error) => {
console.error('上传失败:', error)
formData.value.cover_image = ''
}
//
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
} }
// //
@ -211,19 +280,42 @@ const handleSubmit = async () => {
try { try {
await formRef.value.validate() await formRef.value.validate()
if (formData.value.id) { //
await updateProject(formData.value.id, formData.value) const submitData = { ...formData.value }
ElMessage.success('更新成功') delete submitData.imageInputType //
} else {
await createProject(formData.value) // cover_image
ElMessage.success('创建成功') if (!submitData.cover_image) {
ElMessage.error('请先上传封面图片或输入图片地址')
return
} }
if (submitData.id) {
const res = await updateProject(submitData.id, submitData)
if (res.success) {
ElMessage.success('更新成功')
dialogVisible.value = false dialogVisible.value = false
getList() getList()
} else {
ElMessage.error(res.message || '更新失败')
}
} else {
const res = await createProject(submitData)
if (res.success) {
ElMessage.success('创建成功')
dialogVisible.value = false
getList()
} else {
ElMessage.error(res.message || '创建失败')
}
}
} catch (error) { } catch (error) {
if (error.message) {
ElMessage.error(error.message)
} else {
console.error('提交失败:', error) console.error('提交失败:', error)
ElMessage.error('提交失败') ElMessage.error('提交失败,请检查表单数据')
}
} }
} }
@ -233,6 +325,16 @@ const handleDialogClose = () => {
dialogVisible.value = false dialogVisible.value = false
} }
//
const handleImageError = (e) => {
console.error('图片加载失败:', {
src: e.target.src,
error: e
})
ElMessage.error('图片加载失败,请检查图片地址是否正确')
formData.value.cover_image = ''
}
// //
onMounted(() => { onMounted(() => {
getList() getList()
@ -419,23 +521,47 @@ onMounted(() => {
</el-form-item> </el-form-item>
<el-form-item label="封面图片" prop="cover_image"> <el-form-item label="封面图片" prop="cover_image">
<div class="image-input-container">
<el-radio-group v-model="formData.imageInputType" class="mb-4" @change="handleImageTypeChange">
<el-radio value="url">输入图片地址</el-radio>
<el-radio value="upload">上传图片</el-radio>
</el-radio-group>
<template v-if="formData.imageInputType === 'url'">
<el-input
v-model="formData.cover_image"
placeholder="请输入图片URL地址"
/>
</template>
<template v-else>
<el-upload <el-upload
class="avatar-uploader" class="avatar-uploader"
action="/api/admin/upload" action="http://localhost:3000/api/admin/projects/upload"
:headers="uploadHeaders"
:show-file-list="false" :show-file-list="false"
accept="image/*"
:on-success="handleUploadSuccess" :on-success="handleUploadSuccess"
:on-error="handleUploadError" :on-error="handleUploadError"
accept="image/*" :before-upload="beforeUpload"
name="file"
> >
<img <img
v-if="formData.cover_image" v-if="formData.cover_image"
:src="formData.cover_image" :src="imageUrl"
class="avatar" class="avatar"
> @error="handleImageError"
crossorigin="anonymous"
/>
<el-icon v-else class="avatar-uploader-icon"> <el-icon v-else class="avatar-uploader-icon">
<Plus /> <Plus />
</el-icon> </el-icon>
</el-upload> </el-upload>
<div class="el-upload__tip">
只能上传 jpg/png/gif 格式图片且不超过 2MB
</div>
</template>
</div>
</el-form-item> </el-form-item>
<el-form-item label="内容" prop="content"> <el-form-item label="内容" prop="content">
@ -502,6 +628,26 @@ onMounted(() => {
} }
} }
.content-cell {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 300px;
}
.image-input-container {
.mb-4 {
margin-bottom: 16px;
display: block;
}
.el-upload__tip {
font-size: 12px;
color: #909399;
margin-top: 8px;
}
}
.avatar-uploader { .avatar-uploader {
:deep(.el-upload) { :deep(.el-upload) {
border: 1px dashed var(--el-border-color); border: 1px dashed var(--el-border-color);
@ -530,12 +676,6 @@ onMounted(() => {
width: 178px; width: 178px;
height: 178px; height: 178px;
display: block; display: block;
} object-fit: cover;
.content-cell {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 300px;
} }
</style> </style>