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>
import { ref, onMounted } from 'vue'
import { ref, onMounted, computed, nextTick } from 'vue'
import { Search, Plus, Edit, Delete } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getProjectList, deleteProject, updateProjectSort, createProject, updateProject } from '@/api/projects'
import { formatDateTime } from '@/utils/format'
import { sortArrayByField } from '@/utils/sort'
import request from '@/utils/request'
//
const tableData = ref([])
@ -55,7 +56,8 @@ const formData = ref({
cover_image: '',
type: '',
sort_order: 0,
status: 1
status: 1,
imageInputType: 'upload' //
})
//
@ -73,9 +75,30 @@ const rules = {
sort_order: [
{ required: true, 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 () => {
try {
@ -149,7 +172,8 @@ const handleAdd = () => {
cover_image: '',
type: '',
sort_order: 0,
status: 1
status: 1,
imageInputType: 'upload' //
}
dialogVisible.value = true
}
@ -157,7 +181,14 @@ const handleAdd = () => {
//
const handleEdit = (row) => {
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
}
@ -193,15 +224,53 @@ const handleSortChange = async (row) => {
}
}
//
const handleUploadSuccess = (res) => {
formData.value.cover_image = res.url
ElMessage.success('上传成功')
//
const handleImageTypeChange = () => {
formData.value.cover_image = ''
}
//
const handleUploadError = () => {
ElMessage.error('上传失败')
//
const handleUploadSuccess = (response) => {
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 {
await formRef.value.validate()
if (formData.value.id) {
await updateProject(formData.value.id, formData.value)
ElMessage.success('更新成功')
} else {
await createProject(formData.value)
ElMessage.success('创建成功')
//
const submitData = { ...formData.value }
delete submitData.imageInputType //
// cover_image
if (!submitData.cover_image) {
ElMessage.error('请先上传封面图片或输入图片地址')
return
}
dialogVisible.value = false
getList()
if (submitData.id) {
const res = await updateProject(submitData.id, submitData)
if (res.success) {
ElMessage.success('更新成功')
dialogVisible.value = false
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) {
console.error('提交失败:', error)
ElMessage.error('提交失败')
if (error.message) {
ElMessage.error(error.message)
} else {
console.error('提交失败:', error)
ElMessage.error('提交失败,请检查表单数据')
}
}
}
@ -233,6 +325,16 @@ const handleDialogClose = () => {
dialogVisible.value = false
}
//
const handleImageError = (e) => {
console.error('图片加载失败:', {
src: e.target.src,
error: e
})
ElMessage.error('图片加载失败,请检查图片地址是否正确')
formData.value.cover_image = ''
}
//
onMounted(() => {
getList()
@ -419,23 +521,47 @@ onMounted(() => {
</el-form-item>
<el-form-item label="封面图片" prop="cover_image">
<el-upload
class="avatar-uploader"
action="/api/admin/upload"
:show-file-list="false"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
accept="image/*"
>
<img
v-if="formData.cover_image"
:src="formData.cover_image"
class="avatar"
>
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
<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
class="avatar-uploader"
action="http://localhost:3000/api/admin/projects/upload"
:headers="uploadHeaders"
:show-file-list="false"
accept="image/*"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:before-upload="beforeUpload"
name="file"
>
<img
v-if="formData.cover_image"
:src="imageUrl"
class="avatar"
@error="handleImageError"
crossorigin="anonymous"
/>
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
<div class="el-upload__tip">
只能上传 jpg/png/gif 格式图片且不超过 2MB
</div>
</template>
</div>
</el-form-item>
<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 {
:deep(.el-upload) {
border: 1px dashed var(--el-border-color);
@ -530,12 +676,6 @@ onMounted(() => {
width: 178px;
height: 178px;
display: block;
}
.content-cell {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 300px;
object-fit: cover;
}
</style>