wechat-drag-integration-guide.md 16 KB

企业微信拖拽集成指南

🎯 目标

实现从企业微信群聊拖拽图片和文字到侧边栏,自动上传并启动AI分析。

📊 企业微信拖拽特性

支持的拖拽内容

内容类型 dataTransfer属性 说明
图片文件 files File对象数组,包含图片二进制数据
纯文本 getData('text/plain') 消息文本内容
HTML getData('text/html') 富文本消息(可能包含图片标签)
URL getData('text/uri-list') 图片或文件的URL链接

拖拽事件序列

1. dragenter  → 鼠标进入目标区域
2. dragover   → 鼠标在目标区域移动(持续触发)
3. dragleave  → 鼠标离开目标区域
4. drop       → 释放拖拽内容

🔧 实现方案

方案1: 增强现有拖拽区域

位置: space-requirements-management.component.html

<!-- 增强的拖拽上传区域 -->
<div class="drag-drop-zone drag-drop-zone-enhanced"
     (dragenter)="onDragEnter($event, space.id)"
     (dragover)="onDragOver($event, space.id)"
     (dragleave)="onDragLeave($event)"
     (drop)="onDrop($event, space.id)"
     [class.drag-over]="isDragOver && dragOverSpaceId === space.id"
     [class.wechat-mode]="isWeChatEnv">
  
  <div class="drag-drop-content">
    <!-- 企业微信模式提示 -->
    @if (isWeChatEnv) {
      <div class="wechat-hint">
        <div class="wechat-icon">💬</div>
        <h4>从群聊拖拽到这里</h4>
        <p>支持图片、文字、混合内容</p>
        <p class="ai-hint">🤖 AI将自动分析并智能归类</p>
      </div>
    } @else {
      <!-- 普通模式提示 -->
      <div class="drag-drop-icon">
        <ion-icon name="cloud-upload-outline"></ion-icon>
      </div>
      <h4>拖拽参考图片到此</h4>
      <p>或点击下方按钮上传</p>
      <p class="drag-hint">AI将自动分析图片并智能分类</p>
    }
    
    <!-- 拖拽中的视觉反馈 -->
    @if (isDragOver && dragOverSpaceId === space.id) {
      <div class="drag-feedback">
        <div class="feedback-icon">📥</div>
        <p>松开鼠标即可上传</p>
      </div>
    }
  </div>
</div>

方案2: 拖拽内容解析

位置: stage-requirements.component.ts

/**
 * 解析企业微信拖拽数据
 */
private parseWeChatDragData(event: DragEvent): {
  images: File[];
  text: string;
  html: string;
  urls: string[];
  hasContent: boolean;
  autoAnalyze: boolean;
} {
  const dataTransfer = event.dataTransfer;
  if (!dataTransfer) {
    return {
      images: [],
      text: '',
      html: '',
      urls: [],
      hasContent: false,
      autoAnalyze: false
    };
  }

  console.log('🔍 [企业微信拖拽] dataTransfer.types:', dataTransfer.types);
  console.log('🔍 [企业微信拖拽] files.length:', dataTransfer.files.length);

  // 1. 提取图片文件
  const images: File[] = [];
  if (dataTransfer.files && dataTransfer.files.length > 0) {
    for (let i = 0; i < dataTransfer.files.length; i++) {
      const file = dataTransfer.files[i];
      if (file.type.startsWith('image/')) {
        images.push(file);
        console.log(`📸 [图片文件] ${file.name} (${(file.size/1024).toFixed(2)}KB)`);
      }
    }
  }

  // 2. 提取文本内容
  const text = dataTransfer.getData('text/plain') || '';
  if (text) {
    console.log(`📝 [文本内容] ${text.substring(0, 100)}${text.length > 100 ? '...' : ''}`);
  }

  // 3. 提取HTML内容(可能包含图片标签)
  const html = dataTransfer.getData('text/html') || '';
  if (html) {
    console.log(`🌐 [HTML内容] 长度: ${html.length}`);
    // 可选:从HTML中提取图片URL
    const imgUrls = this.extractImageUrlsFromHtml(html);
    if (imgUrls.length > 0) {
      console.log(`🖼️ [HTML图片] ${imgUrls.length}个`, imgUrls);
    }
  }

  // 4. 提取URL列表
  const urlList = dataTransfer.getData('text/uri-list') || '';
  const urls = urlList.split('\n').filter(url => url.trim() && !url.startsWith('#'));
  if (urls.length > 0) {
    console.log(`🔗 [URL列表] ${urls.length}个`, urls);
  }

  // 5. 判断是否有内容
  const hasContent = images.length > 0 || text.length > 0 || urls.length > 0;

  // 6. 判断是否自动启动AI分析(有图片或有丰富文本)
  const autoAnalyze = images.length > 0 || text.length > 50;

  return {
    images,
    text,
    html,
    urls,
    hasContent,
    autoAnalyze
  };
}

