# 详情图集功能使用指南

## 🎯 功能概述

详情图集功能允许为工作流和课程添加多张详情展示图片，为用户提供更丰富的视觉内容介绍。

## 🔧 技术实现

### 数据库字段
```sql
-- 工作流表
ALTER TABLE workflow ADD COLUMN detail_gallery longtext DEFAULT NULL 
COMMENT '详情图集（JSON格式存储多张图片URL）';

-- 课程表  
ALTER TABLE course ADD COLUMN detail_gallery longtext DEFAULT NULL 
COMMENT '详情图集（JSON格式存储多张图片URL）';
```

### 存储格式
```json
// 详情图集字段存储格式
"detailGallery": "[\"https://oss.../image1.jpg\",\"https://oss.../image2.jpg\",\"https://oss.../image3.jpg\"]"
```

## 📱 前端集成

### 1. 图片上传流程

```javascript
/**
 * 上传详情图集
 * @param {FileList} files - 选择的图片文件
 * @param {string} userId - 用户ID
 * @returns {Promise<string>} 详情图集JSON字符串
 */
async function uploadDetailGallery(files, userId) {
  const uploadPromises = Array.from(files).map(async (file) => {
    // 1. 获取OSS上传签名
    const signResponse = await fetch('/user/oss/post-signature/json', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        fileName: file.name,
        userId: userId
      })
    });
    
    if (!signResponse.ok) {
      throw new Error('获取上传签名失败');
    }
    
    const signResult = await signResponse.json();
    const signData = signResult.data;
    
    // 2. 构建上传表单
    const formData = new FormData();
    const objectKey = signData.dir + file.name;
    
    formData.append('key', objectKey);
    formData.append('policy', signData.policy);
    formData.append('x-oss-credential', signData.x_oss_credential);
    formData.append('x-oss-date', signData.x_oss_date);
    formData.append('x-oss-signature-version', signData.version);
    formData.append('x-oss-signature', signData.signature);
    formData.append('success_action_status', '200');
    formData.append('file', file);
    
    // 3. 上传到OSS
    const uploadResponse = await fetch(signData.url, {
      method: 'POST',
      body: formData
    });
    
    if (!uploadResponse.ok) {
      throw new Error('文件上传失败');
    }
    
    // 4. 返回完整的文件URL
    return `${signData.url}/${objectKey}`;
  });
  
  try {
    const imageUrls = await Promise.all(uploadPromises);
    return JSON.stringify(imageUrls);
  } catch (error) {
    console.error('上传详情图集失败:', error);
    throw error;
  }
}
```

### 2. 解析详情图集

```javascript
/**
 * 解析详情图集
 * @param {string} detailGallery - 详情图集JSON字符串
 * @returns {string[]} 图片URL数组
 */
function parseDetailGallery(detailGallery) {
  if (!detailGallery || detailGallery.trim() === '') {
    return [];
  }
  
  try {
    const urls = JSON.parse(detailGallery);
    return Array.isArray(urls) ? urls : [];
  } catch (error) {
    console.error('解析详情图集失败:', error);
    return [];
  }
}

/**
 * 渲染详情图集
 * @param {string} detailGallery - 详情图集JSON字符串
 * @param {HTMLElement} container - 容器元素
 */
function renderDetailGallery(detailGallery, container) {
  const imageUrls = parseDetailGallery(detailGallery);
  
  container.innerHTML = '';
  
  if (imageUrls.length === 0) {
    container.innerHTML = '<p>暂无详情图片</p>';
    return;
  }
  
  imageUrls.forEach((url, index) => {
    const img = document.createElement('img');
    img.src = url;
    img.alt = `详情图片 ${index + 1}`;
    img.className = 'detail-gallery-image';
    img.style.cssText = `
      width: 100%;
      max-width: 400px;
      height: auto;
      margin: 10px;
      border-radius: 8px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.1);
      cursor: pointer;
    `;
    
    // 点击预览
    img.addEventListener('click', () => {
      showImagePreview(url);
    });
    
    container.appendChild(img);
  });
}
```

### 3. 完整使用示例

