Files
1818web-hoduan/docs/detail-gallery-guide.md
2025-11-14 17:41:15 +08:00

9.0 KiB
Raw Permalink Blame History

详情图集功能使用指南

🎯 功能概述

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

🔧 技术实现

数据库字段

-- 工作流表
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';

存储格式

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

📱 前端集成

1. 图片上传流程

/**
 * 上传详情图集
 * @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. 解析详情图集

/**
 * 解析详情图集
 * @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 -->
<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

{
  "name": "工作流名称",
  "detailGallery": "[\"url1\",\"url2\"]",
  "vodVideoId": "视频ID",
  "data": "工作流JSON数据"
}

更新工作流 - PUT /user/content/workflows/{id}

{
  "name": "更新的名称",
  "detailGallery": "[\"new_url1\",\"new_url2\"]"
}

2. 课程相关接口

更新课程 - PUT /user/course/{id}

{
  "title": "课程标题",
  "detailGallery": "[\"url1\",\"url2\"]",
  "price": 99.99
}

用户内容管理 - PUT /user/content/courses

{
  "id": 1,
  "title": "课程标题",
  "detailGallery": "[\"url1\",\"url2\"]"
}

📋 响应示例

工作流详情API响应

{
  "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响应

{
  "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. 性能优化

// 图片压缩示例
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