/**
 * 从HTML中提取图片URL
 */
private extractImageUrlsFromHtml(html: string): string[] {
  const imgRegex = /<img[^>]+src="([^"]+)"/gi;
  const urls: string[] = [];
  let match;
  
  while ((match = imgRegex.exec(html)) !== null) {
    urls.push(match[1]);
  }
  
  return urls;
}

/**
 * 检测是否为企业微信环境
 */
private isWeChatWorkEnv(): boolean {
  const ua = window.navigator.userAgent.toLowerCase();
  return ua.includes('wxwork') || ua.includes('qywechat');
}

方案3: 增强的拖拽处理

位置: stage-requirements.component.ts

/**
 * 增强的拖拽处理(支持企业微信)
 */
async onDrop(event: DragEvent, spaceId: string): Promise<void> {
  event.preventDefault();
  event.stopPropagation();
  this.isDragOver = false;
  this.dragOverSpaceId = '';

  console.log('📥 [拖拽放下] 空间ID:', spaceId);
  console.log('📥 [拖拽放下] 环境:', this.isWeChatWorkEnv() ? '企业微信' : '普通浏览器');

  // 1. 解析拖拽数据
  const dragData = this.parseWeChatDragData(event);
  
  if (!dragData.hasContent) {
    console.warn('⚠️ [拖拽放下] 未检测到有效内容');
    return;
  }

  console.log('✅ [拖拽放下] 解析结果:', {
    图片数量: dragData.images.length,
    文字长度: dragData.text.length,
    URL数量: dragData.urls.length,
    自动分析: dragData.autoAnalyze
  });

  // 2. 处理图片文件
  if (dragData.images.length > 0) {
    console.log(`📤 [开始上传] ${dragData.images.length}个图片文件`);
    await this.uploadAndAnalyzeImages(dragData.images, spaceId);
  }

  // 3. 处理URL列表(下载并上传)
  if (dragData.urls.length > 0) {
    console.log(`🔗 [处理URL] ${dragData.urls.length}个图片链接`);
    await this.downloadAndUploadFromUrls(dragData.urls, spaceId);
  }

  // 4. 处理文本内容(保存到特殊需求)
  if (dragData.text && dragData.text.length > 0) {
    console.log(`💾 [保存文本] 到特殊需求 (${dragData.text.length}字)`);
    const existingReq = this.getSpaceSpecialRequirements(spaceId) || '';
    const newReq = existingReq 
      ? `${existingReq}\n\n--- 从企业微信拖拽 ---\n${dragData.text}`
      : dragData.text;
    this.setSpaceSpecialRequirements(spaceId, newReq);
  }

  // 5. 可选:自动启动AI设计分析
  if (dragData.autoAnalyze && dragData.images.length > 0) {
    console.log('🤖 [自动分析] 启动AI设计分析');
    setTimeout(() => {
      this.openAIDesignDialogWithFiles(spaceId, dragData.images, dragData.text);
    }, 1000); // 等待上传完成
  }

  this.cdr.markForCheck();
}

/**
 * 从URL下载并上传图片
 */
private async downloadAndUploadFromUrls(urls: string[], spaceId: string): Promise<void> {
  for (const url of urls) {
    try {
      // 检查是否为图片URL
      if (!this.isImageUrl(url)) {
        console.warn(`⚠️ [非图片URL] ${url}`);
        continue;
      }

      console.log(`📥 [下载图片] ${url}`);
      
      // 使用fetch下载图片
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`下载失败: ${response.statusText}`);
      }

      const blob = await response.blob();
      const fileName = this.extractFileNameFromUrl(url) || `image_${Date.now()}.jpg`;
      const file = new File([blob], fileName, { type: blob.type || 'image/jpeg' });

      console.log(`✅ [下载完成] ${fileName} (${(blob.size/1024).toFixed(2)}KB)`);

      // 上传图片
      await this.uploadAndAnalyzeImages([file], spaceId);

    } catch (error) {
      console.error(`❌ [下载失败] ${url}`, error);
    }
  }
}

