@@ -397,7 +400,12 @@
-
+
- 建议上传正方形图片,系统将自动压缩处理,显示在任务卡片上
+ 建议上传正方形图片,系统将自动压缩处理,显示在任务卡片上。支持 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 {