# 企业微信拖拽数据结构分析文档 ## 🎯 目标 记录从企业微信拖拽不同类型消息到AI对话区域时的完整数据结构,为功能实现提供准确的参考。 ## 📊 测试方法 ### 打印函数 ```typescript /** * 详细打印拖拽数据结构 */ private logDragDataStructure(event: DragEvent, context: string): void { console.log(`\n========== [${context}] 拖拽数据结构分析 ==========`); const dt = event.dataTransfer; if (!dt) { console.log('❌ dataTransfer 为空'); return; } // 1. 基本信息 console.log('\n📋 基本信息:'); console.log(' dropEffect:', dt.dropEffect); console.log(' effectAllowed:', dt.effectAllowed); console.log(' types:', Array.from(dt.types)); // 2. Files console.log('\n📁 Files 对象:'); console.log(' files.length:', dt.files?.length || 0); if (dt.files && dt.files.length > 0) { for (let i = 0; i < dt.files.length; i++) { const file = dt.files[i]; console.log(` [${i}] File对象:`, { name: file.name, size: file.size, type: file.type, lastModified: new Date(file.lastModified).toLocaleString(), webkitRelativePath: (file as any).webkitRelativePath || '' }); } } // 3. Items console.log('\n📦 Items 对象:'); console.log(' items.length:', dt.items?.length || 0); if (dt.items && dt.items.length > 0) { for (let i = 0; i < dt.items.length; i++) { const item = dt.items[i]; console.log(` [${i}] DataTransferItem:`, { kind: item.kind, type: item.type }); // 尝试获取item的内容 if (item.kind === 'string') { item.getAsString((str) => { console.log(` → 字符串内容 (${item.type}):`, str.substring(0, 200)); }); } else if (item.kind === 'file') { const file = item.getAsFile(); console.log(` → 文件对象:`, file); } } } // 4. 各种数据类型 console.log('\n📝 getData() 测试:'); const commonTypes = [ 'text/plain', 'text/html', 'text/uri-list', 'text/rtf', 'application/json', 'Files' ]; for (const type of commonTypes) { try { const data = dt.getData(type); if (data) { console.log(` ${type}:`, data.length > 200 ? data.substring(0, 200) + '...' : data); } } catch (e) { // 某些类型可能不可访问 } } // 5. 自定义数据类型 console.log('\n🔍 自定义数据类型:'); if (dt.types) { for (const type of dt.types) { if (!commonTypes.includes(type)) { try { const data = dt.getData(type); console.log(` ${type}:`, data); } catch (e) { console.log(` ${type}: [无法读取]`); } } } } console.log('\n========== 数据结构分析结束 ==========\n'); } ``` ## 📂 测试场景与数据结构 ### 场景1: 拖拽单张图片 **操作**: 从企业微信群聊拖拽一张JPG图片 **预期数据结构**: ```typescript { 基本信息: { dropEffect: "none", effectAllowed: "all", types: ["Files", "text/html", "text/plain"] }, Files对象: { length: 1, [0]: { name: "image.jpg", size: 1234567, // 字节 type: "image/jpeg", lastModified: "2025-12-03 10:30:00", webkitRelativePath: "" } }, Items对象: { length: 3, [0]: { kind: "file", type: "" }, [1]: { kind: "string", type: "text/html" }, [2]: { kind: "string", type: "text/plain" } }, getData测试: { "text/plain": "[图片]", "text/html": "", "text/uri-list": "" } } ``` ### 场景2: 拖拽多张图片 **操作**: 从企业微信群聊选择3张图片一起拖拽 **预期数据结构**: ```typescript { 基本信息: { types: ["Files", "text/html", "text/plain"] }, Files对象: { length: 3, [0]: { name: "image1.jpg", size: 1234567, type: "image/jpeg" }, [1]: { name: "image2.png", size: 2345678, type: "image/png" }, [2]: { name: "image3.jpg", size: 3456789, type: "image/jpeg" } }, Items对象: { length: 5, // Files + HTML + Plain [0]: { kind: "file", type: "" }, [1]: { kind: "file", type: "" }, [2]: { kind: "file", type: "" }, [3]: { kind: "string", type: "text/html" }, [4]: { kind: "string", type: "text/plain" } }, getData测试: { "text/plain": "[图片] [图片] [图片]", "text/html": "" } } ``` ### 场景3: 拖拽纯文字消息 **操作**: 从企业微信群聊拖拽一条文字消息 **预期数据结构**: ```typescript { 基本信息: { types: ["text/html", "text/plain"] }, Files对象: { length: 0 // 没有文件 }, Items对象: { length: 2, [0]: { kind: "string", type: "text/html" }, [1]: { kind: "string", type: "text/plain" } }, getData测试: { "text/plain": "客户要求:现代简约风格,采光要好,预算20万", "text/html": "
客户要求:现代简约风格,采光要好,预算20万
" } } ``` ### 场景4: 拖拽图片+文字(混合内容) **操作**: 从企业微信群聊同时选择图片和文字消息拖拽 **预期数据结构**: ```typescript { 基本信息: { types: ["Files", "text/html", "text/plain"] }, Files对象: { length: 2, [0]: { name: "image1.jpg", size: 1234567, type: "image/jpeg" }, [1]: { name: "image2.jpg", size: 2345678, type: "image/jpeg" } }, Items对象: { length: 4, [0]: { kind: "file", type: "" }, [1]: { kind: "file", type: "" }, [2]: { kind: "string", type: "text/html" }, [3]: { kind: "string", type: "text/plain" } }, getData测试: { "text/plain": "[图片] [图片] 客户要求:现代简约风格...", "text/html": "
客户要求:...
" } } ``` ### 场景5: 拖拽图片URL链接 **操作**: 从企业微信拖拽图片的URL链接 **预期数据结构**: ```typescript { 基本信息: { types: ["text/uri-list", "text/html", "text/plain"] }, Files对象: { length: 0 }, Items对象: { length: 3, [0]: { kind: "string", type: "text/uri-list" }, [1]: { kind: "string", type: "text/html" }, [2]: { kind: "string", type: "text/plain" } }, getData测试: { "text/uri-list": "https://file-cloud.fmode.cn/path/to/image.jpg", "text/plain": "https://file-cloud.fmode.cn/path/to/image.jpg", "text/html": "..." } } ``` ### 场景6: 拖拽文件(非图片) **操作**: 从企业微信群聊拖拽PDF或CAD文件 **预期数据结构**: ```typescript { 基本信息: { types: ["Files", "text/html", "text/plain"] }, Files对象: { length: 1, [0]: { name: "design.pdf", size: 5678901, type: "application/pdf", lastModified: "2025-12-03 10:30:00" } }, Items对象: { length: 3, [0]: { kind: "file", type: "application/pdf" }, [1]: { kind: "string", type: "text/html" }, [2]: { kind: "string", type: "text/plain" } }, getData测试: { "text/plain": "[文件] design.pdf", "text/html": "design.pdf" } } ``` ## 🔍 关键发现 ### 1. Types数组的特点 ```typescript // 企业微信拖拽通常包含的types types: [ "Files", // 有文件时出现 "text/html", // 几乎总是存在 "text/plain", // 几乎总是存在 "text/uri-list" // 有URL链接时出现 ] ``` ### 2. Files vs Items 的区别 - **Files**: 只包含实际的文件对象(File类型) - **Items**: 包含所有拖拽项,包括文件和字符串数据 ### 3. text/plain vs text/html - **text/plain**: 纯文本内容,图片显示为"[图片]" - **text/html**: HTML格式,图片包含``标签,文字包含格式 ### 4. 企业微信特殊行为 1. **图片拖拽**: - Files中包含完整的File对象 - text/plain显示为"[图片]"占位符 - text/html包含img标签(可能包含base64或临时URL) 2. **文字拖拽**: - Files为空 - text/plain包含纯文本 - text/html包含带格式的HTML 3. **混合拖拽**: - Files包含所有图片文件 - text/plain按顺序显示图片和文字 - text/html混合显示img标签和文字div ## 💡 实现建议 ### 检测拖拽内容类型 ```typescript function detectDragContentType(event: DragEvent): string { const dt = event.dataTransfer; if (!dt) return 'empty'; const types = Array.from(dt.types || []); // 1. 有文件 - 可能是图片、PDF、CAD if (types.includes('Files') && dt.files && dt.files.length > 0) { const hasImage = Array.from(dt.files).some(f => f.type.startsWith('image/')); const hasPDF = Array.from(dt.files).some(f => f.type === 'application/pdf'); const hasCAD = Array.from(dt.files).some(f => f.name.toLowerCase().endsWith('.dwg') || f.name.toLowerCase().endsWith('.dxf') ); if (hasImage) return 'images'; if (hasPDF) return 'pdf'; if (hasCAD) return 'cad'; return 'files'; } // 2. 有URL链接 if (types.includes('text/uri-list')) { const uriList = dt.getData('text/uri-list'); if (uriList && uriList.startsWith('http')) { return 'url'; } } // 3. 纯文字 if (types.includes('text/plain')) { return 'text'; } return 'unknown'; } ``` ### 提取所有内容 ```typescript function extractAllDragContent(event: DragEvent): { files: File[]; images: File[]; text: string; html: string; urls: string[]; } { const dt = event.dataTransfer; if (!dt) return { files: [], images: [], text: '', html: '', urls: [] }; // 提取文件 const files: File[] = dt.files ? Array.from(dt.files) : []; const images = files.filter(f => f.type.startsWith('image/')); // 提取文字 const text = dt.getData('text/plain') || ''; const html = dt.getData('text/html') || ''; // 提取URL const uriList = dt.getData('text/uri-list') || ''; const urls = uriList.split('\n') .map(url => url.trim()) .filter(url => url && !url.startsWith('#')); return { files, images, text, html, urls }; } ``` ## 📝 测试代码 ### 完整的测试方法 ```typescript /** * 在AI对话拖拽区域添加测试 */ onAIChat DragDrop(event: DragEvent) { event.preventDefault(); event.stopPropagation(); // 🔍 详细打印数据结构 this.logDragDataStructure(event, 'AI对话区域'); // 📊 提取并打印内容 const content = this.extractAllDragContent(event); console.log('📊 提取的内容:', content); // 🎯 检测内容类型 const contentType = this.detectDragContentType(event); console.log('🎯 内容类型:', contentType); // 继续处理拖拽内容... } ``` ## 🎓 使用示例 ### 在stage-requirements组件中使用 ```typescript // 1. 在AI对话输入区域添加拖拽监听
// 2. 实现拖拽处理方法 async onAIChatDrop(event: DragEvent) { // 打印数据结构(开发测试用) this.logDragDataStructure(event, 'AI对话'); // 提取内容 const content = this.extractAllDragContent(event); // 处理图片 if (content.images.length > 0) { await this.addImagesToAIChat(content.images); } // 处理文字 if (content.text && !content.text.includes('[图片]')) { this.appendTextToAIInput(content.text); } // 处理URL if (content.urls.length > 0) { await this.downloadAndAddToAIChat(content.urls); } } ``` --- **文档版本**: 1.0.0 **创建时间**: 2025-12-03 **用途**: 数据结构分析、功能实现参考、问题排查