feat: 1.预览图支持粘贴图片 2.修复子任务拖拽排序功能

This commit is contained in:
Your Name 2026-01-16 12:03:12 +08:00
parent 96f2521511
commit 7b6d7a3fe1

View File

@ -71,16 +71,19 @@
:key="item.id || index"
class="task-type-item"
:class="{ 'dragging': dragIndex === index, 'drag-over': dragOverIndex === index }"
draggable="true"
@dragstart="handleDragStart($event, index)"
@dragend="handleDragEnd"
@dragover.prevent="handleDragOver($event, index)"
@dragleave="handleDragLeave"
@drop="handleDrop($event, index)"
>
<div class="task-type-row">
<!-- 拖拽手柄 -->
<div class="drag-handle" title="拖拽调整顺序">
<div
class="drag-handle"
title="拖拽调整顺序"
draggable="true"
@dragstart="handleDragStart($event, index)"
@dragend="handleDragEnd"
>
<Icon name="menu" size="sm" />
</div>
<div class="task-type-select task-type-cascader">
@ -397,7 +400,12 @@
<!-- 预览图上传 -->
<a-form-item label="预览图" name="previewImage">
<div class="preview-upload-area">
<div
class="preview-upload-area"
@paste="handlePasteImage"
tabindex="0"
style="outline: none;"
>
<a-upload
v-model:file-list="previewFileList"
:before-upload="beforePreviewUpload"
@ -410,13 +418,14 @@
<div v-if="!formData.previewImage" class="upload-button">
<Icon name="plus" size="lg" />
<div class="upload-text">上传预览图</div>
<div class="paste-hint">或按 Ctrl+V 粘贴图片</div>
</div>
</a-upload>
<div class="preview-tip">
<span class="tip-icon">
<Icon name="image" size="sm" color="#31f08b" />
</span>
<span>建议上传正方形图片,系统将自动压缩处理,显示在任务卡片上</span>
<span>建议上传正方形图片,系统将自动压缩处理,显示在任务卡片上。支持 Ctrl+V 粘贴图片</span>
</div>
</div>
</a-form-item>
@ -941,6 +950,20 @@ const handleDragStart = (e: DragEvent, index: number) => {
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = 'move'
e.dataTransfer.setData('text/plain', String(index))
e.dataTransfer.setData('application/json', JSON.stringify({ index }))
// 设置拖拽图像为整个 item
const target = e.target as HTMLElement
const itemElement = target.closest('.task-type-item') as HTMLElement
if (itemElement) {
// 创建一个临时元素作为拖拽图像
const dragImage = itemElement.cloneNode(true) as HTMLElement
dragImage.style.opacity = '0.8'
dragImage.style.position = 'absolute'
dragImage.style.top = '-1000px'
document.body.appendChild(dragImage)
e.dataTransfer.setDragImage(dragImage, e.offsetX, e.offsetY)
setTimeout(() => document.body.removeChild(dragImage), 0)
}
}
}
@ -966,19 +989,37 @@ const handleDragLeave = () => {
// 放置
const handleDrop = (e: DragEvent, targetIndex: number) => {
e.preventDefault()
if (dragIndex.value === null || dragIndex.value === targetIndex) {
e.stopPropagation()
// 从拖拽数据中获取源索引
let sourceIndex = dragIndex.value
if (sourceIndex === null) {
// 尝试从 dataTransfer 中读取
const data = e.dataTransfer?.getData('text/plain')
if (data) {
sourceIndex = parseInt(data, 10)
} else {
handleDragEnd()
return
}
}
if (sourceIndex === null || sourceIndex === targetIndex || sourceIndex < 0 || targetIndex < 0) {
handleDragEnd()
return
}
console.log('拖拽排序:', sourceIndex, '->', targetIndex)
// 交换位置 - 使用 splice 原地修改数组以确保响应式更新
const sourceIndex = dragIndex.value
const [removed] = formData.taskTypes.splice(sourceIndex, 1)
formData.taskTypes.splice(targetIndex, 0, removed)
// 重新计算积分(pipelineGroups 是 computed,会自动更新)
calculateScore()
message.success('任务顺序已调整')
handleDragEnd()
}
@ -1730,6 +1771,83 @@ const handlePreviewRemove = () => {
return true
}
// 处理粘贴图片
const handlePasteImage = async (e: ClipboardEvent) => {
e.preventDefault()
// 如果已经有预览图,不处理粘贴
if (formData.previewImage) {
return
}
const items = e.clipboardData?.items
if (!items) return
// 查找图片数据
for (let i = 0; i < items.length; i++) {
const item = items[i]
if (item.type.indexOf('image') !== -1) {
const file = item.getAsFile()
if (!file) continue
// 验证文件大小
const isLt10M = file.size / 1024 / 1024 < 10
if (!isLt10M) {
message.error('图片大小不能超过 10MB')
return
}
// 创建 File 对象用于上传
const uploadFile: UploadFile = {
uid: `paste-${Date.now()}`,
name: `粘贴图片-${Date.now()}.png`,
status: 'uploading',
originFileObj: file
}
previewFileList.value = [uploadFile]
// 调用上传处理函数
try {
previewUploading.value = true
const formDataObj = new FormData()
formDataObj.append('file', file)
const response = await fetch('/api/file/upload-preview', {
method: 'POST',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: formDataObj
})
const result = await response.json()
if (result.code === 200 && result.data) {
formData.previewImage = result.data
previewFileList.value = [{
uid: uploadFile.uid,
name: uploadFile.name,
status: 'done',
url: result.data
} as UploadFile]
message.success('图片粘贴成功')
} else {
throw new Error(result.msg || '上传失败')
}
} catch (error: any) {
console.error('粘贴图片上传失败:', error)
previewFileList.value = []
message.error('粘贴图片上传失败: ' + error.message)
} finally {
previewUploading.value = false
}
break
}
}
}
// 加载任务数据用于编辑
const loadTaskForEdit = async (taskId: number) => {
try {
@ -1908,8 +2026,19 @@ const loadReleaseTypeConfig = async () => {
}
}
// 全局粘贴事件处理(当预览图区域可见时)
const handleGlobalPaste = (e: ClipboardEvent) => {
// 如果预览图区域可见且没有图片,处理粘贴
if (!formData.previewImage) {
handlePasteImage(e)
}
}
// 组件挂载
onMounted(async () => {
// 添加全局粘贴事件监听
document.addEventListener('paste', handleGlobalPaste as EventListener)
// 先刷新用户信息,确保部门信息最新
try {
await authStore.fetchUserInfo()
@ -1941,6 +2070,7 @@ onMounted(async () => {
// 组件卸载
onUnmounted(() => {
window.removeEventListener('configUpdated', handleConfigUpdate as EventListener)
document.removeEventListener('paste', handleGlobalPaste as EventListener)
})
</script>
@ -2678,6 +2808,21 @@ onUnmounted(() => {
margin-top: 8px;
font-size: 12px;
}
.paste-hint {
margin-top: 4px;
font-size: 11px;
color: #666;
opacity: 0.7;
}
}
// 当区域获得焦点时显示提示
&:focus-within {
.paste-hint {
opacity: 1;
color: $brand-primary;
}
}
.preview-tip {