diff --git a/frontend/src/views/CreateTask.vue b/frontend/src/views/CreateTask.vue index ba1e304..cab59c0 100644 --- a/frontend/src/views/CreateTask.vue +++ b/frontend/src/views/CreateTask.vue @@ -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)" >
-
+
@@ -397,7 +400,12 @@ -
+
上传预览图
+
或按 Ctrl+V 粘贴图片
- 建议上传正方形图片,系统将自动压缩处理,显示在任务卡片上 + 建议上传正方形图片,系统将自动压缩处理,显示在任务卡片上。支持 Ctrl+V 粘贴图片
@@ -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) }) @@ -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 {