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:
parent
777532f396
commit
c041ef9671
@ -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>
|
||||
Loading…
x
Reference in New Issue
Block a user