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

375 lines
9.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 详情图集功能使用指南
## 🎯 功能概述
详情图集功能允许为工作流和课程添加多张详情展示图片,为用户提供更丰富的视觉内容介绍。
## 🔧 技术实现
### 数据库字段
```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