/**
 * 判断是否为图片URL
 */
private isImageUrl(url: string): boolean {
  const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg'];
  const lowerUrl = url.toLowerCase();
  return imageExts.some(ext => lowerUrl.includes(ext)) || 
         lowerUrl.includes('image') ||
         lowerUrl.includes('photo');
}

/**
 * 从URL提取文件名
 */
private extractFileNameFromUrl(url: string): string | null {
  try {
    const urlObj = new URL(url);
    const pathname = urlObj.pathname;
    const segments = pathname.split('/');
    return segments[segments.length - 1] || null;
  } catch {
    return null;
  }
}

/**
 * 打开AI设计分析弹窗并预填内容
 */
private openAIDesignDialogWithFiles(spaceId: string, files: File[], text: string): void {
  // 1. 打开弹窗
  const space = this.projectProducts.find(p => p.id === spaceId);
  if (!space) return;

  this.openAIDesignDialog(space);

  // 2. 等待组件初始化后,预填内容
  setTimeout(() => {
    // 假设有AI分析组件引用
    if (this.aiDesignAnalysisComponent) {
      this.aiDesignAnalysisComponent.aiDesignTextDescription = text;
      this.aiDesignAnalysisComponent.processAIFiles(files);
      // 可选:自动启动分析
      // this.aiDesignAnalysisComponent.startAIDesignAnalysis();
    }
  }, 500);
}

🎨 UI优化

企业微信侧边栏适配

位置: space-requirements-management.component.scss

// 企业微信环境检测
.wechat-mode {
  // 侧边栏宽度:280-400px
  .drag-drop-zone {
    min-height: 100px; // 减小高度
    padding: 16px;
    
    .wechat-hint {
      text-align: center;
      
      .wechat-icon {
        font-size: 32px;
        margin-bottom: 8px;
        animation: pulse 2s infinite;
      }
      
      h4 {
        font-size: 14px;
        margin: 8px 0;
        color: #1a202c;
      }
      
      p {
        font-size: 12px;
        color: #718096;
        margin: 4px 0;
      }
      
      .ai-hint {
        color: #667eea;
        font-weight: 600;
        margin-top: 8px;
      }
    }
  }
  
  // 拖拽反馈
  &.drag-over {
    .drag-feedback {
      display: flex;
      flex-direction: column;
      align-items: center;
      
      .feedback-icon {
        font-size: 48px;
        animation: bounce 0.6s infinite;
      }
      
      p {
        margin-top: 8px;
        font-size: 14px;
        font-weight: 600;
        color: #667eea;
      }
    }
  }
}

@keyframes pulse {
  0%, 100% {
    opacity: 1;
    transform: scale(1);
  }
  50% {
    opacity: 0.8;
    transform: scale(1.1);
  }
}

@keyframes bounce {
  0%, 100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-10px);
  }
}

// 移动端和企业微信侧边栏
@media (max-width: 480px) {
  .space-requirements-card {
    padding: 12px;
    
    .drag-drop-zone {
      min-height: 80px;
      padding: 12px;
      
      h4 {
        font-size: 13px;
      }
      
      p {
        font-size: 11px;
      }
    }
    
    // 图片类型标签:2x2网格
    .image-type-tabs-scroll {
      grid-template-columns: repeat(2, 1fr);
      gap: 6px;
      
      .tab-button {
        padding: 8px 12px;
        font-size: 12px;
        min-height: 40px;
      }
    }
    
    // 图片展示:2列网格
    .images-grid {
      grid-template-columns: repeat(2, 1fr);
      gap: 8px;
    }
  }
}

📱 进度提示优化

上传进度Toast

位置: stage-requirements.component.ts

/**
 * 显示上传摘要(企业微信友好)
 */
