实现从企业微信群聊拖拽图片和文字到侧边栏,自动上传并启动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 → 释放拖拽内容
位置: 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>
位置: 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');
}
位置: 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);
}
位置: 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;
}
}
}
位置: 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张图片到侧边栏
预期:
✅ 图片成功上传
✅ AI自动分析并归类
✅ 显示在对应类型标签页
✅ Toast提示"成功上传1个文件"
操作:从企业微信群聊拖拽3张图片到侧边栏
预期:
✅ 3张图片全部上传
✅ AI批量分析
✅ 按类型自动归类
✅ Toast提示"成功上传3个文件"
操作:从企业微信群聊拖拽图片和文字到侧边栏
预期:
✅ 图片上传成功
✅ 文字保存到特殊需求
✅ 自动打开AI分析弹窗
✅ 文字预填到描述框
操作:从企业微信群聊拖拽文字到侧边栏
预期:
✅ 文字保存到特殊需求
⚠️ 不启动AI分析(无图片)
✅ Toast提示"已保存到特殊需求"
操作:拖拽超大文件(>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
});
}
}
upload或file// 检测企业微信环境
console.log('📱 [环境检测]', {
userAgent: navigator.userAgent,
isWeChatWork: /wxwork|qywechat/i.test(navigator.userAgent),
platform: navigator.platform,
viewport: {
width: window.innerWidth,
height: window.innerHeight
}
});
文档版本: 1.0.0
创建时间: 2025-12-03
适用环境: 企业微信侧边栏
兼容性: 支持Chrome、Safari、企业微信内置浏览器