```html
<!-- HTML -->
<div class="upload-section">
  <label for="detail-images">选择详情图片（可选择多张）:</label>
  <input type="file" id="detail-images" multiple accept="image/*">
  <button onclick="handleUpload()">上传作品</button>
</div>

<div id="preview-container"></div>

<script>
async function handleUpload() {
  const fileInput = document.getElementById('detail-images');
  const files = fileInput.files;
  
  let detailGallery = '';
  
  // 如果选择了图片，则上传详情图集
  if (files.length > 0) {
    try {
      detailGallery = await uploadDetailGallery(files, getCurrentUserId());
      console.log('详情图集上传成功:', detailGallery);
    } catch (error) {
      alert('详情图集上传失败: ' + error.message);
      return;
    }
  }
  
  // 提交工作流
  const workflowData = {
    name: "我的工作流",
    description: "工作流描述",
    detailGallery: detailGallery, // 详情图集
    vodVideoId: "a0776b0179bf71f0bea45017f1e90102",
    data: JSON.stringify({nodes: [], edges: []}),
    isFree: 1
  };
  
  try {
    const response = await fetch('/user/workflow/submit', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + getToken()
      },
      body: JSON.stringify(workflowData)
    });
    
    if (response.ok) {
      const result = await response.json();
      alert('工作流上传成功！ID: ' + result.data);
    } else {
      alert('工作流上传失败');
    }
  } catch (error) {
    alert('提交失败: ' + error.message);
  }
}

// 获取当前用户ID
function getCurrentUserId() {
  return '17543607206742139'; // 示例用户ID
}

// 获取认证token
function getToken() {
  return localStorage.getItem('token');
}
</script>
```

## 🚀 后端接口支持

### 1. 工作流相关接口

**上传工作流** - `POST /user/workflow/submit`
```json
{
  "name": "工作流名称",
  "detailGallery": "[\"url1\",\"url2\"]",
  "vodVideoId": "视频ID",
  "data": "工作流JSON数据"
}
```

**更新工作流** - `PUT /user/content/workflows/{id}`
```json
{
  "name": "更新的名称",
  "detailGallery": "[\"new_url1\",\"new_url2\"]"
}
```

### 2. 课程相关接口

**更新课程** - `PUT /user/course/{id}`
```json
{
  "title": "课程标题",
  "detailGallery": "[\"url1\",\"url2\"]",
  "price": 99.99
}
```

**用户内容管理** - `PUT /user/content/courses`
```json
{
  "id": 1,
  "title": "课程标题",
  "detailGallery": "[\"url1\",\"url2\"]"
}
```

## 📋 响应示例

### 工作流详情API响应
```json
{
  "code": 200,
  "message": "success",
  "data": {
    "workflow": {
      "id": 1,
      "name": "智能图像生成工作流",
      "coverUrl": "https://oss.../cover.jpg",
      "detailGallery": "[\"https://oss.../detail1.jpg\",\"https://oss.../detail2.jpg\",\"https://oss.../detail3.jpg\"]",
      "vodVideoId": "a0776b0179bf71f0bea45017f1e90102",
      "price": 29.99
    }
  }
}
```

### 课程详情API响应
```json
{
  "id": 1,
  "title": "AI图像处理入门课程",
  "coverUrl": "https://oss.../cover.jpg",
  "detailGallery": "[\"https://oss.../detail1.jpg\",\"https://oss.../detail2.jpg\"]",
  "price": 99.99,
  "chapters": [...]
}
```

## ⚙️ 最佳实践

### 1. 图片要求
- **尺寸**: 建议 1200x800px 或同等比例
- **格式**: 推荐 JPG/PNG
- **大小**: 单张图片不超过 5MB
- **数量**: 建议 2-5 张图片

### 2. 用户体验
- **预览功能**: 支持图片点击放大预览
- **加载优化**: 使用懒加载和图片压缩
- **错误处理**: 提供友好的错误提示
- **进度显示**: 显示上传进度

### 3. 性能优化
```javascript
// 图片压缩示例
function compressImage(file, maxWidth = 1200, quality = 0.8) {
  return new Promise((resolve) => {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    const img = new Image();
    
    img.onload = () => {
      const ratio = Math.min(maxWidth / img.width, maxWidth / img.height);
      canvas.width = img.width * ratio;
      canvas.height = img.height * ratio;
      
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
      canvas.toBlob(resolve, 'image/jpeg', quality);
    };
    
    img.src = URL.createObjectURL(file);
  });
}
```

## 🔍 测试验证

### 1. 功能测试清单
- [ ] 单张图片上传
- [ ] 多张图片批量上传
- [ ] 图片格式验证
- [ ] 文件大小限制
- [ ] 详情图集解析
- [ ] 详情图集渲染
- [ ] 接口响应验证

### 2. 兼容性测试
- [ ] 现有工作流不受影响
- [ ] 现有课程不受影响
- [ ] API响应格式保持一致
- [ ] 数据库操作正常

## 🆘 常见问题

**Q: 详情图集是必填字段吗？**
A: 不是，详情图集是可选字段，不影响现有功能。

**Q: 如何清空详情图集？**
A: 设置 `detailGallery` 为空字符串 `""` 或 `null`。

**Q: 支持的最大图片数量？**
A: 理论上无限制，但建议2-5张以获得最佳体验。

**Q: 上传失败如何处理？**
A: 实现重试机制，并提供详细的错误信息。

---

**更新时间**: 2024-12-01  
**版本**: v1.0