private showUploadSummary(results: { success: string[], failed: Array<{name: string, error: string}> }): void {
  const successCount = results.success.length;
  const failedCount = results.failed.length;
  
  if (failedCount === 0) {
    // 全部成功 - 使用Toast
    if (window?.fmode?.toast?.success) {
      window.fmode.toast.success(`✅ 成功上传 ${successCount} 个文件\n🤖 AI正在分析中...`);
    } else {
      console.log(`✅ 成功上传 ${successCount} 个文件,AI正在分析中...`);
    }
  } else if (successCount === 0) {
    // 全部失败 - 使用Alert
    const message = `上传失败\n\n${results.failed.map(f => `• ${f.name}\n  ${f.error}`).join('\n\n')}`;
    window?.fmode?.alert?.(message);
  } else {
    // 部分成功 - 使用详细Alert
    const message = [
      `上传完成`,
      ``,
      `✅ 成功: ${successCount} 个`,
      `❌ 失败: ${failedCount} 个`,
      ``,
      `失败文件:`,
      ...results.failed.map(f => `• ${f.name}\n  ${f.error}`)
    ].join('\n');
    
    window?.fmode?.alert?.(message);
  }
}

🧪 测试场景

测试1: 拖拽单张图片

操作:从企业微信群聊拖拽1张图片到侧边栏
预期:
  ✅ 图片成功上传
  ✅ AI自动分析并归类
  ✅ 显示在对应类型标签页
  ✅ Toast提示"成功上传1个文件"

测试2: 拖拽多张图片

操作:从企业微信群聊拖拽3张图片到侧边栏
预期:
  ✅ 3张图片全部上传
  ✅ AI批量分析
  ✅ 按类型自动归类
  ✅ Toast提示"成功上传3个文件"

测试3: 拖拽图片+文字

操作:从企业微信群聊拖拽图片和文字到侧边栏
预期:
  ✅ 图片上传成功
  ✅ 文字保存到特殊需求
  ✅ 自动打开AI分析弹窗
  ✅ 文字预填到描述框

测试4: 拖拽纯文字

操作:从企业微信群聊拖拽文字到侧边栏
预期:
  ✅ 文字保存到特殊需求
  ⚠️ 不启动AI分析(无图片)
  ✅ Toast提示"已保存到特殊需求"

测试5: 拖拽失败场景

操作:拖拽超大文件(>10MB)
预期:
  ❌ 上传被拒绝
  ✅ Alert提示"文件过大"
  ✅ 显示失败文件列表

🔍 调试技巧

控制台日志

// 在onDrop方法中添加详细日志
console.log('🔍 [拖拽调试] dataTransfer:', {
  types: Array.from(event.dataTransfer?.types || []),
  files: event.dataTransfer?.files,
  filesCount: event.dataTransfer?.files?.length || 0,
  items: event.dataTransfer?.items,
  itemsCount: event.dataTransfer?.items?.length || 0
});

// 遍历DataTransferItem
if (event.dataTransfer?.items) {
  for (let i = 0; i < event.dataTransfer.items.length; i++) {
    const item = event.dataTransfer.items[i];
    console.log(`🔍 [Item ${i}]`, {
      kind: item.kind,
      type: item.type
    });
  }
}

网络面板检查

  1. 打开开发者工具 → Network
  2. 筛选:XHR/Fetch
  3. 查找上传请求:
    • URL包含uploadfile
    • Method: POST/PUT
    • Status: 200(成功)或 631(失败)
  4. 查看Request Payload:文件数据
  5. 查看Response:返回的URL

企业微信调试

// 检测企业微信环境
console.log('📱 [环境检测]', {
  userAgent: navigator.userAgent,
  isWeChatWork: /wxwork|qywechat/i.test(navigator.userAgent),
  platform: navigator.platform,
  viewport: {
    width: window.innerWidth,
    height: window.innerHeight
  }
});

📝 注意事项

1. 企业微信限制

  • 文件大小: 建议 < 10MB
  • 文件格式: JPG、PNG(避免HEIC、WebP)
  • 并发上传: 建议 <= 3个文件
  • URL访问: 需要公网可访问(AI分析需要)

2. 安全考虑

  • 文件名: 自动清理中文和特殊字符
  • URL验证: 检查是否为HTTP/HTTPS
  • 大小检查: 限制单文件10MB
  • 类型检查: 只允许图片和CAD文件

3. 性能优化

  • 异步上传: 不阻塞UI
  • 进度反馈: 实时显示上传进度
  • 错误重试: 自动重试3次
  • 批量优化: 限制并发数量

4. 用户体验

  • 拖拽提示: 明确告知用户可拖拽
  • 视觉反馈: 拖拽中高亮目标区域
  • 结果提示: Toast/Alert显示上传结果
  • AI提示: 告知AI正在分析

文档版本: 1.0.0
创建时间: 2025-12-03
适用环境: 企业微信侧边栏
兼容性: 支持Chrome、Safari、企业微信内置浏览器