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>
|
<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>
|
||||||
Loading…
x
Reference in New Issue
Block a user